Merge remote-tracking branch 'upstream/develop' into task/dialogs-ts

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-09-10 18:49:55 +02:00
commit ee90ff0b98
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D
73 changed files with 2137 additions and 965 deletions

24
.github/workflows/typecheck.yaml vendored Normal file
View file

@ -0,0 +1,24 @@
name: Type Check
on:
pull_request:
branches: [develop]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: c-hive/gha-yarn-cache@v2
- name: Install Deps
run: "./scripts/ci/install-deps.sh --ignore-scripts"
- name: Typecheck
run: "yarn run lint:types"
- name: Switch js-sdk to release mode
run: |
scripts/ci/js-sdk-to-release.js
cd node_modules/matrix-js-sdk
yarn install
yarn run build:compile
yarn run build:types
- name: Typecheck (release mode)
run: "yarn run lint:types"

View file

@ -151,7 +151,7 @@
"@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0", "@typescript-eslint/parser": "^4.17.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"allchange": "^1.0.2", "allchange": "^1.0.3",
"babel-jest": "^26.6.3", "babel-jest": "^26.6.3",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"concurrently": "^5.3.0", "concurrently": "^5.3.0",

View file

@ -114,6 +114,7 @@ $activeBorderColor: $secondary-content;
align-items: center; align-items: center;
padding: 4px 4px 4px 0; padding: 4px 4px 4px 0;
width: 100%; width: 100%;
cursor: pointer;
&.mx_SpaceButton_active { &.mx_SpaceButton_active {
&:not(.mx_SpaceButton_narrow) .mx_SpaceButton_selectionWrapper { &:not(.mx_SpaceButton_narrow) .mx_SpaceButton_selectionWrapper {

View file

@ -96,3 +96,10 @@ div.mx_AccessibleButton_kind_link.mx_Login_forgot {
cursor: not-allowed; cursor: not-allowed;
} }
} }
.mx_Login_spinner {
display: flex;
justify-content: center;
align-items: center;
align-content: center;
padding: 14px;
}

View file

@ -33,6 +33,14 @@ limitations under the License.
margin-top: 2px; margin-top: 2px;
} }
&.mx_EventTile_highlight {
&::before {
background-color: $event-highlight-bg-color;
}
color: $event-highlight-fg-color;
}
/* For replies */ /* For replies */
.mx_EventTile { .mx_EventTile {
padding-top: 0; padding-top: 0;

View file

@ -733,4 +733,8 @@ $hover-select-border: 4px;
padding-bottom: 5px; padding-bottom: 5px;
margin-bottom: 5px; margin-bottom: 5px;
} }
.mx_MessageComposer_sendMessage {
margin-right: 0;
}
} }

View file

@ -186,11 +186,14 @@ limitations under the License.
} }
.mx_MessageComposer_button { .mx_MessageComposer_button {
--size: 26px;
position: relative; position: relative;
margin-right: 6px; margin-right: 6px;
cursor: pointer; cursor: pointer;
height: 26px; height: var(--size);
width: 26px; line-height: var(--size);
width: auto;
padding-left: calc(var(--size) + 5px);
border-radius: 100%; border-radius: 100%;
&::before { &::before {
@ -207,8 +210,22 @@ limitations under the License.
mask-position: center; mask-position: center;
} }
&:hover { &::after {
content: '';
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: var(--size);
height: var(--size);
border-radius: 50%;
}
&:hover,
&.mx_MessageComposer_closeButtonMenu {
&::after {
background: rgba($accent-color, 0.1); background: rgba($accent-color, 0.1);
}
&::before { &::before {
background-color: $accent-color; background-color: $accent-color;
@ -237,10 +254,18 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg'); mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg');
} }
.mx_MessageComposer_buttonMenu::before {
mask-image: url('$(res)/img/image-view/more.svg');
}
.mx_MessageComposer_closeButtonMenu::before {
transform: rotate(90deg);
transform-origin: center;
}
.mx_MessageComposer_sendMessage { .mx_MessageComposer_sendMessage {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
margin-right: 6px;
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: 100%; border-radius: 100%;
@ -349,10 +374,19 @@ limitations under the License.
margin-right: 0; margin-right: 0;
.mx_MessageComposer_wrapper { .mx_MessageComposer_wrapper {
padding: 0; padding: 0 0 0 25px;
} }
.mx_MessageComposer_button:last-child { .mx_MessageComposer_button:last-child {
margin-right: 0; margin-right: 0;
} }
.mx_MessageComposer_e2eIcon {
left: 0;
}
}
.mx_MessageComposer_Menu .mx_CallContextMenu_item {
display: flex;
align-items: center;
} }

17
scripts/ci/js-sdk-to-release.js Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env node
const fsProm = require('fs/promises');
const PKGJSON = 'node_modules/matrix-js-sdk/package.json';
async function main() {
const pkgJson = JSON.parse(await fsProm.readFile(PKGJSON, 'utf8'));
for (const field of ['main', 'typings']) {
if (pkgJson["matrix_lib_"+field] !== undefined) {
pkgJson[field] = pkgJson["matrix_lib_"+field];
}
}
await fsProm.writeFile(PKGJSON, JSON.stringify(pkgJson, null, 2));
}
main();

View file

@ -1,21 +0,0 @@
#!/bin/sh
# This changes the js-sdk into 'release mode', that is:
# * The entry point for the library is the babel-compiled lib/index.js rather than src/index.ts
# * There's a 'typings' entry referencing the types output by tsc
# We do this so we can test that each PR still builds / type checks correctly when built
# against the released js-sdk, because if you do things like `import { User } from 'matrix-js-sdk';`
# rather than `import { User } from 'matrix-js-sdk/src/models/user';` it will work fine with the
# js-sdk in development mode but then break at release time.
# We can't use the last release of the js-sdk though: it might not be up to date enough.
cd node_modules/matrix-js-sdk
for i in main typings
do
lib_value=$(jq -r ".matrix_lib_$i" package.json)
if [ "$lib_value" != "null" ]; then
jq ".$i = .matrix_lib_$i" package.json > package.json.new && mv package.json.new package.json
fi
done
yarn run build:compile
yarn run build:types

View file

@ -93,6 +93,26 @@ declare global {
mxSetupEncryptionStore?: SetupEncryptionStore; mxSetupEncryptionStore?: SetupEncryptionStore;
mxRoomScrollStateStore?: RoomScrollStateStore; mxRoomScrollStateStore?: RoomScrollStateStore;
mxOnRecaptchaLoaded?: () => void; mxOnRecaptchaLoaded?: () => void;
electron?: Electron;
}
interface DesktopCapturerSource {
id: string;
name: string;
thumbnailURL: string;
}
interface GetSourcesOptions {
types: Array<string>;
thumbnailSize?: {
height: number;
width: number;
};
fetchWindowIcons?: boolean;
}
interface Electron {
getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>>;
} }
interface Document { interface Document {

View file

@ -39,6 +39,8 @@ import {
import { IUpload } from "./models/IUpload"; import { IUpload } from "./models/IUpload";
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials"; import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
import { BlurhashEncoder } from "./BlurhashEncoder"; import { BlurhashEncoder } from "./BlurhashEncoder";
import SettingsStore from "./settings/SettingsStore";
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
const MAX_WIDTH = 800; const MAX_WIDTH = 800;
const MAX_HEIGHT = 600; const MAX_HEIGHT = 600;
@ -539,6 +541,10 @@ export default class ContentMessages {
msgtype: "", // set later msgtype: "", // set later
}; };
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
decorateStartSendingTime(content);
}
// if we have a mime type for the file, add it to the message metadata // if we have a mime type for the file, add it to the message metadata
if (file.type) { if (file.type) {
content.info.mimetype = file.type; content.info.mimetype = file.type;
@ -614,6 +620,11 @@ export default class ContentMessages {
}).then(function() { }).then(function() {
if (upload.canceled) throw new UploadCanceledError(); if (upload.canceled) throw new UploadCanceledError();
const prom = matrixClient.sendMessage(roomId, content); const prom = matrixClient.sendMessage(roomId, content);
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
prom.then(resp => {
sendRoundTripMetric(matrixClient, roomId, resp.event_id);
});
}
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, content); CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, content);
return prom; return prom;
}, function(err) { }, function(err) {

View file

@ -100,6 +100,7 @@ export default class DeviceListener {
* @param {String[]} deviceIds List of device IDs to dismiss notifications for * @param {String[]} deviceIds List of device IDs to dismiss notifications for
*/ */
async dismissUnverifiedSessions(deviceIds: Iterable<string>) { async dismissUnverifiedSessions(deviceIds: Iterable<string>) {
console.log("Dismissing unverified sessions: " + Array.from(deviceIds).join(','));
for (const d of deviceIds) { for (const d of deviceIds) {
this.dismissed.add(d); this.dismissed.add(d);
} }
@ -285,6 +286,9 @@ export default class DeviceListener {
} }
} }
console.log("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(','));
console.log("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(','));
// Display or hide the batch toast for old unverified sessions // Display or hide the batch toast for old unverified sessions
if (oldUnverifiedDeviceIds.size > 0) { if (oldUnverifiedDeviceIds.size > 0) {
showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds); showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds);

View file

@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { Playback, PlaybackState } from "./Playback"; import { Playback, PlaybackState } from "./Playback";
import { UPDATE_EVENT } from "../stores/AsyncStore"; import { UPDATE_EVENT } from "../stores/AsyncStore";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";

View file

@ -105,8 +105,12 @@ export default class CallEventGrouper extends EventEmitter {
return ![...this.events].some((event) => event.sender?.userId === MatrixClientPeg.get().getUserId()); return ![...this.events].some((event) => event.sender?.userId === MatrixClientPeg.get().getUserId());
} }
private get callId(): string { private get callId(): string | undefined {
return [...this.events][0].getContent().call_id; return [...this.events][0]?.getContent()?.call_id;
}
private get roomId(): string | undefined {
return [...this.events][0]?.getRoomId();
} }
private onSilencedCallsChanged = () => { private onSilencedCallsChanged = () => {
@ -119,18 +123,24 @@ export default class CallEventGrouper extends EventEmitter {
}; };
public answerCall = () => { public answerCall = () => {
this.call?.answer(); defaultDispatcher.dispatch({
action: 'answer',
room_id: this.roomId,
});
}; };
public rejectCall = () => { public rejectCall = () => {
this.call?.reject(); defaultDispatcher.dispatch({
action: 'reject',
room_id: this.roomId,
});
}; };
public callBack = () => { public callBack = () => {
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: 'place_call', action: 'place_call',
type: this.isVoice ? CallType.Voice : CallType.Video, type: this.isVoice ? CallType.Voice : CallType.Video,
room_id: [...this.events][0]?.getRoomId(), room_id: this.roomId,
}); });
}; };

View file

@ -399,7 +399,9 @@ export default class LeftPanel extends React.Component<IProps, IState> {
mx_LeftPanel_exploreButton_space: !!this.state.activeSpace, mx_LeftPanel_exploreButton_space: !!this.state.activeSpace,
})} })}
onClick={this.onExplore} onClick={this.onExplore}
title={_t("Explore rooms")} title={this.state.activeSpace
? _t("Explore %(spaceName)s", { spaceName: this.state.activeSpace.name })
: _t("Explore rooms")}
/> />
</div> </div>
); );

View file

@ -448,7 +448,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// Always show highlighted event // Always show highlighted event
if (this.props.highlightedEventId === mxEv.getId()) return true; if (this.props.highlightedEventId === mxEv.getId()) return true;
if (mxEv.replyEventId if (mxEv.replyInThread
&& this.props.hideThreadedMessages && this.props.hideThreadedMessages
&& SettingsStore.getValue("feature_thread")) { && SettingsStore.getValue("feature_thread")) {
return false; return false;
@ -705,9 +705,9 @@ export default class MessagePanel extends React.Component<IProps, IState> {
let willWantDateSeparator = false; let willWantDateSeparator = false;
let lastInSection = true; let lastInSection = true;
if (nextEvent) { if (nextEventWithTile) {
willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date()); willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEventWithTile.getDate() || new Date());
lastInSection = willWantDateSeparator || mxEv.getSender() !== nextEvent.getSender(); lastInSection = willWantDateSeparator || mxEv.getSender() !== nextEventWithTile.getSender();
} }
// is this a continuation of the previous message? // is this a continuation of the previous message?

View file

@ -53,6 +53,7 @@ import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import SpaceStore from "../../stores/SpaceStore"; import SpaceStore from "../../stores/SpaceStore";
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
import { E2EStatus } from '../../utils/ShieldUtils';
interface IProps { interface IProps {
room?: Room; // if showing panels for a given room, this is set room?: Room; // if showing panels for a given room, this is set
@ -60,6 +61,7 @@ interface IProps {
user?: User; // used if we know the user ahead of opening the panel user?: User; // used if we know the user ahead of opening the panel
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
} }
interface IState { interface IState {
@ -269,7 +271,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
case RightPanelPhases.EncryptionPanel: case RightPanelPhases.EncryptionPanel:
panel = <UserInfo panel = <UserInfo
user={this.state.member} user={this.state.member}
room={this.state.phase === RightPanelPhases.SpaceMemberInfo ? this.state.space : this.props.room} room={this.context.getRoom(this.state.member.roomId) ?? this.props.room}
key={roomId || this.state.member.userId} key={roomId || this.state.member.userId}
onClose={this.onClose} onClose={this.onClose}
phase={this.state.phase} phase={this.state.phase}
@ -319,7 +321,8 @@ export default class RightPanel extends React.Component<IProps, IState> {
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
onClose={this.onClose} onClose={this.onClose}
mxEvent={this.state.event} mxEvent={this.state.event}
permalinkCreator={this.props.permalinkCreator} />; permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus} />;
break; break;
case RightPanelPhases.ThreadPanel: case RightPanelPhases.ThreadPanel:

View file

@ -1848,6 +1848,19 @@ export default class RoomView extends React.Component<IProps, IState> {
/>; />;
} }
const statusBarAreaClass = classNames("mx_RoomView_statusArea", {
"mx_RoomView_statusArea_expanded": isStatusAreaExpanded,
});
// if statusBar does not exist then statusBarArea is blank and takes up unnecessary space on the screen
// show statusBarArea only if statusBar is present
const statusBarArea = statusBar && <div className={statusBarAreaClass}>
<div className="mx_RoomView_statusAreaBox">
<div className="mx_RoomView_statusAreaBox_line" />
{ statusBar }
</div>
</div>;
const roomVersionRecommendation = this.state.upgradeRecommendation; const roomVersionRecommendation = this.state.upgradeRecommendation;
const showRoomUpgradeBar = ( const showRoomUpgradeBar = (
roomVersionRecommendation && roomVersionRecommendation &&
@ -2045,16 +2058,13 @@ export default class RoomView extends React.Component<IProps, IState> {
/>); />);
} }
const statusBarAreaClass = classNames("mx_RoomView_statusArea", {
"mx_RoomView_statusArea_expanded": isStatusAreaExpanded,
});
const showRightPanel = this.state.room && this.state.showRightPanel; const showRightPanel = this.state.room && this.state.showRightPanel;
const rightPanel = showRightPanel const rightPanel = showRightPanel
? <RightPanel ? <RightPanel
room={this.state.room} room={this.state.room}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)} /> permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
e2eStatus={this.state.e2eStatus} />
: null; : null;
const timelineClasses = classNames("mx_RoomView_timeline", { const timelineClasses = classNames("mx_RoomView_timeline", {
@ -2097,12 +2107,7 @@ export default class RoomView extends React.Component<IProps, IState> {
{ messagePanel } { messagePanel }
{ searchResultsPanel } { searchResultsPanel }
</div> </div>
<div className={statusBarAreaClass}> { statusBarArea }
<div className="mx_RoomView_statusAreaBox">
<div className="mx_RoomView_statusAreaBox_line" />
{ statusBar }
</div>
</div>
{ previewBar } { previewBar }
{ messageComposer } { messageComposer }
</div> </div>

View file

@ -89,7 +89,7 @@ interface IProps {
interface IState { interface IState {
phase: Phase; phase: Phase;
createdRooms?: boolean; // internal state for the creation wizard firstRoomId?: string; // internal state for the creation wizard
showRightPanel: boolean; showRightPanel: boolean;
myMembership: string; myMembership: string;
} }
@ -508,7 +508,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
try { try {
const isPublic = space.getJoinRule() === JoinRule.Public; const isPublic = space.getJoinRule() === JoinRule.Public;
const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean); const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean);
await Promise.all(filteredRoomNames.map(name => { const roomIds = await Promise.all(filteredRoomNames.map(name => {
return createRoom({ return createRoom({
createOpts: { createOpts: {
preset: isPublic ? Preset.PublicChat : Preset.PrivateChat, preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
@ -523,7 +523,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
suggested: true, suggested: true,
}); });
})); }));
onFinished(filteredRoomNames.length > 0); onFinished(roomIds[0]);
} catch (e) { } catch (e) {
console.error("Failed to create initial space rooms", e); console.error("Failed to create initial space rooms", e);
setError(_t("Failed to create initial space rooms")); setError(_t("Failed to create initial space rooms"));
@ -533,7 +533,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
let onClick = (ev) => { let onClick = (ev) => {
ev.preventDefault(); ev.preventDefault();
onFinished(false); onFinished();
}; };
let buttonLabel = _t("Skip for now"); let buttonLabel = _t("Skip for now");
if (roomNames.some(name => name.trim())) { if (roomNames.some(name => name.trim())) {
@ -588,7 +588,11 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
</div>; </div>;
}; };
const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRooms }) => { interface ISpaceSetupPublicShareProps extends Pick<IProps & IState, "justCreatedOpts" | "space" | "firstRoomId"> {
onFinished(): void;
}
const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, firstRoomId }: ISpaceSetupPublicShareProps) => {
return <div className="mx_SpaceRoomView_publicShare"> return <div className="mx_SpaceRoomView_publicShare">
<h1>{ _t("Share %(name)s", { <h1>{ _t("Share %(name)s", {
name: justCreatedOpts?.createOpts?.name || space.name, name: justCreatedOpts?.createOpts?.name || space.name,
@ -601,7 +605,7 @@ const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRoom
<div className="mx_SpaceRoomView_buttons"> <div className="mx_SpaceRoomView_buttons">
<AccessibleButton kind="primary" onClick={onFinished}> <AccessibleButton kind="primary" onClick={onFinished}>
{ createdRooms ? _t("Go to my first room") : _t("Go to my space") } { firstRoomId ? _t("Go to my first room") : _t("Go to my space") }
</AccessibleButton> </AccessibleButton>
</div> </div>
</div>; </div>;
@ -844,35 +848,10 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
}; };
private goToFirstRoom = async () => { private goToFirstRoom = async () => {
// TODO actually go to the first room if (this.state.firstRoomId) {
const childRooms = SpaceStore.instance.getChildRooms(this.props.space.roomId);
if (childRooms.length) {
const room = childRooms[0];
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: "view_room", action: "view_room",
room_id: room.roomId, room_id: this.state.firstRoomId,
});
return;
}
let suggestedRooms = SpaceStore.instance.suggestedRooms;
if (SpaceStore.instance.activeSpace !== this.props.space) {
// the space store has the suggested rooms loaded for a different space, fetch the right ones
suggestedRooms = (await SpaceStore.instance.fetchSuggestedRooms(this.props.space, 1));
}
if (suggestedRooms.length) {
const room = suggestedRooms[0];
defaultDispatcher.dispatch({
action: "view_room",
room_id: room.room_id,
room_alias: room.canonical_alias || room.aliases?.[0],
via_servers: room.viaServers,
oobData: {
avatarUrl: room.avatar_url,
name: room.name || room.canonical_alias || room.aliases?.[0] || _t("Empty room"),
},
}); });
return; return;
} }
@ -902,14 +881,14 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
_t("Let's create a room for each of them.") + "\n" + _t("Let's create a room for each of them.") + "\n" +
_t("You can add more later too, including already existing ones.") _t("You can add more later too, including already existing ones.")
} }
onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.PublicShare, createdRooms })} onFinished={(firstRoomId: string) => this.setState({ phase: Phase.PublicShare, firstRoomId })}
/>; />;
case Phase.PublicShare: case Phase.PublicShare:
return <SpaceSetupPublicShare return <SpaceSetupPublicShare
justCreatedOpts={this.props.justCreatedOpts} justCreatedOpts={this.props.justCreatedOpts}
space={this.props.space} space={this.props.space}
onFinished={this.goToFirstRoom} onFinished={this.goToFirstRoom}
createdRooms={this.state.createdRooms} firstRoomId={this.state.firstRoomId}
/>; />;
case Phase.PrivateScope: case Phase.PrivateScope:
@ -931,7 +910,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
title={_t("What projects are you working on?")} title={_t("What projects are you working on?")}
description={_t("We'll create rooms for each of them. " + description={_t("We'll create rooms for each of them. " +
"You can add more later too, including already existing ones.")} "You can add more later too, including already existing ones.")}
onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.Landing, createdRooms })} onFinished={(firstRoomId: string) => this.setState({ phase: Phase.Landing, firstRoomId })}
/>; />;
case Phase.PrivateExistingRooms: case Phase.PrivateExistingRooms:
return <SpaceAddExistingRooms return <SpaceAddExistingRooms

View file

@ -32,6 +32,8 @@ import dis from "../../dispatcher/dispatcher";
import { ActionPayload } from '../../dispatcher/payloads'; import { ActionPayload } from '../../dispatcher/payloads';
import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload'; import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload';
import { Action } from '../../dispatcher/actions'; import { Action } from '../../dispatcher/actions';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import { E2EStatus } from '../../utils/ShieldUtils';
interface IProps { interface IProps {
room: Room; room: Room;
@ -39,6 +41,7 @@ interface IProps {
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
} }
interface IState { interface IState {
@ -49,6 +52,7 @@ interface IState {
@replaceableComponent("structures.ThreadView") @replaceableComponent("structures.ThreadView")
export default class ThreadView extends React.Component<IProps, IState> { export default class ThreadView extends React.Component<IProps, IState> {
private dispatcherRef: string; private dispatcherRef: string;
private timelinePanelRef: React.RefObject<TimelinePanel> = React.createRef();
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -89,12 +93,15 @@ export default class ThreadView extends React.Component<IProps, IState> {
}; };
private setupThread = (mxEv: MatrixEvent) => { private setupThread = (mxEv: MatrixEvent) => {
const thread = mxEv.getThread(); let thread = mxEv.getThread();
if (thread) { if (!thread) {
const client = MatrixClientPeg.get();
thread = new Thread([mxEv], this.props.room, client);
mxEv.setThread(thread);
}
thread.on("Thread.update", this.updateThread); thread.on("Thread.update", this.updateThread);
thread.once("Thread.ready", this.updateThread); thread.once("Thread.ready", this.updateThread);
this.updateThread(thread); this.updateThread(thread);
}
}; };
private teardownThread = () => { private teardownThread = () => {
@ -106,10 +113,13 @@ export default class ThreadView extends React.Component<IProps, IState> {
private updateThread = (thread?: Thread) => { private updateThread = (thread?: Thread) => {
if (thread) { if (thread) {
this.setState({ thread }); this.setState({
} else { thread,
this.forceUpdate(); replyToEvent: thread.replyToEvent,
});
} }
this.timelinePanelRef.current?.refreshTimeline();
}; };
public render(): JSX.Element { public render(): JSX.Element {
@ -122,6 +132,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
> >
{ this.state.thread && ( { this.state.thread && (
<TimelinePanel <TimelinePanel
ref={this.timelinePanelRef}
manageReadReceipts={false} manageReadReceipts={false}
manageReadMarkers={false} manageReadMarkers={false}
timelineSet={this.state?.thread?.timelineSet} timelineSet={this.state?.thread?.timelineSet}
@ -140,6 +151,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
replyToEvent={this.state?.thread?.replyToEvent} replyToEvent={this.state?.thread?.replyToEvent}
showReplyPreview={false} showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus}
compact={true} compact={true}
/> />
</BaseCard> </BaseCard>

View file

@ -47,11 +47,14 @@ import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import Spinner from "../views/elements/Spinner"; import Spinner from "../views/elements/Spinner";
import EditorStateTransfer from '../../utils/EditorStateTransfer'; import EditorStateTransfer from '../../utils/EditorStateTransfer';
import ErrorDialog from '../views/dialogs/ErrorDialog'; import ErrorDialog from '../views/dialogs/ErrorDialog';
import { debounce } from 'lodash';
const PAGINATE_SIZE = 20; const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20; const INITIAL_SIZE = 20;
const READ_RECEIPT_INTERVAL_MS = 500; const READ_RECEIPT_INTERVAL_MS = 500;
const READ_MARKER_DEBOUNCE_MS = 100;
const DEBUG = false; const DEBUG = false;
let debuglog = function(...s: any[]) {}; let debuglog = function(...s: any[]) {};
@ -475,6 +478,20 @@ class TimelinePanel extends React.Component<IProps, IState> {
} }
if (this.props.manageReadMarkers) { if (this.props.manageReadMarkers) {
this.doManageReadMarkers();
}
};
/*
* Debounced function to manage read markers because we don't need to
* do this on every tiny scroll update. It also sets state which causes
* a component update, which can in turn reset the scroll position, so
* it's important we allow the browser to scroll a bit before running this
* (hence trailing edge only and debounce rather than throttle because
* we really only need to update this once the user has finished scrolling,
* not periodically while they scroll).
*/
private doManageReadMarkers = debounce(() => {
const rmPosition = this.getReadMarkerPosition(); const rmPosition = this.getReadMarkerPosition();
// we hide the read marker when it first comes onto the screen, but if // we hide the read marker when it first comes onto the screen, but if
// it goes back off the top of the screen (presumably because the user // it goes back off the top of the screen (presumably because the user
@ -488,8 +505,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
const timeout = this.readMarkerTimeout(rmPosition); const timeout = this.readMarkerTimeout(rmPosition);
// NO-OP when timeout already has set to the given value // NO-OP when timeout already has set to the given value
this.readMarkerActivityTimer.changeTimeout(timeout); this.readMarkerActivityTimer.changeTimeout(timeout);
} }, READ_MARKER_DEBOUNCE_MS, { leading: false, trailing: true });
};
private onAction = (payload: ActionPayload): void => { private onAction = (payload: ActionPayload): void => {
switch (payload.action) { switch (payload.action) {
@ -1179,6 +1195,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
this.setState(this.getEvents()); this.setState(this.getEvents());
} }
// Force refresh the timeline before threads support pending events
public refreshTimeline(): void {
this.loadTimeline();
this.reloadEvents();
}
// get the list of events from the timeline window and the pending event list // get the list of events from the timeline window and the pending event list
private getEvents(): Pick<IState, "events" | "liveEvents" | "firstVisibleEventIndex"> { private getEvents(): Pick<IState, "events" | "liveEvents" | "firstVisibleEventIndex"> {
const events: MatrixEvent[] = this.timelineWindow.getEvents(); const events: MatrixEvent[] = this.timelineWindow.getEvents();

View file

@ -31,6 +31,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm'; import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
import { IValidationResult } from "../../views/elements/Validation"; import { IValidationResult } from "../../views/elements/Validation";
import InlineSpinner from '../../views/elements/InlineSpinner';
enum Phase { enum Phase {
// Show the forgot password inputs // Show the forgot password inputs
@ -66,13 +67,14 @@ interface IState {
serverDeadError: string; serverDeadError: string;
passwordFieldValid: boolean; passwordFieldValid: boolean;
currentHttpRequest?: Promise<any>;
} }
@replaceableComponent("structures.auth.ForgotPassword") @replaceableComponent("structures.auth.ForgotPassword")
export default class ForgotPassword extends React.Component<IProps, IState> { export default class ForgotPassword extends React.Component<IProps, IState> {
private reset: PasswordReset; private reset: PasswordReset;
state = { state: IState = {
phase: Phase.Forgot, phase: Phase.Forgot,
email: "", email: "",
password: "", password: "",
@ -148,8 +150,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
console.error("onVerify called before submitPasswordReset!"); console.error("onVerify called before submitPasswordReset!");
return; return;
} }
if (this.state.currentHttpRequest) return;
try { try {
await this.reset.checkEmailLinkClicked(); await this.handleHttpRequest(this.reset.checkEmailLinkClicked());
this.setState({ phase: Phase.Done }); this.setState({ phase: Phase.Done });
} catch (err) { } catch (err) {
this.showErrorDialog(err.message); this.showErrorDialog(err.message);
@ -158,9 +162,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
private onSubmitForm = async (ev: React.FormEvent): Promise<void> => { private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
ev.preventDefault(); ev.preventDefault();
if (this.state.currentHttpRequest) return;
// refresh the server errors, just in case the server came back online // refresh the server errors, just in case the server came back online
await this.checkServerLiveliness(this.props.serverConfig); await this.handleHttpRequest(this.checkServerLiveliness(this.props.serverConfig));
await this['password_field'].validate({ allowEmpty: false }); await this['password_field'].validate({ allowEmpty: false });
@ -221,6 +226,17 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
}); });
} }
private handleHttpRequest<T = unknown>(request: Promise<T>): Promise<T> {
this.setState({
currentHttpRequest: request,
});
return request.finally(() => {
this.setState({
currentHttpRequest: undefined,
});
});
}
renderForgot() { renderForgot() {
const Field = sdk.getComponent('elements.Field'); const Field = sdk.getComponent('elements.Field');
@ -320,6 +336,9 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
type="button" type="button"
onClick={this.onVerify} onClick={this.onVerify}
value={_t('I have verified my email address')} /> value={_t('I have verified my email address')} />
{ this.state.currentHttpRequest && (
<div className="mx_Login_spinner"><InlineSpinner w={64} h={64} /></div>)
}
</div>; </div>;
} }
@ -357,6 +376,8 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
case Phase.Done: case Phase.Done:
resetPasswordJsx = this.renderDone(); resetPasswordJsx = this.renderDone();
break; break;
default:
resetPasswordJsx = <div className="mx_Login_spinner"><InlineSpinner w={64} h={64} /></div>;
} }
return ( return (

View file

@ -168,7 +168,7 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase, action: Action.SetRightPanelPhase,
phase: RightPanelPhases.SpaceMemberList, phase: RightPanelPhases.SpaceMemberList,
refireParams: { space: space }, refireParams: { space },
}); });
onFinished(); onFinished();
}; };

View file

@ -79,7 +79,10 @@ export default class RoomSettingsDialog extends React.Component<IProps> {
ROOM_SECURITY_TAB, ROOM_SECURITY_TAB,
_td("Security & Privacy"), _td("Security & Privacy"),
"mx_RoomSettingsDialog_securityIcon", "mx_RoomSettingsDialog_securityIcon",
<SecurityRoomSettingsTab roomId={this.props.roomId} />, <SecurityRoomSettingsTab
roomId={this.props.roomId}
closeSettingsFn={() => this.props.onFinished(true)}
/>,
)); ));
tabs.push(new Tab( tabs.push(new Tab(
ROOM_ROLES_TAB, ROOM_ROLES_TAB,

View file

@ -158,7 +158,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
if (this.state.linkSpecificEvent) { if (this.state.linkSpecificEvent) {
matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId()); matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId());
} else { } else {
matrixToUrl = this.props.permalinkCreator.forRoom(); matrixToUrl = this.props.permalinkCreator.forShareableRoom();
} }
} }
return matrixToUrl; return matrixToUrl;

View file

@ -60,7 +60,7 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
SpaceSettingsTab.Visibility, SpaceSettingsTab.Visibility,
_td("Visibility"), _td("Visibility"),
"mx_SpaceSettingsDialog_visibilityIcon", "mx_SpaceSettingsDialog_visibilityIcon",
<SpaceSettingsVisibilityTab matrixClient={cli} space={space} />, <SpaceSettingsVisibilityTab matrixClient={cli} space={space} closeSettingsFn={onFinished} />,
), ),
SettingsStore.getValue(UIFeature.AdvancedSettings) SettingsStore.getValue(UIFeature.AdvancedSettings)
? new Tab( ? new Tab(

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2019 Travis Ralston Copyright 2019 Travis Ralston
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -20,13 +21,13 @@ import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import { Widget, WidgetKind } from "matrix-widget-api"; import { Widget, WidgetKind } from "matrix-widget-api";
import { OIDCState, WidgetPermissionStore } from "../../../stores/widgets/WidgetPermissionStore"; import { OIDCState, WidgetPermissionStore } from "../../../stores/widgets/WidgetPermissionStore";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import DialogButtons from "../elements/DialogButtons"; import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
interface IProps { interface IProps extends IDialogProps {
onFinished: (confirmed: boolean) => void;
widget: Widget; widget: Widget;
widgetKind: WidgetKind; // WidgetKind from widget-api widgetKind: WidgetKind;
inRoomId?: string; inRoomId?: string;
} }
@ -35,7 +36,7 @@ interface IState {
} }
@replaceableComponent("views.dialogs.WidgetOpenIDPermissionsDialog") @replaceableComponent("views.dialogs.WidgetOpenIDPermissionsDialog")
export default class WidgetOpenIDPermissionsDialog extends React.Component<IProps, IState> { export default class WidgetOpenIDPermissionsDialog extends React.PureComponent<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -69,7 +70,7 @@ export default class WidgetOpenIDPermissionsDialog extends React.Component<IProp
this.setState({ rememberSelection: newVal }); this.setState({ rememberSelection: newVal });
}; };
render() { public render(): JSX.Element {
return ( return (
<BaseDialog <BaseDialog
className='mx_WidgetOpenIDPermissionsDialog' className='mx_WidgetOpenIDPermissionsDialog'

View file

@ -25,6 +25,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> { interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
title: string; title: string;
tooltip?: React.ReactNode; tooltip?: React.ReactNode;
label?: React.ReactNode;
tooltipClassName?: string; tooltipClassName?: string;
forceHide?: boolean; forceHide?: boolean;
yOffset?: number; yOffset?: number;
@ -84,7 +85,8 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
aria-label={title} aria-label={title}
> >
{ children } { children }
{ tip } { this.props.label }
{ (tooltip || title) && tip }
</AccessibleButton> </AccessibleButton>
); );
} }

View file

@ -20,14 +20,21 @@ import BaseDialog from "..//dialogs/BaseDialog";
import DialogButtons from "./DialogButtons"; import DialogButtons from "./DialogButtons";
import classNames from 'classnames'; import classNames from 'classnames';
import AccessibleButton from './AccessibleButton'; import AccessibleButton from './AccessibleButton';
import { getDesktopCapturerSources } from "matrix-js-sdk/src/webrtc/call";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView'; import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView';
export interface DesktopCapturerSource { export function getDesktopCapturerSources(): Promise<Array<DesktopCapturerSource>> {
id: string; const options: GetSourcesOptions = {
name: string; thumbnailSize: {
thumbnailURL; height: 176,
width: 312,
},
types: [
"screen",
"window",
],
};
return window.electron.getDesktopCapturerSources(options);
} }
export enum Tabs { export enum Tabs {
@ -78,7 +85,7 @@ export interface PickerIState {
selectedSource: DesktopCapturerSource | null; selectedSource: DesktopCapturerSource | null;
} }
export interface PickerIProps { export interface PickerIProps {
onFinished(source: DesktopCapturerSource): void; onFinished(sourceId: string): void;
} }
@replaceableComponent("views.elements.DesktopCapturerSourcePicker") @replaceableComponent("views.elements.DesktopCapturerSourcePicker")
@ -123,7 +130,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<
}; };
private onShare = (): void => { private onShare = (): void => {
this.props.onFinished(this.state.selectedSource); this.props.onFinished(this.state.selectedSource.id);
}; };
private onTabChange = (): void => { private onTabChange = (): void => {

View file

@ -16,26 +16,38 @@ limitations under the License.
import React, { forwardRef, useContext } from 'react'; import React, { forwardRef, useContext } from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { IRoomEncryption } from "matrix-js-sdk/src/crypto/RoomList";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import EventTileBubble from "./EventTileBubble"; import EventTileBubble from "./EventTileBubble";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
import { objectHasDiff } from "../../../utils/objects";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
} }
const ALGORITHM = "m.megolm.v1.aes-sha2";
const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent }, ref) => { const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent }, ref) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const roomId = mxEvent.getRoomId(); const roomId = mxEvent.getRoomId();
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId); const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
if (mxEvent.getContent().algorithm === 'm.megolm.v1.aes-sha2' && isRoomEncrypted) { const prevContent = mxEvent.getPrevContent() as IRoomEncryption;
const content = mxEvent.getContent<IRoomEncryption>();
// if no change happened then skip rendering this, a shallow check is enough as all known fields are top-level.
if (!objectHasDiff(prevContent, content)) return null; // nop
if (content.algorithm === ALGORITHM && isRoomEncrypted) {
let subtitle: string; let subtitle: string;
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId); const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
if (dmPartner) { if (prevContent.algorithm === ALGORITHM) {
subtitle = _t("Some encryption parameters have been changed.");
} else if (dmPartner) {
const displayName = cli?.getRoom(roomId)?.getMember(dmPartner)?.rawDisplayName || dmPartner; const displayName = cli?.getRoom(roomId)?.getMember(dmPartner)?.rawDisplayName || dmPartner;
subtitle = _t("Messages here are end-to-end encrypted. " + subtitle = _t("Messages here are end-to-end encrypted. " +
"Verify %(displayName)s in their profile - tap on their avatar.", { displayName }); "Verify %(displayName)s in their profile - tap on their avatar.", { displayName });
@ -49,7 +61,9 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent }, ref) =>
title={_t("Encryption enabled")} title={_t("Encryption enabled")}
subtitle={subtitle} subtitle={subtitle}
/>; />;
} else if (isRoomEncrypted) { }
if (isRoomEncrypted) {
return <EventTileBubble return <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon" className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("Encryption enabled")} title={_t("Encryption enabled")}

View file

@ -17,8 +17,7 @@ limitations under the License.
*/ */
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import PropTypes from 'prop-types'; import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
import { EventStatus } from 'matrix-js-sdk/src/models/event';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -37,8 +36,19 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import DownloadActionButton from "./DownloadActionButton"; import DownloadActionButton from "./DownloadActionButton";
import SettingsStore from '../../../settings/SettingsStore'; import SettingsStore from '../../../settings/SettingsStore';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ReplyThread from '../elements/ReplyThread';
const OptionsButton = ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => { interface IOptionsButtonProps {
mxEvent: MatrixEvent;
getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
getReplyThread: () => ReplyThread;
permalinkCreator: RoomPermalinkCreator;
onFocusChange: (menuDisplayed: boolean) => void;
}
const OptionsButton: React.FC<IOptionsButtonProps> =
({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button); const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => { useEffect(() => {
@ -78,7 +88,13 @@ const OptionsButton = ({ mxEvent, getTile, getReplyThread, permalinkCreator, onF
</React.Fragment>; </React.Fragment>;
}; };
const ReactButton = ({ mxEvent, reactions, onFocusChange }) => { interface IReactButtonProps {
mxEvent: MatrixEvent;
reactions: any; // TODO: types
onFocusChange: (menuDisplayed: boolean) => void;
}
const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusChange }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button); const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => { useEffect(() => {
@ -109,21 +125,21 @@ const ReactButton = ({ mxEvent, reactions, onFocusChange }) => {
</React.Fragment>; </React.Fragment>;
}; };
@replaceableComponent("views.messages.MessageActionBar") interface IMessageActionBarProps {
export default class MessageActionBar extends React.PureComponent { mxEvent: MatrixEvent;
static propTypes = {
mxEvent: PropTypes.object.isRequired,
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions: PropTypes.object, reactions?: any; // TODO: types
permalinkCreator: PropTypes.object, permalinkCreator?: RoomPermalinkCreator;
getTile: PropTypes.func, getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
getReplyThread: PropTypes.func, getReplyThread?: () => ReplyThread;
onFocusChange: PropTypes.func, onFocusChange?: (menuDisplayed: boolean) => void;
}; }
static contextType = RoomContext; @replaceableComponent("views.messages.MessageActionBar")
export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {
public static contextType = RoomContext;
componentDidMount() { public componentDidMount(): void {
if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) { if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
this.props.mxEvent.on("Event.status", this.onSent); this.props.mxEvent.on("Event.status", this.onSent);
} }
@ -137,43 +153,43 @@ export default class MessageActionBar extends React.PureComponent {
this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction); this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction);
} }
componentWillUnmount() { public componentWillUnmount(): void {
this.props.mxEvent.off("Event.status", this.onSent); this.props.mxEvent.off("Event.status", this.onSent);
this.props.mxEvent.off("Event.decrypted", this.onDecrypted); this.props.mxEvent.off("Event.decrypted", this.onDecrypted);
this.props.mxEvent.off("Event.beforeRedaction", this.onBeforeRedaction); this.props.mxEvent.off("Event.beforeRedaction", this.onBeforeRedaction);
} }
onDecrypted = () => { private onDecrypted = (): void => {
// When an event decrypts, it is likely to change the set of available // When an event decrypts, it is likely to change the set of available
// actions, so we force an update to check again. // actions, so we force an update to check again.
this.forceUpdate(); this.forceUpdate();
}; };
onBeforeRedaction = () => { private onBeforeRedaction = (): void => {
// When an event is redacted, we can't edit it so update the available actions. // When an event is redacted, we can't edit it so update the available actions.
this.forceUpdate(); this.forceUpdate();
}; };
onSent = () => { private onSent = (): void => {
// When an event is sent and echoed the possible actions change. // When an event is sent and echoed the possible actions change.
this.forceUpdate(); this.forceUpdate();
}; };
onFocusChange = (focused) => { private onFocusChange = (focused: boolean): void => {
if (!this.props.onFocusChange) { if (!this.props.onFocusChange) {
return; return;
} }
this.props.onFocusChange(focused); this.props.onFocusChange(focused);
}; };
onReplyClick = (ev) => { private onReplyClick = (ev: React.MouseEvent): void => {
dis.dispatch({ dis.dispatch({
action: 'reply_to_event', action: 'reply_to_event',
event: this.props.mxEvent, event: this.props.mxEvent,
}); });
}; };
onThreadClick = () => { private onThreadClick = (): void => {
dis.dispatch({ dis.dispatch({
action: Action.SetRightPanelPhase, action: Action.SetRightPanelPhase,
phase: RightPanelPhases.ThreadView, phase: RightPanelPhases.ThreadView,
@ -182,9 +198,9 @@ export default class MessageActionBar extends React.PureComponent {
event: this.props.mxEvent, event: this.props.mxEvent,
}, },
}); });
} };
onEditClick = (ev) => { private onEditClick = (ev: React.MouseEvent): void => {
dis.dispatch({ dis.dispatch({
action: 'edit_event', action: 'edit_event',
event: this.props.mxEvent, event: this.props.mxEvent,
@ -200,7 +216,7 @@ export default class MessageActionBar extends React.PureComponent {
* @param {Function} fn The execution function. * @param {Function} fn The execution function.
* @param {Function} checkFn The test function. * @param {Function} checkFn The test function.
*/ */
runActionOnFailedEv(fn, checkFn) { private runActionOnFailedEv(fn: (ev: MatrixEvent) => void, checkFn?: (ev: MatrixEvent) => boolean): void {
if (!checkFn) checkFn = () => true; if (!checkFn) checkFn = () => true;
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
@ -215,18 +231,18 @@ export default class MessageActionBar extends React.PureComponent {
} }
} }
onResendClick = (ev) => { private onResendClick = (ev: React.MouseEvent): void => {
this.runActionOnFailedEv((tarEv) => Resend.resend(tarEv)); this.runActionOnFailedEv((tarEv) => Resend.resend(tarEv));
}; };
onCancelClick = (ev) => { private onCancelClick = (ev: React.MouseEvent): void => {
this.runActionOnFailedEv( this.runActionOnFailedEv(
(tarEv) => Resend.removeFromQueue(tarEv), (tarEv) => Resend.removeFromQueue(tarEv),
(testEv) => canCancel(testEv.status), (testEv) => canCancel(testEv.status),
); );
}; };
render() { public render(): JSX.Element {
const toolbarOpts = []; const toolbarOpts = [];
if (canEditContent(this.props.mxEvent)) { if (canEditContent(this.props.mxEvent)) {
toolbarOpts.push(<RovingAccessibleTooltipButton toolbarOpts.push(<RovingAccessibleTooltipButton
@ -249,7 +265,7 @@ export default class MessageActionBar extends React.PureComponent {
const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status; const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status;
const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status; const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status;
const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus); const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus);
const isFailed = [mxEvent.status, editStatus, redactStatus].includes("not_sent"); const isFailed = [mxEvent.status, editStatus, redactStatus].includes(EventStatus.NOT_SENT);
if (allowCancel && isFailed) { if (allowCancel && isFailed) {
// The resend button needs to appear ahead of the edit button, so insert to the // The resend button needs to appear ahead of the edit button, so insert to the
// start of the opts // start of the opts

View file

@ -57,7 +57,7 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
// state to show a spinner immediately after clicking "start verification", // state to show a spinner immediately after clicking "start verification",
// before we have a request // before we have a request
const [isRequesting, setRequesting] = useState(false); const [isRequesting, setRequesting] = useState(false);
const [phase, setPhase] = useState(request && request.phase); const [phase, setPhase] = useState(request?.phase);
useEffect(() => { useEffect(() => {
setRequest(verificationRequest); setRequest(verificationRequest);
if (verificationRequest) { if (verificationRequest) {

View file

@ -1278,7 +1278,9 @@ const BasicUserInfo: React.FC<{
// hide the Roles section for DMs as it doesn't make sense there // hide the Roles section for DMs as it doesn't make sense there
if (!DMRoomMap.shared().getUserIdForRoomId((member as RoomMember).roomId)) { if (!DMRoomMap.shared().getUserIdForRoomId((member as RoomMember).roomId)) {
memberDetails = <div className="mx_UserInfo_container"> memberDetails = <div className="mx_UserInfo_container">
<h3>{ _t("Role") }</h3> <h3>{ _t("Role in <RoomName/>", {}, {
RoomName: () => <b>{ room.name }</b>,
}) }</h3>
<PowerLevelSection <PowerLevelSection
powerLevels={powerLevels} powerLevels={powerLevels}
user={member as RoomMember} user={member as RoomMember}
@ -1573,11 +1575,12 @@ const UserInfo: React.FC<IProps> = ({
// We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time
if (room && phase === RightPanelPhases.EncryptionPanel) { if (room && phase === RightPanelPhases.EncryptionPanel) {
previousPhase = RightPanelPhases.RoomMemberInfo; previousPhase = RightPanelPhases.RoomMemberInfo;
refireParams = { member: member }; refireParams = { member };
} else if (room?.isSpaceRoom() && SpaceStore.spacesEnabled) {
previousPhase = previousPhase = RightPanelPhases.SpaceMemberList;
refireParams = { space: room };
} else if (room) { } else if (room) {
previousPhase = previousPhase = SpaceStore.spacesEnabled && room.isSpaceRoom() previousPhase = RightPanelPhases.RoomMemberList;
? RightPanelPhases.SpaceMemberList
: RightPanelPhases.RoomMemberList;
} }
const onEncryptionPanelClose = () => { const onEncryptionPanelClose = () => {

View file

@ -29,43 +29,27 @@ import VerificationQRCode from "../elements/crypto/VerificationQRCode";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import E2EIcon from "../rooms/E2EIcon"; import E2EIcon from "../rooms/E2EIcon";
import { import { Phase } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
PHASE_READY,
PHASE_DONE,
PHASE_STARTED,
PHASE_CANCELLED,
} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import Spinner from "../elements/Spinner"; import Spinner from "../elements/Spinner";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import VerificationShowSas from "../verification/VerificationShowSas"; import VerificationShowSas from "../verification/VerificationShowSas";
// XXX: Should be defined in matrix-js-sdk
enum VerificationPhase {
PHASE_UNSENT,
PHASE_REQUESTED,
PHASE_READY,
PHASE_DONE,
PHASE_STARTED,
PHASE_CANCELLED,
}
interface IProps { interface IProps {
layout: string; layout: string;
request: VerificationRequest; request: VerificationRequest;
member: RoomMember | User; member: RoomMember | User;
phase: VerificationPhase; phase: Phase;
onClose: () => void; onClose: () => void;
isRoomEncrypted: boolean; isRoomEncrypted: boolean;
inDialog: boolean; inDialog: boolean;
key: number;
} }
interface IState { interface IState {
sasEvent?: SAS; sasEvent?: SAS["sasEvent"];
emojiButtonClicked?: boolean; emojiButtonClicked?: boolean;
reciprocateButtonClicked?: boolean; reciprocateButtonClicked?: boolean;
reciprocateQREvent?: ReciprocateQRCode; reciprocateQREvent?: ReciprocateQRCode["reciprocateQREvent"];
} }
@replaceableComponent("views.right_panel.VerificationPanel") @replaceableComponent("views.right_panel.VerificationPanel")
@ -321,9 +305,9 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const displayName = (member as User).displayName || (member as RoomMember).name || member.userId; const displayName = (member as User).displayName || (member as RoomMember).name || member.userId;
switch (phase) { switch (phase) {
case PHASE_READY: case Phase.Ready:
return this.renderQRPhase(); return this.renderQRPhase();
case PHASE_STARTED: case Phase.Started:
switch (request.chosenMethod) { switch (request.chosenMethod) {
case verificationMethods.RECIPROCATE_QR_CODE: case verificationMethods.RECIPROCATE_QR_CODE:
return this.renderQRReciprocatePhase(); return this.renderQRReciprocatePhase();
@ -346,9 +330,9 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
default: default:
return null; return null;
} }
case PHASE_DONE: case Phase.Done:
return this.renderVerifiedPhase(); return this.renderVerifiedPhase();
case PHASE_CANCELLED: case Phase.Cancelled:
return this.renderCancelledPhase(); return this.renderCancelledPhase();
} }
console.error("VerificationPanel unhandled phase:", phase); console.error("VerificationPanel unhandled phase:", phase);
@ -375,7 +359,8 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
private updateVerifierState = () => { private updateVerifierState = () => {
const { request } = this.props; const { request } = this.props;
const { sasEvent, reciprocateQREvent } = request.verifier; const sasEvent = (request.verifier as SAS).sasEvent;
const reciprocateQREvent = (request.verifier as ReciprocateQRCode).reciprocateQREvent;
request.verifier.off('show_sas', this.updateVerifierState); request.verifier.off('show_sas', this.updateVerifierState);
request.verifier.off('show_reciprocate_qr', this.updateVerifierState); request.verifier.off('show_reciprocate_qr', this.updateVerifierState);
this.setState({ sasEvent, reciprocateQREvent }); this.setState({ sasEvent, reciprocateQREvent });
@ -402,7 +387,8 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const { request } = this.props; const { request } = this.props;
request.on("change", this.onRequestChange); request.on("change", this.onRequestChange);
if (request.verifier) { if (request.verifier) {
const { sasEvent, reciprocateQREvent } = request.verifier; const sasEvent = (request.verifier as SAS).sasEvent;
const reciprocateQREvent = (request.verifier as ReciprocateQRCode).reciprocateQREvent;
this.setState({ sasEvent, reciprocateQREvent }); this.setState({ sasEvent, reciprocateQREvent });
} }
this.onRequestChange(); this.onRequestChange();

View file

@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, { createRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -27,7 +27,12 @@ import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalin
import ContentMessages from '../../../ContentMessages'; import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon'; import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; import {
aboveLeftOf,
ContextMenu,
useContextMenu,
MenuItem,
} from "../../structures/ContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ReplyPreview from "./ReplyPreview"; import ReplyPreview from "./ReplyPreview";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
@ -45,6 +50,10 @@ import { Action } from "../../../dispatcher/actions";
import EditorModel from "../../../editor/model"; import EditorModel from "../../../editor/model";
import EmojiPicker from '../emojipicker/EmojiPicker'; import EmojiPicker from '../emojipicker/EmojiPicker';
import MemberStatusMessageAvatar from "../avatars/MemberStatusMessageAvatar"; import MemberStatusMessageAvatar from "../avatars/MemberStatusMessageAvatar";
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
let instanceCount = 0;
const NARROW_MODE_BREAKPOINT = 500;
interface IComposerAvatarProps { interface IComposerAvatarProps {
me: object; me: object;
@ -71,13 +80,19 @@ function SendButton(props: ISendButtonProps) {
); );
} }
const EmojiButton = ({ addEmoji }) => { interface IEmojiButtonProps {
addEmoji: (unicode: string) => boolean;
menuPosition: any; // TODO: Types
narrowMode: boolean;
}
const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narrowMode }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
let contextMenu; let contextMenu;
if (menuDisplayed) { if (menuDisplayed) {
const buttonRect = button.current.getBoundingClientRect(); const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}> contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} /> <EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
</ContextMenu>; </ContextMenu>;
} }
@ -93,12 +108,11 @@ const EmojiButton = ({ addEmoji }) => {
// TODO: replace ContextMenuTooltipButton with a unified representation of // TODO: replace ContextMenuTooltipButton with a unified representation of
// the header buttons and the right panel buttons // the header buttons and the right panel buttons
return <React.Fragment> return <React.Fragment>
<ContextMenuTooltipButton <AccessibleTooltipButton
className={className} className={className}
onClick={openMenu} onClick={openMenu}
isExpanded={menuDisplayed} title={!narrowMode && _t('Emoji picker')}
title={_t('Emoji picker')} label={narrowMode && _t("Add emoji")}
inputRef={button}
/> />
{ contextMenu } { contextMenu }
@ -196,6 +210,9 @@ interface IState {
haveRecording: boolean; haveRecording: boolean;
recordingTimeLeftSeconds?: number; recordingTimeLeftSeconds?: number;
me?: RoomMember; me?: RoomMember;
narrowMode?: boolean;
isMenuOpen: boolean;
showStickers: boolean;
} }
@replaceableComponent("views.rooms.MessageComposer") @replaceableComponent("views.rooms.MessageComposer")
@ -203,6 +220,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
private dispatcherRef: string; private dispatcherRef: string;
private messageComposerInput: SendMessageComposer; private messageComposerInput: SendMessageComposer;
private voiceRecordingButton: VoiceRecordComposerTile; private voiceRecordingButton: VoiceRecordComposerTile;
private ref: React.RefObject<HTMLDivElement> = createRef();
private instanceId: number;
static defaultProps = { static defaultProps = {
replyInThread: false, replyInThread: false,
@ -220,15 +239,32 @@ export default class MessageComposer extends React.Component<IProps, IState> {
isComposerEmpty: true, isComposerEmpty: true,
haveRecording: false, haveRecording: false,
recordingTimeLeftSeconds: null, // when set to a number, shows a toast recordingTimeLeftSeconds: null, // when set to a number, shows a toast
isMenuOpen: false,
showStickers: false,
}; };
this.instanceId = instanceCount++;
} }
componentDidMount() { componentDidMount() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
this.waitForOwnMember(); this.waitForOwnMember();
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
} }
private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry) => {
if (type === UI_EVENTS.Resize) {
const narrowMode = entry.contentRect.width <= NARROW_MODE_BREAKPOINT;
this.setState({
narrowMode,
isMenuOpen: !narrowMode ? false : this.state.isMenuOpen,
showStickers: false,
});
}
};
private onAction = (payload: ActionPayload) => { private onAction = (payload: ActionPayload) => {
if (payload.action === 'reply_to_event') { if (payload.action === 'reply_to_event') {
// add a timeout for the reply preview to be rendered, so // add a timeout for the reply preview to be rendered, so
@ -263,6 +299,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
} }
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate); VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize);
} }
private onRoomStateEvents = (ev, state) => { private onRoomStateEvents = (ev, state) => {
@ -312,7 +350,11 @@ export default class MessageComposer extends React.Component<IProps, IState> {
private renderPlaceholderText = () => { private renderPlaceholderText = () => {
if (this.props.replyToEvent) { if (this.props.replyToEvent) {
if (this.props.e2eStatus) { if (this.props.replyInThread && this.props.e2eStatus) {
return _t('Reply to encrypted thread…');
} else if (this.props.replyInThread) {
return _t('Reply to thread…');
} else if (this.props.e2eStatus) {
return _t('Send an encrypted reply…'); return _t('Send an encrypted reply…');
} else { } else {
return _t('Send a reply…'); return _t('Send a reply…');
@ -326,11 +368,12 @@ export default class MessageComposer extends React.Component<IProps, IState> {
} }
}; };
private addEmoji(emoji: string) { private addEmoji(emoji: string): boolean {
dis.dispatch<ComposerInsertPayload>({ dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert, action: Action.ComposerInsert,
text: emoji, text: emoji,
}); });
return true;
} }
private sendMessage = async () => { private sendMessage = async () => {
@ -369,6 +412,97 @@ export default class MessageComposer extends React.Component<IProps, IState> {
} }
}; };
private shouldShowStickerPicker = (): boolean => {
return SettingsStore.getValue(UIFeature.Widgets)
&& SettingsStore.getValue("MessageComposerInput.showStickersButton")
&& !this.state.haveRecording;
};
private showStickers = (showStickers: boolean) => {
this.setState({ showStickers });
};
private toggleButtonMenu = (): void => {
this.setState({
isMenuOpen: !this.state.isMenuOpen,
});
};
private renderButtons(menuPosition): JSX.Element | JSX.Element[] {
const buttons: JSX.Element[] = [];
if (!this.state.haveRecording) {
buttons.push(
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
);
buttons.push(
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />,
);
}
if (this.shouldShowStickerPicker()) {
let title;
if (!this.state.narrowMode) {
title = this.state.showStickers ? _t("Hide Stickers") : _t("Show Stickers");
}
buttons.push(
<AccessibleTooltipButton
id='stickersButton'
key="controls_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={() => this.showStickers(!this.state.showStickers)}
title={title}
label={this.state.narrowMode && _t("Send a sticker")}
/>,
);
}
if (!this.state.haveRecording && !this.state.narrowMode) {
buttons.push(
<AccessibleTooltipButton
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
onClick={() => this.voiceRecordingButton?.onRecordStartEndClick()}
title={_t("Send voice message")}
/>,
);
}
if (!this.state.narrowMode) {
return buttons;
} else {
const classnames = classNames({
mx_MessageComposer_button: true,
mx_MessageComposer_buttonMenu: true,
mx_MessageComposer_closeButtonMenu: this.state.isMenuOpen,
});
return <>
{ buttons[0] }
<AccessibleTooltipButton
className={classnames}
onClick={this.toggleButtonMenu}
title={_t("More options")}
tooltip={false}
/>
{ this.state.isMenuOpen && (
<ContextMenu
onFinished={this.toggleButtonMenu}
{...menuPosition}
menuPaddingRight={10}
menuPaddingTop={5}
menuPaddingBottom={5}
menuWidth={150}
wrapperClassName="mx_MessageComposer_Menu"
>
{ buttons.slice(1).map((button, index) => (
<MenuItem className="mx_CallContextMenu_item" key={index} onClick={this.toggleButtonMenu}>
{ button }
</MenuItem>
)) }
</ContextMenu>
) }
</>;
}
}
render() { render() {
const controls = [ const controls = [
this.state.me && !this.props.compact ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null, this.state.me && !this.props.compact ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
@ -377,6 +511,12 @@ export default class MessageComposer extends React.Component<IProps, IState> {
null, null,
]; ];
let menuPosition;
if (this.ref.current) {
const contentRect = this.ref.current.getBoundingClientRect();
menuPosition = aboveLeftOf(contentRect);
}
if (!this.state.tombstone && this.state.canSendMessages) { if (!this.state.tombstone && this.state.canSendMessages) {
controls.push( controls.push(
<SendMessageComposer <SendMessageComposer
@ -392,33 +532,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
/>, />,
); );
if (!this.state.haveRecording) {
controls.push(
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
);
}
if (SettingsStore.getValue(UIFeature.Widgets) &&
SettingsStore.getValue("MessageComposerInput.showStickersButton") &&
!this.state.haveRecording) {
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
}
controls.push(<VoiceRecordComposerTile controls.push(<VoiceRecordComposerTile
key="controls_voice_record" key="controls_voice_record"
ref={c => this.voiceRecordingButton = c} ref={c => this.voiceRecordingButton = c}
room={this.props.room} />); room={this.props.room} />);
if (!this.state.isComposerEmpty || this.state.haveRecording) {
controls.push(
<SendButton
key="controls_send"
onClick={this.sendMessage}
title={this.state.haveRecording ? _t("Send voice message") : undefined}
/>,
);
}
} else if (this.state.tombstone) { } else if (this.state.tombstone) {
const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
@ -459,6 +576,15 @@ export default class MessageComposer extends React.Component<IProps, IState> {
yOffset={-50} yOffset={-50}
/>; />;
} }
controls.push(
<Stickerpicker
room={this.props.room}
showStickers={this.state.showStickers}
setShowStickers={this.showStickers}
menuPosition={menuPosition} />,
);
const showSendButton = !this.state.isComposerEmpty || this.state.haveRecording;
const classes = classNames({ const classes = classNames({
"mx_MessageComposer": true, "mx_MessageComposer": true,
@ -467,7 +593,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}); });
return ( return (
<div className={classes}> <div className={classes} ref={this.ref}>
{ recordingTooltip } { recordingTooltip }
<div className="mx_MessageComposer_wrapper"> <div className="mx_MessageComposer_wrapper">
{ this.props.showReplyPreview && ( { this.props.showReplyPreview && (
@ -475,6 +601,14 @@ export default class MessageComposer extends React.Component<IProps, IState> {
) } ) }
<div className="mx_MessageComposer_row"> <div className="mx_MessageComposer_row">
{ controls } { controls }
{ this.renderButtons(menuPosition) }
{ showSendButton && (
<SendButton
key="controls_send"
onClick={this.sendMessage}
title={this.state.haveRecording ? _t("Send voice message") : undefined}
/>
) }
</div> </div>
</div> </div>
</div> </div>

View file

@ -48,6 +48,7 @@ import SpaceStore, { ISuggestedRoom, SUGGESTED_ROOMS } from "../../../stores/Spa
import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
interface IProps { interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void; onKeyDown: (ev: React.KeyboardEvent) => void;
@ -522,20 +523,23 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
} else if ( } else if (
this.props.activeSpace?.canInvite(userId) || this.props.activeSpace?.getMyMembership() === "join" this.props.activeSpace?.canInvite(userId) || this.props.activeSpace?.getMyMembership() === "join"
) { ) {
const spaceName = this.props.activeSpace.name;
explorePrompt = <div className="mx_RoomList_explorePrompt"> explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{ _t("Quick actions") }</div> <div>{ _t("Quick actions") }</div>
{ this.props.activeSpace.canInvite(userId) && <AccessibleButton { this.props.activeSpace.canInvite(userId) && <AccessibleTooltipButton
className="mx_RoomList_explorePrompt_spaceInvite" className="mx_RoomList_explorePrompt_spaceInvite"
onClick={this.onSpaceInviteClick} onClick={this.onSpaceInviteClick}
title={_t("Invite to %(spaceName)s", { spaceName })}
> >
{ _t("Invite people") } { _t("Invite people") }
</AccessibleButton> } </AccessibleTooltipButton> }
{ this.props.activeSpace.getMyMembership() === "join" && <AccessibleButton { this.props.activeSpace.getMyMembership() === "join" && <AccessibleTooltipButton
className="mx_RoomList_explorePrompt_spaceExplore" className="mx_RoomList_explorePrompt_spaceExplore"
onClick={this.onExplore} onClick={this.onExplore}
title={_t("Explore %(spaceName)s", { spaceName })}
> >
{ _t("Explore rooms") } { _t("Explore rooms") }
</AccessibleButton> } </AccessibleTooltipButton> }
</div>; </div>;
} else if (Object.values(this.state.sublists).some(list => list.length > 0)) { } else if (Object.values(this.state.sublists).some(list => list.length > 0)) {
const unfilteredLists = RoomListStore.instance.unfilteredLists; const unfilteredLists = RoomListStore.instance.unfilteredLists;

View file

@ -54,6 +54,7 @@ import { Room } from 'matrix-js-sdk/src/models/room';
import ErrorDialog from "../dialogs/ErrorDialog"; import ErrorDialog from "../dialogs/ErrorDialog";
import QuestionDialog from "../dialogs/QuestionDialog"; import QuestionDialog from "../dialogs/QuestionDialog";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
import { decorateStartSendingTime, sendRoundTripMetric } from "../../../sendTimePerformanceMetrics";
function addReplyToMessageContent( function addReplyToMessageContent(
content: IContent, content: IContent,
@ -418,6 +419,10 @@ export default class SendMessageComposer extends React.Component<IProps> {
// don't bother sending an empty message // don't bother sending an empty message
if (!content.body.trim()) return; if (!content.body.trim()) return;
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
decorateStartSendingTime(content);
}
const prom = this.context.sendMessage(roomId, content); const prom = this.context.sendMessage(roomId, content);
if (replyToEvent) { if (replyToEvent) {
// Clear reply_to_event as we put the message into the queue // Clear reply_to_event as we put the message into the queue
@ -433,6 +438,11 @@ export default class SendMessageComposer extends React.Component<IProps> {
dis.dispatch({ action: `effects.${effect.command}` }); dis.dispatch({ action: `effects.${effect.command}` });
} }
}); });
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
prom.then(resp => {
sendRoundTripMetric(this.context, roomId, resp.event_id);
});
}
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content); CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
} }

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import AppTile from '../elements/AppTile'; import AppTile from '../elements/AppTile';
@ -27,7 +26,6 @@ import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu"; import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
import { WidgetType } from "../../../widgets/WidgetType"; import { WidgetType } from "../../../widgets/WidgetType";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -44,10 +42,12 @@ const PERSISTED_ELEMENT_KEY = "stickerPicker";
interface IProps { interface IProps {
room: Room; room: Room;
showStickers: boolean;
menuPosition?: any;
setShowStickers: (showStickers: boolean) => void;
} }
interface IState { interface IState {
showStickers: boolean;
imError: string; imError: string;
stickerpickerX: number; stickerpickerX: number;
stickerpickerY: number; stickerpickerY: number;
@ -72,7 +72,6 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {
showStickers: false,
imError: null, imError: null,
stickerpickerX: null, stickerpickerX: null,
stickerpickerY: null, stickerpickerY: null,
@ -114,7 +113,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
console.warn('No widget ID specified, not disabling assets'); console.warn('No widget ID specified, not disabling assets');
} }
this.setState({ showStickers: false }); this.props.setShowStickers(false);
WidgetUtils.removeStickerpickerWidgets().then(() => { WidgetUtils.removeStickerpickerWidgets().then(() => {
this.forceUpdate(); this.forceUpdate();
}).catch((e) => { }).catch((e) => {
@ -146,15 +145,15 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
} }
public componentDidUpdate(prevProps: IProps, prevState: IState): void { public componentDidUpdate(prevProps: IProps, prevState: IState): void {
this.sendVisibilityToWidget(this.state.showStickers); this.sendVisibilityToWidget(this.props.showStickers);
} }
private imError(errorMsg: string, e: Error): void { private imError(errorMsg: string, e: Error): void {
console.error(errorMsg, e); console.error(errorMsg, e);
this.setState({ this.setState({
showStickers: false,
imError: _t(errorMsg), imError: _t(errorMsg),
}); });
this.props.setShowStickers(false);
} }
private updateWidget = (): void => { private updateWidget = (): void => {
@ -194,12 +193,12 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
this.forceUpdate(); this.forceUpdate();
break; break;
case "stickerpicker_close": case "stickerpicker_close":
this.setState({ showStickers: false }); this.props.setShowStickers(false);
break; break;
case Action.AfterRightPanelPhaseChange: case Action.AfterRightPanelPhaseChange:
case "show_left_panel": case "show_left_panel":
case "hide_left_panel": case "hide_left_panel":
this.setState({ showStickers: false }); this.props.setShowStickers(false);
break; break;
} }
}; };
@ -338,8 +337,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
this.props.setShowStickers(true);
this.setState({ this.setState({
showStickers: true,
stickerpickerX: x, stickerpickerX: x,
stickerpickerY: y, stickerpickerY: y,
stickerpickerChevronOffset, stickerpickerChevronOffset,
@ -351,8 +350,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
* @param {Event} ev Event that triggered the function call * @param {Event} ev Event that triggered the function call
*/ */
private onHideStickersClick = (ev: React.MouseEvent): void => { private onHideStickersClick = (ev: React.MouseEvent): void => {
if (this.state.showStickers) { if (this.props.showStickers) {
this.setState({ showStickers: false }); this.props.setShowStickers(false);
} }
}; };
@ -360,8 +359,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
* Called when the window is resized * Called when the window is resized
*/ */
private onResize = (): void => { private onResize = (): void => {
if (this.state.showStickers) { if (this.props.showStickers) {
this.setState({ showStickers: false }); this.props.setShowStickers(false);
} }
}; };
@ -369,8 +368,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
* The stickers picker was hidden * The stickers picker was hidden
*/ */
private onFinished = (): void => { private onFinished = (): void => {
if (this.state.showStickers) { if (this.props.showStickers) {
this.setState({ showStickers: false }); this.props.setShowStickers(false);
} }
}; };
@ -395,26 +394,9 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
}; };
public render(): JSX.Element { public render(): JSX.Element {
let stickerPicker; if (!this.props.showStickers) return null;
let stickersButton;
const className = classNames(
"mx_MessageComposer_button",
"mx_MessageComposer_stickers",
"mx_Stickers_hideStickers",
"mx_MessageComposer_button_highlight",
);
if (this.state.showStickers) {
// Show hide-stickers button
stickersButton =
<AccessibleButton
id='stickersButton'
key="controls_hide_stickers"
className={className}
onClick={this.onHideStickersClick}
title={_t("Hide Stickers")}
/>;
stickerPicker = <ContextMenu return <ContextMenu
chevronOffset={this.state.stickerpickerChevronOffset} chevronOffset={this.state.stickerpickerChevronOffset}
chevronFace={ChevronFace.Bottom} chevronFace={ChevronFace.Bottom}
left={this.state.stickerpickerX} left={this.state.stickerpickerX}
@ -426,23 +408,9 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
menuPaddingLeft={0} menuPaddingLeft={0}
menuPaddingRight={0} menuPaddingRight={0}
zIndex={STICKERPICKER_Z_INDEX} zIndex={STICKERPICKER_Z_INDEX}
{...this.props.menuPosition}
> >
<GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} /> <GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
</ContextMenu>; </ContextMenu>;
} else {
// Show show-stickers button
stickersButton =
<AccessibleTooltipButton
id='stickersButton'
key="controls_show_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={this.onShowStickersClick}
title={_t("Show Stickers")}
/>;
}
return <React.Fragment>
{ stickersButton }
{ stickerPicker }
</React.Fragment>;
} }
} }

View file

@ -20,7 +20,6 @@ import React, { ReactNode } from "react";
import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording"; import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import classNames from "classnames";
import LiveRecordingWaveform from "../audio_messages/LiveRecordingWaveform"; import LiveRecordingWaveform from "../audio_messages/LiveRecordingWaveform";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import LiveRecordingClock from "../audio_messages/LiveRecordingClock"; import LiveRecordingClock from "../audio_messages/LiveRecordingClock";
@ -137,7 +136,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
await this.disposeRecording(); await this.disposeRecording();
}; };
private onRecordStartEndClick = async () => { public onRecordStartEndClick = async () => {
if (this.state.recorder) { if (this.state.recorder) {
await this.state.recorder.stop(); await this.state.recorder.stop();
return; return;
@ -215,27 +214,23 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
} }
public render(): ReactNode { public render(): ReactNode {
let stopOrRecordBtn; if (!this.state.recordingPhase) return null;
let deleteButton;
if (!this.state.recordingPhase || this.state.recordingPhase === RecordingState.Started) {
const classes = classNames({
'mx_MessageComposer_button': !this.state.recorder,
'mx_MessageComposer_voiceMessage': !this.state.recorder,
'mx_VoiceRecordComposerTile_stop': this.state.recorder?.isRecording,
});
let stopBtn;
let deleteButton;
if (this.state.recordingPhase === RecordingState.Started) {
let tooltip = _t("Send voice message"); let tooltip = _t("Send voice message");
if (!!this.state.recorder) { if (!!this.state.recorder) {
tooltip = _t("Stop recording"); tooltip = _t("Stop recording");
} }
stopOrRecordBtn = <AccessibleTooltipButton stopBtn = <AccessibleTooltipButton
className={classes} className="mx_VoiceRecordComposerTile_stop"
onClick={this.onRecordStartEndClick} onClick={this.onRecordStartEndClick}
title={tooltip} title={tooltip}
/>; />;
if (this.state.recorder && !this.state.recorder?.isRecording) { if (this.state.recorder && !this.state.recorder?.isRecording) {
stopOrRecordBtn = null; stopBtn = null;
} }
} }
@ -264,13 +259,10 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
</span>; </span>;
} }
// The record button (mic icon) is meant to be on the right edge, but we also want the
// stop button to be left of the waveform area. Luckily, none of the surrounding UI is
// rendered when we're not recording, so the record button ends up in the correct spot.
return (<> return (<>
{ uploadIndicator } { uploadIndicator }
{ deleteButton } { deleteButton }
{ stopOrRecordBtn } { stopBtn }
{ this.renderWaveformArea() } { this.renderWaveformArea() }
</>); </>);
} }

View file

@ -0,0 +1,269 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { IJoinRuleEventContent, JoinRule, RestrictedAllowType } from "matrix-js-sdk/src/@types/partials";
import { Room } from "matrix-js-sdk/src/models/room";
import { EventType } from "matrix-js-sdk/src/@types/event";
import StyledRadioGroup, { IDefinition } from "../elements/StyledRadioGroup";
import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import Modal from "../../../Modal";
import ManageRestrictedJoinRuleDialog from "../dialogs/ManageRestrictedJoinRuleDialog";
import RoomUpgradeWarningDialog from "../dialogs/RoomUpgradeWarningDialog";
import { upgradeRoom } from "../../../utils/RoomUpgrade";
import { arrayHasDiff } from "../../../utils/arrays";
import { useLocalEcho } from "../../../hooks/useLocalEcho";
import dis from "../../../dispatcher/dispatcher";
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
interface IProps {
room: Room;
promptUpgrade?: boolean;
closeSettingsFn(): void;
onError(error: Error): void;
beforeChange?(joinRule: JoinRule): Promise<boolean>; // if returns false then aborts the change
}
const JoinRuleSettings = ({ room, promptUpgrade, onError, beforeChange, closeSettingsFn }: IProps) => {
const cli = room.client;
const restrictedRoomCapabilities = SpaceStore.instance.restrictedJoinRuleSupport;
const roomSupportsRestricted = Array.isArray(restrictedRoomCapabilities?.support)
&& restrictedRoomCapabilities.support.includes(room.getVersion());
const preferredRestrictionVersion = !roomSupportsRestricted && promptUpgrade
? restrictedRoomCapabilities?.preferred
: undefined;
const disabled = !room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, cli);
const [content, setContent] = useLocalEcho<IJoinRuleEventContent>(
() => room.currentState.getStateEvents(EventType.RoomJoinRules, "")?.getContent(),
content => cli.sendStateEvent(room.roomId, EventType.RoomJoinRules, content, ""),
onError,
);
const { join_rule: joinRule } = content;
const restrictedAllowRoomIds = joinRule === JoinRule.Restricted
? content.allow.filter(o => o.type === RestrictedAllowType.RoomMembership).map(o => o.room_id)
: undefined;
const editRestrictedRoomIds = async (): Promise<string[] | undefined> => {
let selected = restrictedAllowRoomIds;
if (!selected?.length && SpaceStore.instance.activeSpace) {
selected = [SpaceStore.instance.activeSpace.roomId];
}
const matrixClient = MatrixClientPeg.get();
const { finished } = Modal.createTrackedDialog('Edit restricted', '', ManageRestrictedJoinRuleDialog, {
matrixClient,
room,
selected,
}, "mx_ManageRestrictedJoinRuleDialog_wrapper");
const [roomIds] = await finished;
return roomIds;
};
const definitions: IDefinition<JoinRule>[] = [{
value: JoinRule.Invite,
label: _t("Private (invite only)"),
description: _t("Only invited people can join."),
checked: joinRule === JoinRule.Invite || (joinRule === JoinRule.Restricted && !restrictedAllowRoomIds?.length),
}, {
value: JoinRule.Public,
label: _t("Public"),
description: _t("Anyone can find and join."),
}];
if (roomSupportsRestricted || preferredRestrictionVersion || joinRule === JoinRule.Restricted) {
let upgradeRequiredPill;
if (preferredRestrictionVersion) {
upgradeRequiredPill = <span className="mx_SecurityRoomSettingsTab_upgradeRequired">
{ _t("Upgrade required") }
</span>;
}
let description;
if (joinRule === JoinRule.Restricted && restrictedAllowRoomIds?.length) {
// only show the first 4 spaces we know about, so that the UI doesn't grow out of proportion there are lots.
const shownSpaces = restrictedAllowRoomIds
.map(roomId => cli.getRoom(roomId))
.filter(room => room?.isSpaceRoom())
.slice(0, 4);
let moreText;
if (shownSpaces.length < restrictedAllowRoomIds.length) {
if (shownSpaces.length > 0) {
moreText = _t("& %(count)s more", {
count: restrictedAllowRoomIds.length - shownSpaces.length,
});
} else {
moreText = _t("Currently, %(count)s spaces have access", {
count: restrictedAllowRoomIds.length,
});
}
}
const onRestrictedRoomIdsChange = (newAllowRoomIds: string[]) => {
if (!arrayHasDiff(restrictedAllowRoomIds || [], newAllowRoomIds)) return;
if (!newAllowRoomIds.length) {
setContent({
join_rule: JoinRule.Invite,
});
return;
}
setContent({
join_rule: JoinRule.Restricted,
allow: newAllowRoomIds.map(roomId => ({
"type": RestrictedAllowType.RoomMembership,
"room_id": roomId,
})),
});
};
const onEditRestrictedClick = async () => {
const restrictedAllowRoomIds = await editRestrictedRoomIds();
if (!Array.isArray(restrictedAllowRoomIds)) return;
if (restrictedAllowRoomIds.length > 0) {
onRestrictedRoomIdsChange(restrictedAllowRoomIds);
} else {
onChange(JoinRule.Invite);
}
};
description = <div>
<span>
{ _t("Anyone in a space can find and join. <a>Edit which spaces can access here.</a>", {}, {
a: sub => <AccessibleButton
disabled={disabled}
onClick={onEditRestrictedClick}
kind="link"
>
{ sub }
</AccessibleButton>,
}) }
</span>
<div className="mx_SecurityRoomSettingsTab_spacesWithAccess">
<h4>{ _t("Spaces with access") }</h4>
{ shownSpaces.map(room => {
return <span key={room.roomId}>
<RoomAvatar room={room} height={32} width={32} />
{ room.name }
</span>;
}) }
{ moreText && <span>{ moreText }</span> }
</div>
</div>;
} else if (SpaceStore.instance.activeSpace) {
description = _t("Anyone in <spaceName/> can find and join. You can select other spaces too.", {}, {
spaceName: () => <b>{ SpaceStore.instance.activeSpace.name }</b>,
});
} else {
description = _t("Anyone in a space can find and join. You can select multiple spaces.");
}
definitions.splice(1, 0, {
value: JoinRule.Restricted,
label: <>
{ _t("Space members") }
{ upgradeRequiredPill }
</>,
description,
// if there are 0 allowed spaces then render it as invite only instead
checked: joinRule === JoinRule.Restricted && !!restrictedAllowRoomIds?.length,
});
}
const onChange = async (joinRule: JoinRule) => {
const beforeJoinRule = content.join_rule;
let restrictedAllowRoomIds: string[];
if (joinRule === JoinRule.Restricted) {
if (beforeJoinRule === JoinRule.Restricted || roomSupportsRestricted) {
// Have the user pick which spaces to allow joins from
restrictedAllowRoomIds = await editRestrictedRoomIds();
if (!Array.isArray(restrictedAllowRoomIds)) return;
} else if (preferredRestrictionVersion) {
// Block this action on a room upgrade otherwise it'd make their room unjoinable
const targetVersion = preferredRestrictionVersion;
Modal.createTrackedDialog('Restricted join rule upgrade', '', RoomUpgradeWarningDialog, {
roomId: room.roomId,
targetVersion,
description: _t("This upgrade will allow members of selected spaces " +
"access to this room without an invite."),
onFinished: async (resp) => {
if (!resp?.continue) return;
const roomId = await upgradeRoom(room, targetVersion, resp.invite, true, true, true);
closeSettingsFn();
// switch to the new room in the background
dis.dispatch({
action: "view_room",
room_id: roomId,
});
// open new settings on this tab
dis.dispatch({
action: "open_room_settings",
initial_tab_id: ROOM_SECURITY_TAB,
});
},
});
return;
}
// when setting to 0 allowed rooms/spaces set to invite only instead as per the note
if (!restrictedAllowRoomIds.length) {
joinRule = JoinRule.Invite;
}
}
if (beforeJoinRule === joinRule && !restrictedAllowRoomIds) return;
if (beforeChange && !await beforeChange(joinRule)) return;
const newContent: IJoinRuleEventContent = {
join_rule: joinRule,
};
// pre-set the accepted spaces with the currently viewed one as per the microcopy
if (joinRule === JoinRule.Restricted) {
newContent.allow = restrictedAllowRoomIds.map(roomId => ({
"type": RestrictedAllowType.RoomMembership,
"room_id": roomId,
}));
}
setContent(newContent);
};
return (
<StyledRadioGroup
name="joinRule"
value={joinRule}
onChange={onChange}
definitions={definitions}
disabled={disabled}
/>
);
};
export default JoinRuleSettings;

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "matrix-js-sdk/src/@types/partials"; import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "matrix-js-sdk/src/@types/partials";
import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from 'matrix-js-sdk/src/@types/event'; import { EventType } from 'matrix-js-sdk/src/@types/event';
import { _t } from "../../../../../languageHandler"; import { _t } from "../../../../../languageHandler";
@ -24,35 +24,29 @@ import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import Modal from "../../../../../Modal"; import Modal from "../../../../../Modal";
import QuestionDialog from "../../../dialogs/QuestionDialog"; import QuestionDialog from "../../../dialogs/QuestionDialog";
import StyledRadioGroup, { IDefinition } from '../../../elements/StyledRadioGroup'; import StyledRadioGroup from '../../../elements/StyledRadioGroup';
import { SettingLevel } from "../../../../../settings/SettingLevel"; import { SettingLevel } from "../../../../../settings/SettingLevel";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import { UIFeature } from "../../../../../settings/UIFeature"; import { UIFeature } from "../../../../../settings/UIFeature";
import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
import SpaceStore from "../../../../../stores/SpaceStore";
import RoomAvatar from "../../../avatars/RoomAvatar";
import ManageRestrictedJoinRuleDialog from '../../../dialogs/ManageRestrictedJoinRuleDialog';
import RoomUpgradeWarningDialog from '../../../dialogs/RoomUpgradeWarningDialog';
import { upgradeRoom } from "../../../../../utils/RoomUpgrade";
import { arrayHasDiff } from "../../../../../utils/arrays";
import SettingsFlag from '../../../elements/SettingsFlag'; import SettingsFlag from '../../../elements/SettingsFlag';
import createRoom, { IOpts } from '../../../../../createRoom'; import createRoom, { IOpts } from '../../../../../createRoom';
import CreateRoomDialog from '../../../dialogs/CreateRoomDialog'; import CreateRoomDialog from '../../../dialogs/CreateRoomDialog';
import JoinRuleSettings from "../../JoinRuleSettings";
import ErrorDialog from "../../../dialogs/ErrorDialog";
interface IProps { interface IProps {
roomId: string; roomId: string;
closeSettingsFn: () => void;
} }
interface IState { interface IState {
joinRule: JoinRule;
restrictedAllowRoomIds?: string[]; restrictedAllowRoomIds?: string[];
guestAccess: GuestAccess; guestAccess: GuestAccess;
history: HistoryVisibility; history: HistoryVisibility;
hasAliases: boolean; hasAliases: boolean;
encrypted: boolean; encrypted: boolean;
roomSupportsRestricted?: boolean;
preferredRestrictionVersion?: string;
showAdvancedSection: boolean; showAdvancedSection: boolean;
} }
@ -62,7 +56,6 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
super(props); super(props);
this.state = { this.state = {
joinRule: JoinRule.Invite,
guestAccess: GuestAccess.Forbidden, guestAccess: GuestAccess.Forbidden,
history: HistoryVisibility.Shared, history: HistoryVisibility.Shared,
hasAliases: false, hasAliases: false,
@ -103,12 +96,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
); );
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId); const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
const restrictedRoomCapabilities = SpaceStore.instance.restrictedJoinRuleSupport; this.setState({ restrictedAllowRoomIds, guestAccess, history, encrypted });
const roomSupportsRestricted = Array.isArray(restrictedRoomCapabilities?.support)
&& restrictedRoomCapabilities.support.includes(room.getVersion());
const preferredRestrictionVersion = roomSupportsRestricted ? undefined : restrictedRoomCapabilities?.preferred;
this.setState({ joinRule, restrictedAllowRoomIds, guestAccess, history, encrypted,
roomSupportsRestricted, preferredRestrictionVersion });
this.hasAliases().then(hasAliases => this.setState({ hasAliases })); this.hasAliases().then(hasAliases => this.setState({ hasAliases }));
} }
@ -132,7 +120,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
}; };
private onEncryptionChange = async () => { private onEncryptionChange = async () => {
if (this.state.joinRule == "public") { if (MatrixClientPeg.get().getRoom(this.props.roomId)?.getJoinRule() === JoinRule.Public) {
const dialog = Modal.createTrackedDialog('Confirm Public Encrypted Room', '', QuestionDialog, { const dialog = Modal.createTrackedDialog('Confirm Public Encrypted Room', '', QuestionDialog, {
title: _t('Are you sure you want to add encryption to this public room?'), title: _t('Are you sure you want to add encryption to this public room?'),
description: <div> description: <div>
@ -199,117 +187,6 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
}); });
}; };
private onJoinRuleChange = async (joinRule: JoinRule) => {
const beforeJoinRule = this.state.joinRule;
let restrictedAllowRoomIds: string[];
if (joinRule === JoinRule.Restricted) {
const matrixClient = MatrixClientPeg.get();
const roomId = this.props.roomId;
const room = matrixClient.getRoom(roomId);
if (beforeJoinRule === JoinRule.Restricted || this.state.roomSupportsRestricted) {
// Have the user pick which spaces to allow joins from
restrictedAllowRoomIds = await this.editRestrictedRoomIds();
if (!Array.isArray(restrictedAllowRoomIds)) return;
} else if (this.state.preferredRestrictionVersion) {
// Block this action on a room upgrade otherwise it'd make their room unjoinable
const targetVersion = this.state.preferredRestrictionVersion;
Modal.createTrackedDialog('Restricted join rule upgrade', '', RoomUpgradeWarningDialog, {
roomId,
targetVersion,
description: _t("This upgrade will allow members of selected spaces " +
"access to this room without an invite."),
onFinished: (resp) => {
if (!resp?.continue) return;
upgradeRoom(room, targetVersion, resp.invite);
},
});
return;
}
}
if (
this.state.encrypted &&
this.state.joinRule !== JoinRule.Public &&
joinRule === JoinRule.Public
) {
const dialog = Modal.createTrackedDialog('Confirm Public Encrypted Room', '', QuestionDialog, {
title: _t("Are you sure you want to make this encrypted room public?"),
description: <div>
<p> { _t(
"<b>It's not recommended to make encrypted rooms public.</b> " +
"It will mean anyone can find and join the room, so anyone can read messages. " +
"You'll get none of the benefits of encryption. Encrypting messages in a public " +
"room will make receiving and sending messages slower.",
null,
{ "b": (sub) => <b>{ sub }</b> },
) } </p>
<p> { _t(
"To avoid these issues, create a <a>new public room</a> for the conversation " +
"you plan to have.",
null,
{
"a": (sub) => <a
className="mx_linkButton"
onClick={() => {
dialog.close();
this.createNewRoom(true, false);
}}> { sub } </a>,
},
) } </p>
</div>,
});
const { finished } = dialog;
const [confirm] = await finished;
if (!confirm) return;
}
if (beforeJoinRule === joinRule && !restrictedAllowRoomIds) return;
const content: IContent = {
join_rule: joinRule,
};
// pre-set the accepted spaces with the currently viewed one as per the microcopy
if (joinRule === JoinRule.Restricted) {
content.allow = restrictedAllowRoomIds.map(roomId => ({
"type": RestrictedAllowType.RoomMembership,
"room_id": roomId,
}));
}
this.setState({ joinRule, restrictedAllowRoomIds });
const client = MatrixClientPeg.get();
client.sendStateEvent(this.props.roomId, EventType.RoomJoinRules, content, "").catch((e) => {
console.error(e);
this.setState({
joinRule: beforeJoinRule,
restrictedAllowRoomIds: undefined,
});
});
};
private onRestrictedRoomIdsChange = (restrictedAllowRoomIds: string[]) => {
const beforeRestrictedAllowRoomIds = this.state.restrictedAllowRoomIds;
if (!arrayHasDiff(beforeRestrictedAllowRoomIds || [], restrictedAllowRoomIds)) return;
this.setState({ restrictedAllowRoomIds });
const client = MatrixClientPeg.get();
client.sendStateEvent(this.props.roomId, EventType.RoomJoinRules, {
join_rule: JoinRule.Restricted,
allow: restrictedAllowRoomIds.map(roomId => ({
"type": RestrictedAllowType.RoomMembership,
"room_id": roomId,
})),
}, "").catch((e) => {
console.error(e);
this.setState({ restrictedAllowRoomIds: beforeRestrictedAllowRoomIds });
});
};
private onGuestAccessChange = (allowed: boolean) => { private onGuestAccessChange = (allowed: boolean) => {
const guestAccess = allowed ? GuestAccess.CanJoin : GuestAccess.Forbidden; const guestAccess = allowed ? GuestAccess.CanJoin : GuestAccess.Forbidden;
const beforeGuestAccess = this.state.guestAccess; const beforeGuestAccess = this.state.guestAccess;
@ -371,42 +248,12 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
} }
} }
private editRestrictedRoomIds = async (): Promise<string[] | undefined> => {
let selected = this.state.restrictedAllowRoomIds;
if (!selected?.length && SpaceStore.instance.activeSpace) {
selected = [SpaceStore.instance.activeSpace.roomId];
}
const matrixClient = MatrixClientPeg.get();
const { finished } = Modal.createTrackedDialog('Edit restricted', '', ManageRestrictedJoinRuleDialog, {
matrixClient,
room: matrixClient.getRoom(this.props.roomId),
selected,
}, "mx_ManageRestrictedJoinRuleDialog_wrapper");
const [restrictedAllowRoomIds] = await finished;
return restrictedAllowRoomIds;
};
private onEditRestrictedClick = async () => {
const restrictedAllowRoomIds = await this.editRestrictedRoomIds();
if (!Array.isArray(restrictedAllowRoomIds)) return;
if (restrictedAllowRoomIds.length > 0) {
this.onRestrictedRoomIdsChange(restrictedAllowRoomIds);
} else {
this.onJoinRuleChange(JoinRule.Invite);
}
};
private renderJoinRule() { private renderJoinRule() {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId); const room = client.getRoom(this.props.roomId);
const joinRule = this.state.joinRule;
const canChangeJoinRule = room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, client);
let aliasWarning = null; let aliasWarning = null;
if (joinRule === JoinRule.Public && !this.state.hasAliases) { if (room.getJoinRule() === JoinRule.Public && !this.state.hasAliases) {
aliasWarning = ( aliasWarning = (
<div className='mx_SecurityRoomSettingsTab_warning'> <div className='mx_SecurityRoomSettingsTab_warning'>
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} /> <img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
@ -417,112 +264,69 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
); );
} }
const radioDefinitions: IDefinition<JoinRule>[] = [{ return <div className="mx_SecurityRoomSettingsTab_joinRule">
value: JoinRule.Invite,
label: _t("Private (invite only)"),
description: _t("Only invited people can join."),
checked: this.state.joinRule === JoinRule.Invite
|| (this.state.joinRule === JoinRule.Restricted && !this.state.restrictedAllowRoomIds?.length),
}, {
value: JoinRule.Public,
label: _t("Public"),
description: _t("Anyone can find and join."),
}];
if (this.state.roomSupportsRestricted ||
this.state.preferredRestrictionVersion ||
joinRule === JoinRule.Restricted
) {
let upgradeRequiredPill;
if (this.state.preferredRestrictionVersion) {
upgradeRequiredPill = <span className="mx_SecurityRoomSettingsTab_upgradeRequired">
{ _t("Upgrade required") }
</span>;
}
let description;
if (joinRule === JoinRule.Restricted && this.state.restrictedAllowRoomIds?.length) {
const shownSpaces = this.state.restrictedAllowRoomIds
.map(roomId => client.getRoom(roomId))
.filter(room => room?.isSpaceRoom())
.slice(0, 4);
let moreText;
if (shownSpaces.length < this.state.restrictedAllowRoomIds.length) {
if (shownSpaces.length > 0) {
moreText = _t("& %(count)s more", {
count: this.state.restrictedAllowRoomIds.length - shownSpaces.length,
});
} else {
moreText = _t("Currently, %(count)s spaces have access", {
count: this.state.restrictedAllowRoomIds.length,
});
}
}
description = <div>
<span>
{ _t("Anyone in a space can find and join. <a>Edit which spaces can access here.</a>", {}, {
a: sub => <AccessibleButton
disabled={!canChangeJoinRule}
onClick={this.onEditRestrictedClick}
kind="link"
>
{ sub }
</AccessibleButton>,
}) }
</span>
<div className="mx_SecurityRoomSettingsTab_spacesWithAccess">
<h4>{ _t("Spaces with access") }</h4>
{ shownSpaces.map(room => {
return <span key={room.roomId}>
<RoomAvatar room={room} height={32} width={32} />
{ room.name }
</span>;
}) }
{ moreText && <span>{ moreText }</span> }
</div>
</div>;
} else if (SpaceStore.instance.activeSpace) {
description = _t("Anyone in %(spaceName)s can find and join. You can select other spaces too.", {
spaceName: SpaceStore.instance.activeSpace.name,
});
} else {
description = _t("Anyone in a space can find and join. You can select multiple spaces.");
}
radioDefinitions.splice(1, 0, {
value: JoinRule.Restricted,
label: <>
{ _t("Space members") }
{ upgradeRequiredPill }
</>,
description,
// if there are 0 allowed spaces then render it as invite only instead
checked: this.state.joinRule === JoinRule.Restricted && !!this.state.restrictedAllowRoomIds?.length,
});
}
return (
<div className="mx_SecurityRoomSettingsTab_joinRule">
<div className="mx_SettingsTab_subsectionText"> <div className="mx_SettingsTab_subsectionText">
<span>{ _t("Decide who can join %(roomName)s.", { <span>{ _t("Decide who can join %(roomName)s.", {
roomName: client.getRoom(this.props.roomId)?.name, roomName: room?.name,
}) }</span> }) }</span>
</div> </div>
{ aliasWarning } { aliasWarning }
<StyledRadioGroup
name="joinRule" <JoinRuleSettings
value={joinRule} room={room}
onChange={this.onJoinRuleChange} beforeChange={this.onBeforeJoinRuleChange}
definitions={radioDefinitions} onError={this.onJoinRuleChangeError}
disabled={!canChangeJoinRule} closeSettingsFn={this.props.closeSettingsFn}
promptUpgrade={true}
/> />
</div> </div>;
);
} }
private onJoinRuleChangeError = (error: Error) => {
Modal.createTrackedDialog('Room not found', '', ErrorDialog, {
title: _t("Failed to update the join rules"),
description: error.message ?? _t("Unknown failure"),
});
};
private onBeforeJoinRuleChange = async (joinRule: JoinRule): Promise<boolean> => {
if (this.state.encrypted && joinRule === JoinRule.Public) {
const dialog = Modal.createTrackedDialog('Confirm Public Encrypted Room', '', QuestionDialog, {
title: _t("Are you sure you want to make this encrypted room public?"),
description: <div>
<p> { _t(
"<b>It's not recommended to make encrypted rooms public.</b> " +
"It will mean anyone can find and join the room, so anyone can read messages. " +
"You'll get none of the benefits of encryption. Encrypting messages in a public " +
"room will make receiving and sending messages slower.",
null,
{ "b": (sub) => <b>{ sub }</b> },
) } </p>
<p> { _t(
"To avoid these issues, create a <a>new public room</a> for the conversation " +
"you plan to have.",
null,
{
"a": (sub) => <a
className="mx_linkButton"
onClick={() => {
dialog.close();
this.createNewRoom(true, false);
}}> { sub } </a>,
},
) } </p>
</div>,
});
const { finished } = dialog;
const [confirm] = await finished;
if (!confirm) return false;
}
return true;
};
private renderHistory() { private renderHistory() {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const history = this.state.history; const history = this.state.history;
@ -620,6 +424,22 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
historySection = null; historySection = null;
} }
let advanced;
if (room.getJoinRule() === JoinRule.Public) {
advanced = (
<>
<AccessibleButton
onClick={this.toggleAdvancedSection}
kind="link"
className="mx_SettingsTab_showAdvanced"
>
{ this.state.showAdvancedSection ? _t("Hide advanced") : _t("Show advanced") }
</AccessibleButton>
{ this.state.showAdvancedSection && this.renderAdvanced() }
</>
);
}
return ( return (
<div className="mx_SettingsTab mx_SecurityRoomSettingsTab"> <div className="mx_SettingsTab mx_SecurityRoomSettingsTab">
<div className="mx_SettingsTab_heading">{ _t("Security & Privacy") }</div> <div className="mx_SettingsTab_heading">{ _t("Security & Privacy") }</div>
@ -645,15 +465,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{ this.renderJoinRule() } { this.renderJoinRule() }
</div> </div>
<AccessibleButton { advanced }
onClick={this.toggleAdvancedSection}
kind="link"
className="mx_SettingsTab_showAdvanced"
>
{ this.state.showAdvancedSection ? _t("Hide advanced") : _t("Show advanced") }
</AccessibleButton>
{ this.state.showAdvancedSection && this.renderAdvanced() }
{ historySection } { historySection }
</div> </div>
); );

View file

@ -72,8 +72,10 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
private getVersionInfo(): { appVersion: string, olmVersion: string } { private getVersionInfo(): { appVersion: string, olmVersion: string } {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const appVersion = this.state.appVersion || 'unknown'; const appVersion = this.state.appVersion || 'unknown';
let olmVersion = MatrixClientPeg.get().olmVersion; const olmVersionTuple = MatrixClientPeg.get().olmVersion;
olmVersion = olmVersion ? `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}` : '<not-enabled>'; const olmVersion = olmVersionTuple
? `${olmVersionTuple[0]}.${olmVersionTuple[1]}.${olmVersionTuple[2]}`
: '<not-enabled>';
return { return {
appVersion: `${_t("%(brand)s version:", { brand })} ${appVersion}`, appVersion: `${_t("%(brand)s version:", { brand })} ${appVersion}`,

View file

@ -195,12 +195,10 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
{ (provided, snapshot) => ( { (provided, snapshot) => (
<SpaceItem <SpaceItem
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps} dragHandleProps={provided.dragHandleProps}
key={s.roomId} key={s.roomId}
innerRef={provided.innerRef} innerRef={provided.innerRef}
className={snapshot.isDragging className={snapshot.isDragging ? "mx_SpaceItem_dragging" : undefined}
? "mx_SpaceItem_dragging"
: undefined}
space={s} space={s}
activeSpaces={activeSpaces} activeSpaces={activeSpaces}
isPanelCollapsed={isPanelCollapsed} isPanelCollapsed={isPanelCollapsed}
@ -223,6 +221,8 @@ const SpacePanel = () => {
}, []); }, []);
const onKeyDown = (ev: React.KeyboardEvent) => { const onKeyDown = (ev: React.KeyboardEvent) => {
if (ev.defaultPrevented) return;
let handled = true; let handled = true;
switch (ev.key) { switch (ev.key) {

View file

@ -39,7 +39,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
onClick={async () => { onClick={async () => {
const permalinkCreator = new RoomPermalinkCreator(space); const permalinkCreator = new RoomPermalinkCreator(space);
permalinkCreator.load(); permalinkCreator.load();
const success = await copyPlaintext(permalinkCreator.forRoom()); const success = await copyPlaintext(permalinkCreator.forShareableRoom());
const text = success ? _t("Copied!") : _t("Failed to copy"); const text = success ? _t("Copied!") : _t("Failed to copy");
setCopiedText(text); setCopiedText(text);
await sleep(5000); await sleep(5000);

View file

@ -25,49 +25,22 @@ import AccessibleButton from "../elements/AccessibleButton";
import AliasSettings from "../room_settings/AliasSettings"; import AliasSettings from "../room_settings/AliasSettings";
import { useStateToggle } from "../../../hooks/useStateToggle"; import { useStateToggle } from "../../../hooks/useStateToggle";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import StyledRadioGroup from "../elements/StyledRadioGroup"; import { useLocalEcho } from "../../../hooks/useLocalEcho";
import JoinRuleSettings from "../settings/JoinRuleSettings";
import { useRoomState } from "../../../hooks/useRoomState";
interface IProps { interface IProps {
matrixClient: MatrixClient; matrixClient: MatrixClient;
space: Room; space: Room;
closeSettingsFn(): void;
} }
enum SpaceVisibility { const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space, closeSettingsFn }: IProps) => {
Unlisted = "unlisted",
Private = "private",
}
const useLocalEcho = <T extends any>(
currentFactory: () => T,
setterFn: (value: T) => Promise<unknown>,
errorFn: (error: Error) => void,
): [value: T, handler: (value: T) => void] => {
const [value, setValue] = useState(currentFactory);
const handler = async (value: T) => {
setValue(value);
try {
await setterFn(value);
} catch (e) {
setValue(currentFactory());
errorFn(e);
}
};
return [value, handler];
};
const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
const [error, setError] = useState(""); const [error, setError] = useState("");
const userId = cli.getUserId(); const userId = cli.getUserId();
const [visibility, setVisibility] = useLocalEcho<SpaceVisibility>( const joinRule = useRoomState(space, state => state.getJoinRule());
() => space.getJoinRule() === JoinRule.Invite ? SpaceVisibility.Private : SpaceVisibility.Unlisted,
visibility => cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, {
join_rule: visibility === SpaceVisibility.Unlisted ? JoinRule.Public : JoinRule.Invite,
}, ""),
() => setError(_t("Failed to update the visibility of this space")),
);
const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho<boolean>( const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho<boolean>(
() => space.currentState.getStateEvents(EventType.RoomGuestAccess, "") () => space.currentState.getStateEvents(EventType.RoomGuestAccess, "")
?.getContent()?.guest_access === GuestAccess.CanJoin, ?.getContent()?.guest_access === GuestAccess.CanJoin,
@ -87,13 +60,13 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
const [showAdvancedSection, toggleAdvancedSection] = useStateToggle(); const [showAdvancedSection, toggleAdvancedSection] = useStateToggle();
const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId);
const canSetGuestAccess = space.currentState.maySendStateEvent(EventType.RoomGuestAccess, userId); const canSetGuestAccess = space.currentState.maySendStateEvent(EventType.RoomGuestAccess, userId);
const canSetHistoryVisibility = space.currentState.maySendStateEvent(EventType.RoomHistoryVisibility, userId); const canSetHistoryVisibility = space.currentState.maySendStateEvent(EventType.RoomHistoryVisibility, userId);
const canSetCanonical = space.currentState.mayClientSendStateEvent(EventType.RoomCanonicalAlias, cli); const canSetCanonical = space.currentState.mayClientSendStateEvent(EventType.RoomCanonicalAlias, cli);
const canonicalAliasEv = space.currentState.getStateEvents(EventType.RoomCanonicalAlias, ""); const canonicalAliasEv = space.currentState.getStateEvents(EventType.RoomCanonicalAlias, "");
let advancedSection; let advancedSection;
if (joinRule === JoinRule.Public) {
if (showAdvancedSection) { if (showAdvancedSection) {
advancedSection = <> advancedSection = <>
<AccessibleButton onClick={toggleAdvancedSection} kind="link" className="mx_SettingsTab_showAdvanced"> <AccessibleButton onClick={toggleAdvancedSection} kind="link" className="mx_SettingsTab_showAdvanced">
@ -119,9 +92,10 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
</AccessibleButton> </AccessibleButton>
</>; </>;
} }
}
let addressesSection; let addressesSection;
if (visibility !== SpaceVisibility.Private) { if (space.getJoinRule() === JoinRule.Public) {
addressesSection = <> addressesSection = <>
<span className="mx_SettingsTab_subheading">{ _t("Address") }</span> <span className="mx_SettingsTab_subheading">{ _t("Address") }</span>
<div className="mx_SettingsTab_section mx_SettingsTab_subsectionText"> <div className="mx_SettingsTab_section mx_SettingsTab_subsectionText">
@ -147,22 +121,10 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
</div> </div>
<div> <div>
<StyledRadioGroup <JoinRuleSettings
name="spaceVisibility" room={space}
value={visibility} onError={() => setError(_t("Failed to update the visibility of this space"))}
onChange={setVisibility} closeSettingsFn={closeSettingsFn}
disabled={!canSetJoinRule}
definitions={[
{
value: SpaceVisibility.Unlisted,
label: _t("Public"),
description: _t("anyone with the link can view and join"),
}, {
value: SpaceVisibility.Private,
label: _t("Invite only"),
description: _t("only invited people can view and join"),
},
]}
/> />
</div> </div>

View file

@ -29,7 +29,6 @@ import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore"; import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore"; import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge"; import NotificationBadge from "../rooms/NotificationBadge";
import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import { toRightOf, useContextMenu } from "../../structures/ContextMenu"; import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
@ -40,8 +39,11 @@ import { NotificationColor } from "../../../stores/notifications/NotificationCol
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager"; import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
import { NotificationState } from "../../../stores/notifications/NotificationState"; import { NotificationState } from "../../../stores/notifications/NotificationState";
import SpaceContextMenu from "../context_menus/SpaceContextMenu"; import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
interface IButtonProps extends Omit<ComponentProps<typeof RovingAccessibleTooltipButton>, "title"> { interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title"> {
space?: Room; space?: Room;
className?: string; className?: string;
selected?: boolean; selected?: boolean;
@ -68,7 +70,9 @@ export const SpaceButton: React.FC<IButtonProps> = ({
ContextMenuComponent, ContextMenuComponent,
...props ...props
}) => { }) => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>(); const [menuDisplayed, ref, openMenu, closeMenu] = useContextMenu<HTMLElement>();
const [onFocus, isActive, handle] = useRovingTabIndex(ref);
const tabIndex = isActive ? 0 : -1;
let avatar = <div className="mx_SpaceButton_avatarPlaceholder"><div className="mx_SpaceButton_icon" /></div>; let avatar = <div className="mx_SpaceButton_avatarPlaceholder"><div className="mx_SpaceButton_icon" /></div>;
if (space) { if (space) {
@ -88,6 +92,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
forceCount={false} forceCount={false}
notification={notificationState} notification={notificationState}
aria-label={ariaLabel} aria-label={ariaLabel}
tabIndex={tabIndex}
/> />
</div>; </div>;
} }
@ -102,7 +107,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
} }
return ( return (
<RovingAccessibleTooltipButton <AccessibleTooltipButton
{...props} {...props}
className={classNames("mx_SpaceButton", className, { className={classNames("mx_SpaceButton", className, {
mx_SpaceButton_active: selected, mx_SpaceButton_active: selected,
@ -114,6 +119,8 @@ export const SpaceButton: React.FC<IButtonProps> = ({
onContextMenu={openMenu} onContextMenu={openMenu}
forceHide={!isNarrow || menuDisplayed} forceHide={!isNarrow || menuDisplayed}
inputRef={handle} inputRef={handle}
tabIndex={tabIndex}
onFocus={onFocus}
> >
{ children } { children }
<div className="mx_SpaceButton_selectionWrapper"> <div className="mx_SpaceButton_selectionWrapper">
@ -130,7 +137,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
{ contextMenu } { contextMenu }
</div> </div>
</RovingAccessibleTooltipButton> </AccessibleTooltipButton>
); );
}; };
@ -142,6 +149,7 @@ interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
onExpand?: Function; onExpand?: Function;
parents?: Set<string>; parents?: Set<string>;
innerRef?: LegacyRef<HTMLLIElement>; innerRef?: LegacyRef<HTMLLIElement>;
dragHandleProps?: DraggableProvidedDragHandleProps;
} }
interface IItemState { interface IItemState {
@ -252,7 +260,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
render() { render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef, const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef, dragHandleProps,
...otherProps } = this.props; ...otherProps } = this.props;
const collapsed = this.isCollapsed; const collapsed = this.isCollapsed;
@ -270,8 +278,10 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
? StaticNotificationState.forSymbol("!", NotificationColor.Red) ? StaticNotificationState.forSymbol("!", NotificationColor.Red)
: SpaceStore.instance.getNotificationState(space.roomId); : SpaceStore.instance.getNotificationState(space.roomId);
const hasChildren = this.state.childSpaces?.length;
let childItems; let childItems;
if (this.state.childSpaces?.length && !collapsed) { if (hasChildren && !collapsed) {
childItems = <SpaceTreeLevel childItems = <SpaceTreeLevel
spaces={this.state.childSpaces} spaces={this.state.childSpaces}
activeSpaces={activeSpaces} activeSpaces={activeSpaces}
@ -280,7 +290,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
/>; />;
} }
const toggleCollapseButton = this.state.childSpaces?.length ? const toggleCollapseButton = hasChildren ?
<AccessibleButton <AccessibleButton
className="mx_SpaceButton_toggleCollapse" className="mx_SpaceButton_toggleCollapse"
onClick={this.toggleCollapse} onClick={this.toggleCollapse}
@ -288,9 +298,19 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
aria-label={collapsed ? _t("Expand") : _t("Collapse")} aria-label={collapsed ? _t("Expand") : _t("Collapse")}
/> : null; /> : null;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { tabIndex, ...restDragHandleProps } = dragHandleProps || {};
return ( return (
<li {...otherProps} className={itemClasses} ref={innerRef} aria-expanded={!collapsed} role="treeitem"> <li
{...otherProps}
className={itemClasses}
ref={innerRef}
aria-expanded={hasChildren ? !collapsed : undefined}
role="treeitem"
>
<SpaceButton <SpaceButton
{...restDragHandleProps}
space={space} space={space}
className={isInvite ? "mx_SpaceButton_invite" : undefined} className={isInvite ? "mx_SpaceButton_invite" : undefined}
selected={activeSpaces.includes(space)} selected={activeSpaces.includes(space)}

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import { SAS } from "matrix-js-sdk/src/crypto/verification/SAS"; import { IGeneratedSas } from "matrix-js-sdk/src/crypto/verification/SAS";
import { DeviceInfo } from "matrix-js-sdk/src//crypto/deviceinfo"; import { DeviceInfo } from "matrix-js-sdk/src//crypto/deviceinfo";
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import { PendingActionSpinner } from "../right_panel/EncryptionInfo"; import { PendingActionSpinner } from "../right_panel/EncryptionInfo";
@ -30,7 +30,7 @@ interface IProps {
device?: DeviceInfo; device?: DeviceInfo;
onDone: () => void; onDone: () => void;
onCancel: () => void; onCancel: () => void;
sas: SAS.sas; sas: IGeneratedSas;
isSelf?: boolean; isSelf?: boolean;
inDialog?: boolean; // whether this component is being shown in a dialog and to use DialogButtons inDialog?: boolean; // whether this component is being shown in a dialog and to use DialogButtons
} }

View file

@ -273,14 +273,14 @@ export default class CallView extends React.Component<IProps, IState> {
}; };
private onScreenshareClick = async (): Promise<void> => { private onScreenshareClick = async (): Promise<void> => {
const isScreensharing = await this.props.call.setScreensharingEnabled( let isScreensharing;
!this.state.screensharing, if (this.state.screensharing) {
async (): Promise<DesktopCapturerSource> => { isScreensharing = await this.props.call.setScreensharingEnabled(false);
} else {
const { finished } = Modal.createDialog(DesktopCapturerSourcePicker); const { finished } = Modal.createDialog(DesktopCapturerSourcePicker);
const [source] = await finished; const [source] = await finished;
return source; isScreensharing = await this.props.call.setScreensharingEnabled(true, source);
}, }
);
this.setState({ this.setState({
sidebarShown: true, sidebarShown: true,

36
src/hooks/useLocalEcho.ts Normal file
View file

@ -0,0 +1,36 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState } from "react";
export const useLocalEcho = <T extends any>(
currentFactory: () => T,
setterFn: (value: T) => Promise<unknown>,
errorFn: (error: Error) => void,
): [value: T, handler: (value: T) => void] => {
const [value, setValue] = useState(currentFactory);
const handler = async (value: T) => {
setValue(value);
try {
await setterFn(value);
} catch (e) {
setValue(currentFactory());
errorFn(e);
}
};
return [value, handler];
};

View file

@ -3368,7 +3368,7 @@
"Failed to update the guest access of this space": "Nepodařilo se aktualizovat přístup hosta do tohoto prostoru", "Failed to update the guest access of this space": "Nepodařilo se aktualizovat přístup hosta do tohoto prostoru",
"Failed to update the visibility of this space": "Nepodařilo se aktualizovat viditelnost tohoto prostoru", "Failed to update the visibility of this space": "Nepodařilo se aktualizovat viditelnost tohoto prostoru",
"e.g. my-space": "např. můj-prostor", "e.g. my-space": "např. můj-prostor",
"Silence call": "Tiché volání", "Silence call": "Ztlumit zvonění",
"Sound on": "Zvuk zapnutý", "Sound on": "Zvuk zapnutý",
"Show notification badges for People in Spaces": "Zobrazit odznaky oznámení v Lidi v prostorech", "Show notification badges for People in Spaces": "Zobrazit odznaky oznámení v Lidi v prostorech",
"If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "Pokud je zakázáno, můžete stále přidávat přímé zprávy do osobních prostorů. Pokud je povoleno, automaticky se zobrazí všichni, kteří jsou členy daného prostoru.", "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "Pokud je zakázáno, můžete stále přidávat přímé zprávy do osobních prostorů. Pokud je povoleno, automaticky se zobrazí všichni, kteří jsou členy daného prostoru.",
@ -3594,5 +3594,31 @@
"You can change this later.": "Toto můžete změnit později.", "You can change this later.": "Toto můžete změnit později.",
"Unknown failure: %(reason)s": "Neznámá chyba: %(reason)s", "Unknown failure: %(reason)s": "Neznámá chyba: %(reason)s",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Ladící protokoly obsahují údaje o používání aplikace včetně vašeho uživatelského jména, ID nebo aliasů místností nebo skupin, které jste navštívili, s kterými prvky uživatelského rozhraní jste naposledy interagovali a uživatelská jména ostatních uživatelů. Neobsahují zprávy.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Ladící protokoly obsahují údaje o používání aplikace včetně vašeho uživatelského jména, ID nebo aliasů místností nebo skupin, které jste navštívili, s kterými prvky uživatelského rozhraní jste naposledy interagovali a uživatelská jména ostatních uživatelů. Neobsahují zprávy.",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. Ladicí protokoly obsahují údaje o používání aplikace včetně vašeho uživatelského jména, ID nebo aliasů místností nebo skupin, které jste navštívili, s jakými prvky uživatelského rozhraní jste naposledy interagovali a uživatelská jména ostatních uživatelů. Neobsahují zprávy." "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. Ladicí protokoly obsahují údaje o používání aplikace včetně vašeho uživatelského jména, ID nebo aliasů místností nebo skupin, které jste navštívili, s jakými prvky uživatelského rozhraní jste naposledy interagovali a uživatelská jména ostatních uživatelů. Neobsahují zprávy.",
"Rooms and spaces": "Místnosti a prostory",
"Results": "Výsledky",
"Enable encryption in settings.": "Povolte šifrování v nastavení.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Vaše soukromé zprávy jsou obvykle šifrované, ale tato místnost není. Obvykle je to způsobeno nepodporovaným zařízením nebo použitou metodou, například e-mailovými pozvánkami.",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Abyste se těmto problémům vyhnuli, vytvořte pro plánovanou konverzaci <a>novou veřejnou místnost</a>.",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Nedoporučujeme šifrované místnosti zveřejňovat.</b> Znamená to, že místnost může kdokoli najít a připojit se k ní, takže si kdokoli může přečíst zprávy. Nezískáte tak žádnou z výhod šifrování. Šifrování zpráv ve veřejné místnosti zpomalí příjem a odesílání zpráv.",
"Are you sure you want to make this encrypted room public?": "Jste si jisti, že chcete tuto šifrovanou místnost zveřejnit?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Chcete-li se těmto problémům vyhnout, vytvořte pro plánovanou konverzaci <a>novou šifrovanou místnost</a>.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Nedoporučuje se šifrovat veřejné místnosti.</b>Veřejné místnosti může najít a připojit se k nim kdokoli, takže si v nich může číst zprávy kdokoli. Nezískáte tak žádnou z výhod šifrování a nebudete ho moci později vypnout. Šifrování zpráv ve veřejné místnosti zpomalí příjem a odesílání zpráv.",
"Are you sure you want to add encryption to this public room?": "Opravdu chcete šifrovat tuto veřejnou místnost?",
"Cross-signing is ready but keys are not backed up.": "Křížové podepisování je připraveno, ale klíče nejsou zálohovány.",
"Low bandwidth mode (requires compatible homeserver)": "Režim malé šířky pásma (vyžaduje kompatibilní homeserver)",
"Multiple integration managers (requires manual setup)": "Více správců integrace (vyžaduje ruční nastavení)",
"Threaded messaging": "Zprávy ve vláknech",
"Thread": "Vlákno",
"Show threads": "Zobrazit vlákna",
"The above, but in <Room /> as well": "Výše uvedené, ale také v <Room />",
"The above, but in any room you are joined or invited to as well": "Výše uvedené, ale také v jakékoli místnosti, ke které jste připojeni nebo do které jste pozváni",
"Autoplay videos": "Automatické přehrávání videí",
"Autoplay GIFs": "Automatické přehrávání GIFů",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s odepnul zprávu z této místnosti. Zobrazit všechny připnuté zprávy.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s odepnul <a>zprávu</a> z této místnosti. Zobrazit všechny <b>připnuté zprávy</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s připnul zprávu k této místnosti. Zobrazit všechny připnuté zprávy.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s připnul <a>zprávu</a> k této místnosti. Zobrazit všechny <b>připnuté zprávy</b>.",
"Currently, %(count)s spaces have access|one": "V současné době má prostor přístup",
"& %(count)s more|one": "a %(count)s další"
} }

View file

@ -1065,7 +1065,6 @@
"Saving...": "Saving...", "Saving...": "Saving...",
"Save Changes": "Save Changes", "Save Changes": "Save Changes",
"Leave Space": "Leave Space", "Leave Space": "Leave Space",
"Failed to update the visibility of this space": "Failed to update the visibility of this space",
"Failed to update the guest access of this space": "Failed to update the guest access of this space", "Failed to update the guest access of this space": "Failed to update the guest access of this space",
"Failed to update the history visibility of this space": "Failed to update the history visibility of this space", "Failed to update the history visibility of this space": "Failed to update the history visibility of this space",
"Hide advanced": "Hide advanced", "Hide advanced": "Hide advanced",
@ -1075,9 +1074,7 @@
"Show advanced": "Show advanced", "Show advanced": "Show advanced",
"Visibility": "Visibility", "Visibility": "Visibility",
"Decide who can view and join %(spaceName)s.": "Decide who can view and join %(spaceName)s.", "Decide who can view and join %(spaceName)s.": "Decide who can view and join %(spaceName)s.",
"anyone with the link can view and join": "anyone with the link can view and join", "Failed to update the visibility of this space": "Failed to update the visibility of this space",
"Invite only": "Invite only",
"only invited people can view and join": "only invited people can view and join",
"Preview Space": "Preview Space", "Preview Space": "Preview Space",
"Allow people to preview your space before they join.": "Allow people to preview your space before they join.", "Allow people to preview your space before they join.": "Allow people to preview your space before they join.",
"Recommended for public spaces.": "Recommended for public spaces.", "Recommended for public spaces.": "Recommended for public spaces.",
@ -1151,6 +1148,20 @@
"Connecting to integration manager...": "Connecting to integration manager...", "Connecting to integration manager...": "Connecting to integration manager...",
"Cannot connect to integration manager": "Cannot connect to integration manager", "Cannot connect to integration manager": "Cannot connect to integration manager",
"The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.", "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.",
"Private (invite only)": "Private (invite only)",
"Only invited people can join.": "Only invited people can join.",
"Anyone can find and join.": "Anyone can find and join.",
"Upgrade required": "Upgrade required",
"& %(count)s more|other": "& %(count)s more",
"& %(count)s more|one": "& %(count)s more",
"Currently, %(count)s spaces have access|other": "Currently, %(count)s spaces have access",
"Currently, %(count)s spaces have access|one": "Currently, a space has access",
"Anyone in a space can find and join. <a>Edit which spaces can access here.</a>": "Anyone in a space can find and join. <a>Edit which spaces can access here.</a>",
"Spaces with access": "Spaces with access",
"Anyone in <spaceName/> can find and join. You can select other spaces too.": "Anyone in <spaceName/> can find and join. You can select other spaces too.",
"Anyone in a space can find and join. You can select multiple spaces.": "Anyone in a space can find and join. You can select multiple spaces.",
"Space members": "Space members",
"This upgrade will allow members of selected spaces access to this room without an invite.": "This upgrade will allow members of selected spaces access to this room without an invite.",
"Message layout": "Message layout", "Message layout": "Message layout",
"IRC": "IRC", "IRC": "IRC",
"Modern": "Modern", "Modern": "Modern",
@ -1459,23 +1470,13 @@
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.", "To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.",
"Enable encryption?": "Enable encryption?", "Enable encryption?": "Enable encryption?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>", "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>",
"This upgrade will allow members of selected spaces access to this room without an invite.": "This upgrade will allow members of selected spaces access to this room without an invite.", "To link to this room, please add an address.": "To link to this room, please add an address.",
"Decide who can join %(roomName)s.": "Decide who can join %(roomName)s.",
"Failed to update the join rules": "Failed to update the join rules",
"Unknown failure": "Unknown failure",
"Are you sure you want to make this encrypted room public?": "Are you sure you want to make this encrypted room public?", "Are you sure you want to make this encrypted room public?": "Are you sure you want to make this encrypted room public?",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.", "<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.", "To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.",
"To link to this room, please add an address.": "To link to this room, please add an address.",
"Private (invite only)": "Private (invite only)",
"Only invited people can join.": "Only invited people can join.",
"Anyone can find and join.": "Anyone can find and join.",
"Upgrade required": "Upgrade required",
"& %(count)s more|other": "& %(count)s more",
"Currently, %(count)s spaces have access|other": "Currently, %(count)s spaces have access",
"Anyone in a space can find and join. <a>Edit which spaces can access here.</a>": "Anyone in a space can find and join. <a>Edit which spaces can access here.</a>",
"Spaces with access": "Spaces with access",
"Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Anyone in %(spaceName)s can find and join. You can select other spaces too.",
"Anyone in a space can find and join. You can select multiple spaces.": "Anyone in a space can find and join. You can select multiple spaces.",
"Space members": "Space members",
"Decide who can join %(roomName)s.": "Decide who can join %(roomName)s.",
"Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)",
"Members only (since they were invited)": "Members only (since they were invited)", "Members only (since they were invited)": "Members only (since they were invited)",
"Members only (since they joined)": "Members only (since they joined)", "Members only (since they joined)": "Members only (since they joined)",
@ -1557,12 +1558,19 @@
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)",
"Send message": "Send message", "Send message": "Send message",
"Emoji picker": "Emoji picker", "Emoji picker": "Emoji picker",
"Add emoji": "Add emoji",
"Upload file": "Upload file", "Upload file": "Upload file",
"Reply to encrypted thread…": "Reply to encrypted thread…",
"Reply to thread…": "Reply to thread…",
"Send an encrypted reply…": "Send an encrypted reply…", "Send an encrypted reply…": "Send an encrypted reply…",
"Send a reply…": "Send a reply…", "Send a reply…": "Send a reply…",
"Send an encrypted message…": "Send an encrypted message…", "Send an encrypted message…": "Send an encrypted message…",
"Send a message…": "Send a message…", "Send a message…": "Send a message…",
"Hide Stickers": "Hide Stickers",
"Show Stickers": "Show Stickers",
"Send a sticker": "Send a sticker",
"Send voice message": "Send voice message", "Send voice message": "Send voice message",
"More options": "More options",
"The conversation continues here.": "The conversation continues here.", "The conversation continues here.": "The conversation continues here.",
"This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
"You do not have permission to post to this room": "You do not have permission to post to this room", "You do not have permission to post to this room": "You do not have permission to post to this room",
@ -1641,6 +1649,7 @@
"Start a new chat": "Start a new chat", "Start a new chat": "Start a new chat",
"Explore all public rooms": "Explore all public rooms", "Explore all public rooms": "Explore all public rooms",
"Quick actions": "Quick actions", "Quick actions": "Quick actions",
"Explore %(spaceName)s": "Explore %(spaceName)s",
"Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below", "Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below",
"%(count)s results in all spaces|other": "%(count)s results in all spaces", "%(count)s results in all spaces|other": "%(count)s results in all spaces",
"%(count)s results in all spaces|one": "%(count)s result in all spaces", "%(count)s results in all spaces|one": "%(count)s result in all spaces",
@ -1723,8 +1732,6 @@
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
"Add some now": "Add some now", "Add some now": "Add some now",
"Stickerpack": "Stickerpack", "Stickerpack": "Stickerpack",
"Hide Stickers": "Hide Stickers",
"Show Stickers": "Show Stickers",
"Failed to revoke invite": "Failed to revoke invite", "Failed to revoke invite": "Failed to revoke invite",
"Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.", "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.",
"Admin Tools": "Admin Tools", "Admin Tools": "Admin Tools",
@ -1864,7 +1871,7 @@
"Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?", "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?",
"Deactivate user": "Deactivate user", "Deactivate user": "Deactivate user",
"Failed to deactivate user": "Failed to deactivate user", "Failed to deactivate user": "Failed to deactivate user",
"Role": "Role", "Role in <RoomName/>": "Role in <RoomName/>",
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
"Edit devices": "Edit devices", "Edit devices": "Edit devices",
"Security": "Security", "Security": "Security",
@ -1920,6 +1927,7 @@
"Decrypting": "Decrypting", "Decrypting": "Decrypting",
"Download": "Download", "Download": "Download",
"View Source": "View Source", "View Source": "View Source",
"Some encryption parameters have been changed.": "Some encryption parameters have been changed.",
"Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.", "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.",
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.", "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.",
"Encryption enabled": "Encryption enabled", "Encryption enabled": "Encryption enabled",

View file

@ -18,7 +18,7 @@
"Are you sure?": "¿Estás seguro?", "Are you sure?": "¿Estás seguro?",
"Are you sure you want to reject the invitation?": "¿Estás seguro que quieres rechazar la invitación?", "Are you sure you want to reject the invitation?": "¿Estás seguro que quieres rechazar la invitación?",
"Attachment": "Adjunto", "Attachment": "Adjunto",
"Autoplay GIFs and videos": "Reproducir automáticamente GIFs y videos", "Autoplay GIFs and videos": "Reproducir automáticamente GIFs y vídeos",
"%(senderName)s banned %(targetName)s.": "%(senderName)s vetó a %(targetName)s.", "%(senderName)s banned %(targetName)s.": "%(senderName)s vetó a %(targetName)s.",
"Ban": "Vetar", "Ban": "Vetar",
"Banned users": "Usuarios vetados", "Banned users": "Usuarios vetados",
@ -360,7 +360,7 @@
"Your language of choice": "Idioma elegido", "Your language of choice": "Idioma elegido",
"Your homeserver's URL": "La URL de tu servidor base", "Your homeserver's URL": "La URL de tu servidor base",
"The information being sent to us to help make %(brand)s better includes:": "La información que se nos envía para ayudarnos a mejorar %(brand)s incluye:", "The information being sent to us to help make %(brand)s better includes:": "La información que se nos envía para ayudarnos a mejorar %(brand)s incluye:",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Estés utilizando o no el modo de texto enriquecido del editor de texto enriquecido", "Whether or not you're using the Richtext mode of the Rich Text Editor": "Si estás o no usando el editor de texto enriquecido",
"Who would you like to add to this community?": "¿A quién te gustaría añadir a esta comunidad?", "Who would you like to add to this community?": "¿A quién te gustaría añadir a esta comunidad?",
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Advertencia: cualquier persona que añadas a una comunidad será públicamente visible a cualquiera que conozca la ID de la comunidad", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Advertencia: cualquier persona que añadas a una comunidad será públicamente visible a cualquiera que conozca la ID de la comunidad",
"Invite new community members": "Invita nuevos miembros a la comunidad", "Invite new community members": "Invita nuevos miembros a la comunidad",
@ -527,7 +527,7 @@
"Stops ignoring a user, showing their messages going forward": "Deja de ignorar a un usuario, mostrando sus mensajes a partir de ahora", "Stops ignoring a user, showing their messages going forward": "Deja de ignorar a un usuario, mostrando sus mensajes a partir de ahora",
"Unignored user": "Usuario no ignorado", "Unignored user": "Usuario no ignorado",
"You are no longer ignoring %(userId)s": "Ya no ignoras a %(userId)s", "You are no longer ignoring %(userId)s": "Ya no ignoras a %(userId)s",
"Opens the Developer Tools dialog": "Abre el diálogo de Herramientas de Desarrollador", "Opens the Developer Tools dialog": "Abre el diálogo de herramientas de desarrollo",
"%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s cambió su nombre público a %(displayName)s.", "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s cambió su nombre público a %(displayName)s.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s cambió los mensajes anclados de la sala.", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s cambió los mensajes anclados de la sala.",
"%(widgetName)s widget modified by %(senderName)s": "el widget %(widgetName)s fue modificado por %(senderName)s", "%(widgetName)s widget modified by %(senderName)s": "el widget %(widgetName)s fue modificado por %(senderName)s",
@ -610,7 +610,7 @@
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Cuando alguien incluye una URL en su mensaje, se mostrará una vista previa para ofrecer información sobre el enlace, que incluirá el título, descripción, y una imagen del sitio web.", "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Cuando alguien incluye una URL en su mensaje, se mostrará una vista previa para ofrecer información sobre el enlace, que incluirá el título, descripción, y una imagen del sitio web.",
"Error decrypting audio": "Error al descifrar el sonido", "Error decrypting audio": "Error al descifrar el sonido",
"Error decrypting image": "Error al descifrar imagen", "Error decrypting image": "Error al descifrar imagen",
"Error decrypting video": "Error al descifrar video", "Error decrypting video": "Error al descifrar el vídeo",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s cambió el avatar para %(roomName)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s cambió el avatar para %(roomName)s",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s eliminó el avatar de la sala.", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s eliminó el avatar de la sala.",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s cambió el avatar de la sala a <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s cambió el avatar de la sala a <img/>",
@ -855,8 +855,8 @@
"Please contact your homeserver administrator.": "Por favor, contacta con la administración de tu servidor base.", "Please contact your homeserver administrator.": "Por favor, contacta con la administración de tu servidor base.",
"This room has been replaced and is no longer active.": "Esta sala ha sido reemplazada y ya no está activa.", "This room has been replaced and is no longer active.": "Esta sala ha sido reemplazada y ya no está activa.",
"The conversation continues here.": "La conversación continúa aquí.", "The conversation continues here.": "La conversación continúa aquí.",
"This room is a continuation of another conversation.": "Esta sala es una continuación de otra conversación.", "This room is a continuation of another conversation.": "Esta sala es una continuación de otra.",
"Click here to see older messages.": "Haz clic aquí para ver mensajes más antiguos.", "Click here to see older messages.": "Haz clic aquí para ver mensajes anteriores.",
"Failed to upgrade room": "No se pudo actualizar la sala", "Failed to upgrade room": "No se pudo actualizar la sala",
"The room upgrade could not be completed": "La actualización de la sala no pudo ser completada", "The room upgrade could not be completed": "La actualización de la sala no pudo ser completada",
"Upgrade this room to version %(version)s": "Actualiza esta sala a la versión %(version)s", "Upgrade this room to version %(version)s": "Actualiza esta sala a la versión %(version)s",
@ -867,7 +867,7 @@
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s ahora utiliza de 3 a 5 veces menos memoria, porque solo carga información sobre otros usuarios cuando es necesario. Por favor, ¡aguarda mientras volvemos a sincronizar con el servidor!", "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s ahora utiliza de 3 a 5 veces menos memoria, porque solo carga información sobre otros usuarios cuando es necesario. Por favor, ¡aguarda mientras volvemos a sincronizar con el servidor!",
"Updating %(brand)s": "Actualizando %(brand)s", "Updating %(brand)s": "Actualizando %(brand)s",
"Room version:": "Versión de la sala:", "Room version:": "Versión de la sala:",
"Developer options": "Opciones de desarrollador", "Developer options": "Opciones de desarrollo",
"Room version": "Versión de la sala", "Room version": "Versión de la sala",
"Room information": "Información de la sala", "Room information": "Información de la sala",
"Room Topic": "Asunto de la sala", "Room Topic": "Asunto de la sala",
@ -938,7 +938,7 @@
"Send typing notifications": "Enviar notificaciones de tecleo", "Send typing notifications": "Enviar notificaciones de tecleo",
"Allow Peer-to-Peer for 1:1 calls": "Permitir conexiones «peer-to-peer en llamadas individuales", "Allow Peer-to-Peer for 1:1 calls": "Permitir conexiones «peer-to-peer en llamadas individuales",
"Prompt before sending invites to potentially invalid matrix IDs": "Pedir confirmación antes de enviar invitaciones a IDs de matrix que parezcan inválidas", "Prompt before sending invites to potentially invalid matrix IDs": "Pedir confirmación antes de enviar invitaciones a IDs de matrix que parezcan inválidas",
"Show developer tools": "Mostrar herramientas de desarrollador", "Show developer tools": "Mostrar herramientas de desarrollo",
"Messages containing my username": "Mensajes que contengan mi nombre", "Messages containing my username": "Mensajes que contengan mi nombre",
"Messages containing @room": "Mensajes que contengan @room", "Messages containing @room": "Mensajes que contengan @room",
"Encrypted messages in one-to-one chats": "Mensajes cifrados en salas uno a uno", "Encrypted messages in one-to-one chats": "Mensajes cifrados en salas uno a uno",
@ -1091,8 +1091,8 @@
"Enable Community Filter Panel": "Activar el panel de filtro de comunidad", "Enable Community Filter Panel": "Activar el panel de filtro de comunidad",
"Verify this user by confirming the following emoji appear on their screen.": "Verifica este usuario confirmando que los siguientes emojis aparecen en su pantalla.", "Verify this user by confirming the following emoji appear on their screen.": "Verifica este usuario confirmando que los siguientes emojis aparecen en su pantalla.",
"Your %(brand)s is misconfigured": "Tu %(brand)s tiene un error de configuración", "Your %(brand)s is misconfigured": "Tu %(brand)s tiene un error de configuración",
"Whether or not you're logged in (we don't record your username)": "Hayas o no iniciado sesión (no guardamos tu nombre de usuario)", "Whether or not you're logged in (we don't record your username)": "Si has iniciado sesión o no (no guardamos tu nombre de usuario)",
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Uses o no las «migas de pan» (iconos sobre la lista de salas)", "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Si estás usando o no las «migas de pan» (iconos sobre la lista de salas)",
"Replying With Files": "Respondiendo con archivos", "Replying With Files": "Respondiendo con archivos",
"At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "En este momento no es posible responder con un archivo. ¿Te gustaría subir el archivo sin responder?", "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "En este momento no es posible responder con un archivo. ¿Te gustaría subir el archivo sin responder?",
"The file '%(fileName)s' failed to upload.": "La subida del archivo «%(fileName)s ha fallado.", "The file '%(fileName)s' failed to upload.": "La subida del archivo «%(fileName)s ha fallado.",
@ -1260,8 +1260,8 @@
"Upgrade private room": "Actualizar sala privada", "Upgrade private room": "Actualizar sala privada",
"Upgrade public room": "Actualizar sala pública", "Upgrade public room": "Actualizar sala pública",
"Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Actualizar una sala es una acción avanzada y es normalmente recomendada cuando una sala es inestable debido a fallos, funcionalidades no disponibles y vulnerabilidades.", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Actualizar una sala es una acción avanzada y es normalmente recomendada cuando una sala es inestable debido a fallos, funcionalidades no disponibles y vulnerabilidades.",
"This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please <a>report a bug</a>.": "Esto solo afecta a como la sala es procesada en el servidor. Si estás teniendo problemas con tu %(brand)s, por favor<a>reporta un fallo</a>.", "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please <a>report a bug</a>.": "Esto solo afecta a cómo procesa la sala el servidor. Si estás teniendo problemas con %(brand)s, por favor, <a>avísanos del fallo</a>.",
"You'll upgrade this room from <oldVersion /> to <newVersion />.": "Actualizarás esta sala de <oldVersion /> a <newVersion />.", "You'll upgrade this room from <oldVersion /> to <newVersion />.": "Actualizarás esta sala de la versión <oldVersion /> a la <newVersion />.",
"Sign out and remove encryption keys?": "¿Salir y borrar las claves de cifrado?", "Sign out and remove encryption keys?": "¿Salir y borrar las claves de cifrado?",
"A username can only contain lower case letters, numbers and '=_-./'": "Un nombre de usuario solo puede contener letras minúsculas, números y '=_-./'", "A username can only contain lower case letters, numbers and '=_-./'": "Un nombre de usuario solo puede contener letras minúsculas, números y '=_-./'",
"Checking...": "Comprobando...", "Checking...": "Comprobando...",
@ -1422,7 +1422,7 @@
"Disconnect from the identity server <idserver />?": "¿Desconectarse del servidor de identidad <idserver />?", "Disconnect from the identity server <idserver />?": "¿Desconectarse del servidor de identidad <idserver />?",
"Disconnect": "Desconectarse", "Disconnect": "Desconectarse",
"You should:": "Deberías:", "You should:": "Deberías:",
"Use Single Sign On to continue": "Continuar con SSO", "Use Single Sign On to continue": "Continuar con registro único (SSO)",
"Confirm adding this email address by using Single Sign On to prove your identity.": "Confirma la nueva dirección de correo usando SSO para probar tu identidad.", "Confirm adding this email address by using Single Sign On to prove your identity.": "Confirma la nueva dirección de correo usando SSO para probar tu identidad.",
"Single Sign On": "Single Sign On", "Single Sign On": "Single Sign On",
"Confirm adding email": "Confirmar un nuevo correo electrónico", "Confirm adding email": "Confirmar un nuevo correo electrónico",
@ -1441,7 +1441,7 @@
"Use your account or create a new one to continue.": "Usa tu cuenta existente o crea una nueva para continuar.", "Use your account or create a new one to continue.": "Usa tu cuenta existente o crea una nueva para continuar.",
"Create Account": "Crear cuenta", "Create Account": "Crear cuenta",
"Sign In": "Iniciar sesión", "Sign In": "Iniciar sesión",
"Sends a message as html, without interpreting it as markdown": "Envía un mensaje como html, sin interpretarlo en markdown", "Sends a message as html, without interpreting it as markdown": "Envía un mensaje como HTML, sin interpretarlo en Markdown",
"Failed to set topic": "No se ha podido cambiar el tema", "Failed to set topic": "No se ha podido cambiar el tema",
"Command failed": "El comando falló", "Command failed": "El comando falló",
"Could not find user in room": "No se ha encontrado el usuario en la sala", "Could not find user in room": "No se ha encontrado el usuario en la sala",
@ -1481,7 +1481,7 @@
"Verify the new login accessing your account: %(name)s": "Verifica el nuevo inicio de sesión que está accediendo a tu cuenta: %(name)s", "Verify the new login accessing your account: %(name)s": "Verifica el nuevo inicio de sesión que está accediendo a tu cuenta: %(name)s",
"From %(deviceName)s (%(deviceId)s)": "De %(deviceName)s (%(deviceId)s)", "From %(deviceName)s (%(deviceId)s)": "De %(deviceName)s (%(deviceId)s)",
"This bridge was provisioned by <user />.": "Este puente fue aportado por <user />.", "This bridge was provisioned by <user />.": "Este puente fue aportado por <user />.",
"This bridge is managed by <user />.": "Este puente es administrado por <user />.", "This bridge is managed by <user />.": "Este puente lo gestiona <user />.",
"Your homeserver does not support cross-signing.": "Tu servidor base no soporta las firmas cruzadas.", "Your homeserver does not support cross-signing.": "Tu servidor base no soporta las firmas cruzadas.",
"Cross-signing and secret storage are enabled.": "La firma cruzada y el almacenamiento secreto están activados.", "Cross-signing and secret storage are enabled.": "La firma cruzada y el almacenamiento secreto están activados.",
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Su cuenta tiene una identidad de firma cruzada en un almacenamiento secreto, pero aún no es confiada en esta sesión.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Su cuenta tiene una identidad de firma cruzada en un almacenamiento secreto, pero aún no es confiada en esta sesión.",
@ -1693,7 +1693,7 @@
"Try to join anyway": "Intentar unirse de todas formas", "Try to join anyway": "Intentar unirse de todas formas",
"You can still join it because this is a public room.": "Todavía puedes unirte, ya que es una sala pública.", "You can still join it because this is a public room.": "Todavía puedes unirte, ya que es una sala pública.",
"Join the discussion": "Unirme a la Sala", "Join the discussion": "Unirme a la Sala",
"Do you want to chat with %(user)s?": "¿Quieres chatear con %(user)s?", "Do you want to chat with %(user)s?": "¿Quieres empezar una conversación con %(user)s?",
"Do you want to join %(roomName)s?": "¿Quieres unirte a %(roomName)s?", "Do you want to join %(roomName)s?": "¿Quieres unirte a %(roomName)s?",
"<userName/> invited you": "<userName/> te ha invitado", "<userName/> invited you": "<userName/> te ha invitado",
"You're previewing %(roomName)s. Want to join it?": "Esto es una vista previa de %(roomName)s. ¿Te quieres unir?", "You're previewing %(roomName)s. Want to join it?": "Esto es una vista previa de %(roomName)s. ¿Te quieres unir?",
@ -1738,8 +1738,8 @@
"This invite to %(roomName)s was sent to %(email)s": "Esta invitación a %(roomName)s fue enviada a %(email)s", "This invite to %(roomName)s was sent to %(email)s": "Esta invitación a %(roomName)s fue enviada a %(email)s",
"Use an identity server in Settings to receive invites directly in %(brand)s.": "Utilice un servidor de identidad en Configuración para recibir invitaciones directamente en %(brand)s.", "Use an identity server in Settings to receive invites directly in %(brand)s.": "Utilice un servidor de identidad en Configuración para recibir invitaciones directamente en %(brand)s.",
"Share this email in Settings to receive invites directly in %(brand)s.": "Comparte este correo electrónico en Configuración para recibir invitaciones directamente en %(brand)s.", "Share this email in Settings to receive invites directly in %(brand)s.": "Comparte este correo electrónico en Configuración para recibir invitaciones directamente en %(brand)s.",
"<userName/> wants to chat": "<userName/> quiere chatear", "<userName/> wants to chat": "<userName/> quiere mandarte mensajes",
"Start chatting": "Empieza una conversación", "Start chatting": "Empezar una conversación",
"Reject & Ignore user": "Rechazar e ignorar usuario", "Reject & Ignore user": "Rechazar e ignorar usuario",
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Actualizar esta sala cerrará la instancia actual de la sala y creará una sala actualizada con el mismo nombre.", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Actualizar esta sala cerrará la instancia actual de la sala y creará una sala actualizada con el mismo nombre.",
"This room has already been upgraded.": "Esta sala ya ha sido actualizada.", "This room has already been upgraded.": "Esta sala ya ha sido actualizada.",
@ -2081,7 +2081,7 @@
"Joins room with given address": "Entrar a la sala con la dirección especificada", "Joins room with given address": "Entrar a la sala con la dirección especificada",
"Unrecognised room address:": "No se encuentra la dirección de la sala:", "Unrecognised room address:": "No se encuentra la dirección de la sala:",
"Opens chat with the given user": "Abrir una conversación con el usuario especificado", "Opens chat with the given user": "Abrir una conversación con el usuario especificado",
"Sends a message to the given user": "Enviar un mensaje al usuario especificado", "Sends a message to the given user": "Enviar un mensaje al usuario seleccionado",
"Light": "Claro", "Light": "Claro",
"Dark": "Oscuro", "Dark": "Oscuro",
"Unexpected server error trying to leave the room": "Error inesperado del servidor al abandonar esta sala", "Unexpected server error trying to leave the room": "Error inesperado del servidor al abandonar esta sala",
@ -2423,7 +2423,7 @@
"Navigate composer history": "Navegar por el historial del editor", "Navigate composer history": "Navegar por el historial del editor",
"Cancel replying to a message": "Cancelar responder al mensaje", "Cancel replying to a message": "Cancelar responder al mensaje",
"Toggle microphone mute": "Alternar silencio del micrófono", "Toggle microphone mute": "Alternar silencio del micrófono",
"Toggle video on/off": "Activar/desactivar video", "Toggle video on/off": "Activar/desactivar vídeo",
"Scroll up/down in the timeline": "Desplazarse hacia arriba o hacia abajo en la línea de tiempo", "Scroll up/down in the timeline": "Desplazarse hacia arriba o hacia abajo en la línea de tiempo",
"Dismiss read marker and jump to bottom": "Descartar el marcador de lectura y saltar al final", "Dismiss read marker and jump to bottom": "Descartar el marcador de lectura y saltar al final",
"Jump to oldest unread message": "Ir al mensaje no leído más antiguo", "Jump to oldest unread message": "Ir al mensaje no leído más antiguo",
@ -2452,7 +2452,7 @@
"This version of %(brand)s does not support searching encrypted messages": "Esta versión de %(brand)s no puede buscar mensajes cifrados", "This version of %(brand)s does not support searching encrypted messages": "Esta versión de %(brand)s no puede buscar mensajes cifrados",
"Video conference ended by %(senderName)s": "Videoconferencia terminada por %(senderName)s", "Video conference ended by %(senderName)s": "Videoconferencia terminada por %(senderName)s",
"Join the conference from the room information card on the right": "Únete a la conferencia desde el panel de información de la sala de la derecha", "Join the conference from the room information card on the right": "Únete a la conferencia desde el panel de información de la sala de la derecha",
"Join the conference at the top of this room": "Unirse a la conferencia en la parte de arriba de la sala", "Join the conference at the top of this room": "Únete a la conferencia en la parte de arriba de la sala",
"Ignored attempt to disable encryption": "Se ha ignorado un intento de desactivar el cifrado", "Ignored attempt to disable encryption": "Se ha ignorado un intento de desactivar el cifrado",
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Los mensajes en esta sala están cifrados de extremo a extremo. Cuando alguien se una podrás verificarle en su perfil, tan solo pulsa en su imagen.", "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Los mensajes en esta sala están cifrados de extremo a extremo. Cuando alguien se una podrás verificarle en su perfil, tan solo pulsa en su imagen.",
"Add widgets, bridges & bots": "Añadir widgets, puentes y bots", "Add widgets, bridges & bots": "Añadir widgets, puentes y bots",
@ -3248,7 +3248,7 @@
"%(seconds)ss left": "%(seconds)ss restantes", "%(seconds)ss left": "%(seconds)ss restantes",
"Failed to send": "No se ha podido mandar", "Failed to send": "No se ha podido mandar",
"Change server ACLs": "Cambiar los ACLs del servidor", "Change server ACLs": "Cambiar los ACLs del servidor",
"Show options to enable 'Do not disturb' mode": "Mostrar opciones para activar el modo «no molestar»", "Show options to enable 'Do not disturb' mode": "Ver opciones para activar el modo «no molestar»",
"Stop the recording": "Parar grabación", "Stop the recording": "Parar grabación",
"Delete recording": "Borrar grabación", "Delete recording": "Borrar grabación",
"Enter your Security Phrase a second time to confirm it.": "Escribe tu frase de seguridad de nuevo para confirmarla.", "Enter your Security Phrase a second time to confirm it.": "Escribe tu frase de seguridad de nuevo para confirmarla.",
@ -3311,7 +3311,7 @@
"sends space invaders": "enviar space invaders", "sends space invaders": "enviar space invaders",
"Sends the given message with a space themed effect": "Envía un mensaje con efectos espaciales", "Sends the given message with a space themed effect": "Envía un mensaje con efectos espaciales",
"If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Si sales, %(brand)s volverá a cargarse con los espacios desactivados. Las comunidades y las etiquetas personalizadas serán visibles de nuevo.", "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Si sales, %(brand)s volverá a cargarse con los espacios desactivados. Las comunidades y las etiquetas personalizadas serán visibles de nuevo.",
"Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Permitir conexión directa (peer-to-peer) en las llamadas individuales (si lo activas, la otra parte podría ver tu dirección IP)", "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Permitir conexión directa (peer-to-peer) en las llamadas individuales (si lo activas, la otra persona podría ver tu dirección IP)",
"See when people join, leave, or are invited to your active room": "Ver cuando alguien se una, salga o se le invite a tu sala activa", "See when people join, leave, or are invited to your active room": "Ver cuando alguien se una, salga o se le invite a tu sala activa",
"Kick, ban, or invite people to this room, and make you leave": "Expulsar, vetar o invitar personas a esta sala, y hacerte salir de ella", "Kick, ban, or invite people to this room, and make you leave": "Expulsar, vetar o invitar personas a esta sala, y hacerte salir de ella",
"Kick, ban, or invite people to your active room, and make you leave": "Expulsar, vetar o invitar a gente a tu sala activa, o hacerte salir", "Kick, ban, or invite people to your active room, and make you leave": "Expulsar, vetar o invitar a gente a tu sala activa, o hacerte salir",
@ -3347,7 +3347,7 @@
"%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s ha echado a %(targetName)s: %(reason)s", "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s ha echado a %(targetName)s: %(reason)s",
"Disagree": "No estoy de acuerdo", "Disagree": "No estoy de acuerdo",
"[number]": "[número]", "[number]": "[número]",
"To view %(spaceName)s, you need an invite": "Para ver %(spaceName)s, necesitas que te inviten.", "To view %(spaceName)s, you need an invite": "Para ver %(spaceName)s, necesitas que te inviten",
"You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Haz clic sobre una imagen en el panel de filtro para ver solo las salas y personas asociadas con una comunidad.", "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Haz clic sobre una imagen en el panel de filtro para ver solo las salas y personas asociadas con una comunidad.",
"Move down": "Bajar", "Move down": "Bajar",
"Move up": "Subir", "Move up": "Subir",
@ -3374,7 +3374,7 @@
"%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s ha cambiado los permisos del servidor %(count)s veces", "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s ha cambiado los permisos del servidor %(count)s veces",
"%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s ha cambiado los permisos del servidor", "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s ha cambiado los permisos del servidor",
"%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s ha cambiado los permisos del servidor %(count)s veces", "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s ha cambiado los permisos del servidor %(count)s veces",
"Message search initialisation failed, check <a>your settings</a> for more information": "Ha fallado el sistema de búsqueda de mensajes. Comprueba <a>tus ajustes</a> para más información.", "Message search initialisation failed, check <a>your settings</a> for more information": "Ha fallado el sistema de búsqueda de mensajes. Comprueba <a>tus ajustes</a> para más información",
"Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Elige una dirección para este espacio y los usuarios de tu servidor base (%(localDomain)s) podrán encontrarlo a través del buscador", "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Elige una dirección para este espacio y los usuarios de tu servidor base (%(localDomain)s) podrán encontrarlo a través del buscador",
"To publish an address, it needs to be set as a local address first.": "Para publicar una dirección, primero debe ser añadida como dirección local.", "To publish an address, it needs to be set as a local address first.": "Para publicar una dirección, primero debe ser añadida como dirección local.",
"Published addresses can be used by anyone on any server to join your room.": "Las direcciones publicadas pueden usarse por cualquiera para unirse a tu sala, independientemente de su servidor base.", "Published addresses can be used by anyone on any server to join your room.": "Las direcciones publicadas pueden usarse por cualquiera para unirse a tu sala, independientemente de su servidor base.",
@ -3404,7 +3404,7 @@
"If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "Si lo desactivas, todavía podrás añadir mensajes directos a tus espacios personales. Si lo activas, aparecerá todo el mundo que pertenezca al espacio.", "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "Si lo desactivas, todavía podrás añadir mensajes directos a tus espacios personales. Si lo activas, aparecerá todo el mundo que pertenezca al espacio.",
"Show people in spaces": "Mostrar gente en los espacios", "Show people in spaces": "Mostrar gente en los espacios",
"Show all rooms in Home": "Mostrar todas las salas en la pantalla de inicio", "Show all rooms in Home": "Mostrar todas las salas en la pantalla de inicio",
"Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototipo de reportes a los moderadores. En las salas que lo permitan, verás el botón «reportar», que te permitirá avisar de mensajes abusivos a los moderadores de la sala.", "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototipo de reportes a los moderadores. En las salas que lo permitan, verás el botón «reportar», que te permitirá avisar de mensajes abusivos a los moderadores de la sala",
"%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s ha anulado la invitación a %(targetName)s", "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s ha anulado la invitación a %(targetName)s",
"%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s ha anulado la invitación a %(targetName)s: %(reason)s", "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s ha anulado la invitación a %(targetName)s: %(reason)s",
"%(targetName)s left the room": "%(targetName)s ha salido de la sala", "%(targetName)s left the room": "%(targetName)s ha salido de la sala",
@ -3422,7 +3422,7 @@
"We sent the others, but the below people couldn't be invited to <RoomName/>": "Hemos enviado el resto, pero no hemos podido invitar las siguientes personas a la sala <RoomName/>", "We sent the others, but the below people couldn't be invited to <RoomName/>": "Hemos enviado el resto, pero no hemos podido invitar las siguientes personas a la sala <RoomName/>",
"Some invites couldn't be sent": "No se han podido enviar algunas invitaciones", "Some invites couldn't be sent": "No se han podido enviar algunas invitaciones",
"Integration manager": "Gestor de integración", "Integration manager": "Gestor de integración",
"Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "%(brand)s no utilizar un \"gestor de integración\" para hacer esto. Por favor, contacta con un administrador.", "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Tu aplicación %(brand)s no te permite usar un gestor de integración para hacer esto. Por favor, contacta con un administrador.",
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your integration manager.": "Al usar este widget puede que se compartan datos <helpIcon /> con %(widgetDomain)s y tu gestor de integraciones.", "Using this widget may share data <helpIcon /> with %(widgetDomain)s & your integration manager.": "Al usar este widget puede que se compartan datos <helpIcon /> con %(widgetDomain)s y tu gestor de integraciones.",
"Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Los gestores de integraciones reciben datos de configuración, y pueden modificar widgets, enviar invitaciones de sala, y establecer niveles de poder en tu nombre.", "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Los gestores de integraciones reciben datos de configuración, y pueden modificar widgets, enviar invitaciones de sala, y establecer niveles de poder en tu nombre.",
"Use an integration manager to manage bots, widgets, and sticker packs.": "Usa un gestor de integraciones para bots, widgets y paquetes de pegatinas.", "Use an integration manager to manage bots, widgets, and sticker packs.": "Usa un gestor de integraciones para bots, widgets y paquetes de pegatinas.",
@ -3445,7 +3445,7 @@
"To view all keyboard shortcuts, click here.": "Para ver todos los atajos de teclado, haz clic aquí.", "To view all keyboard shortcuts, click here.": "Para ver todos los atajos de teclado, haz clic aquí.",
"Keyboard shortcuts": "Atajos de teclado", "Keyboard shortcuts": "Atajos de teclado",
"Identity server is": "El servidor de identidad es", "Identity server is": "El servidor de identidad es",
"There was an error loading your notification settings.": "Ha ocurrido un error al cargar tus ajustes de notificaciones", "There was an error loading your notification settings.": "Ha ocurrido un error al cargar tus ajustes de notificaciones.",
"Mentions & keywords": "Menciones y palabras clave", "Mentions & keywords": "Menciones y palabras clave",
"Global": "Global", "Global": "Global",
"New keyword": "Nueva palabra clave", "New keyword": "Nueva palabra clave",
@ -3465,7 +3465,7 @@
"Image": "Imagen", "Image": "Imagen",
"Sticker": "Pegatina", "Sticker": "Pegatina",
"Downloading": "Descargando", "Downloading": "Descargando",
"The call is in an unknown state!": "La llamada está en un estado desconocido", "The call is in an unknown state!": "¡La llamada está en un estado desconocido!",
"Call back": "Devolver", "Call back": "Devolver",
"You missed this call": "No has cogido esta llamada", "You missed this call": "No has cogido esta llamada",
"This call has failed": "Esta llamada ha fallado", "This call has failed": "Esta llamada ha fallado",
@ -3479,10 +3479,10 @@
"IRC": "IRC", "IRC": "IRC",
"Use Ctrl + F to search timeline": "Usa Control + F para buscar dentro de la conversación", "Use Ctrl + F to search timeline": "Usa Control + F para buscar dentro de la conversación",
"<b>Please note upgrading will make a new version of the room</b>. All current messages will stay in this archived room.": "<b>Ten en cuenta que actualizar crea una nueva versión de la sala</b>. Todos los mensajes hasta ahora quedarán archivados aquí, en esta sala.", "<b>Please note upgrading will make a new version of the room</b>. All current messages will stay in this archived room.": "<b>Ten en cuenta que actualizar crea una nueva versión de la sala</b>. Todos los mensajes hasta ahora quedarán archivados aquí, en esta sala.",
"Automatically invite members from this room to the new one": "Invitar a la nueva sala automáticamente miembros de esta", "Automatically invite members from this room to the new one": "Invitar a la nueva sala automáticamente a los miembros que tiene ahora",
"These are likely ones other room admins are a part of.": "Otros administradores de la sala estarán dentro.", "These are likely ones other room admins are a part of.": "Otros administradores de la sala estarán dentro.",
"Other spaces or rooms you might not know": "Otros espacios o salas que puede que no conozcas", "Other spaces or rooms you might not know": "Otros espacios o salas que puede que no conozcas",
"Decide which spaces can access this room. If a space is selected, its members can find and join <RoomName/>.": "Decide qué espacios pueden acceder a esta sala. Si seleccionas un espacio, sus miembros podrán encontrar y unirse a <RoomName/>.", "Decide which spaces can access this room. If a space is selected, its members can find and join <RoomName/>.": "Decide qué espacios tienen acceso a esta sala. Si seleccionas un espacio, sus miembros podrán encontrar y unirse a <RoomName/>.",
"You're removing all spaces. Access will default to invite only": "Al quitar todos los espacios, el acceso por defecto pasará a ser «solo por invitación»", "You're removing all spaces. Access will default to invite only": "Al quitar todos los espacios, el acceso por defecto pasará a ser «solo por invitación»",
"Only people invited will be able to find and join this space.": "Solo las personas invitadas podrán encontrar y unirse a este espacio.", "Only people invited will be able to find and join this space.": "Solo las personas invitadas podrán encontrar y unirse a este espacio.",
"Anyone will be able to find and join this space, not just members of <SpaceName/>.": "Cualquiera podrá encontrar y unirse a este espacio, incluso si no forman parte de <SpaceName/>.", "Anyone will be able to find and join this space, not just members of <SpaceName/>.": "Cualquiera podrá encontrar y unirse a este espacio, incluso si no forman parte de <SpaceName/>.",
@ -3498,14 +3498,14 @@
"Anyone in a space can find and join. You can select multiple spaces.": "Cualquiera en un espacio puede encontrar y unirse. Puedes seleccionar varios espacios.", "Anyone in a space can find and join. You can select multiple spaces.": "Cualquiera en un espacio puede encontrar y unirse. Puedes seleccionar varios espacios.",
"Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Cualquiera en %(spaceName)s puede encontrar y unirse. También puedes seleccionar otros espacios.", "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Cualquiera en %(spaceName)s puede encontrar y unirse. También puedes seleccionar otros espacios.",
"Spaces with access": "Espacios con acceso", "Spaces with access": "Espacios con acceso",
"Anyone in a space can find and join. <a>Edit which spaces can access here.</a>": "Cualquiera en un espacio puede encontrar y unirse. <a>Ajusta qué espacios pueden acceder desde aquí.", "Anyone in a space can find and join. <a>Edit which spaces can access here.</a>": "Cualquiera en un espacio puede encontrar y unirse. <a>Ajusta qué espacios pueden acceder desde aquí.</a>",
"Currently, %(count)s spaces have access|other": "Ahora mismo, %(count)s espacios tienen acceso", "Currently, %(count)s spaces have access|other": "Ahora mismo, %(count)s espacios tienen acceso",
"& %(count)s more|other": "y %(count)s más", "& %(count)s more|other": "y %(count)s más",
"Upgrade required": "Actualización necesaria", "Upgrade required": "Actualización necesaria",
"Anyone can find and join.": "Cualquiera puede encontrar y unirse.", "Anyone can find and join.": "Cualquiera puede encontrar y unirse.",
"Only invited people can join.": "Solo las personas invitadas pueden unirse.", "Only invited people can join.": "Solo las personas invitadas pueden unirse.",
"Private (invite only)": "Privado (solo por invitación)", "Private (invite only)": "Privado (solo por invitación)",
"This upgrade will allow members of selected spaces access to this room without an invite.": "Esta actualización permitirá a los miembros de los espacios que elijas acceder a esta sala sin que tengas que invitarles.", "This upgrade will allow members of selected spaces access to this room without an invite.": "Si actualizas, podrás configurar la sala para que los miembros de los espacios que elijas puedan unirse sin que tengas que invitarles.",
"Message bubbles": "Burbujas de mensaje", "Message bubbles": "Burbujas de mensaje",
"Show all rooms": "Ver todas las salas", "Show all rooms": "Ver todas las salas",
"Give feedback.": "Danos tu opinión.", "Give feedback.": "Danos tu opinión.",
@ -3597,5 +3597,46 @@
"You can also create a Space from a <a>community</a>.": "También puedes crear un espacio a partir de una <a>comunidad</a>.", "You can also create a Space from a <a>community</a>.": "También puedes crear un espacio a partir de una <a>comunidad</a>.",
"You can change this later.": "Puedes cambiarlo más tarde.", "You can change this later.": "Puedes cambiarlo más tarde.",
"What kind of Space do you want to create?": "¿Qué tipo de espacio quieres crear?", "What kind of Space do you want to create?": "¿Qué tipo de espacio quieres crear?",
"Don't send read receipts": "No enviar confirmaciones de lectura" "Don't send read receipts": "No enviar confirmaciones de lectura",
"To view Spaces, hide communities in <a>Preferences</a>": "Para ver espacios, oculta las comunidades en <a>ajustes</a>",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>No está recomendado activar el cifrado en salas públicas.</b> Cualquiera puede encontrar la sala y unirse, por lo que cualquiera puede leer los mensajes. No disfrutarás de los beneficios del cifrado. Además, activarlo en una sala pública hará que recibir y enviar mensajes tarde más.",
"Communities have been archived to make way for Spaces but you can convert your communities into Spaces below. Converting will ensure your conversations get the latest features.": "Las comunidades han sido archivadas para dar paso a los espacios, pero puedes convertir tus comunidades a espacios debajo. Al convertirlas, te aseguras de que tus conversaciones tienen acceso a las últimas funcionalidades.",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Si nos has avisado de un fallo a través de Github, los registros de depuración nos pueden ayudar a encontrar más fácil el problema. Los registros incluyen datos de uso de la aplicación incluyendo tu nombre de usuario, las IDs o nombres de las salas o grupos que has visitado, los elementos de la interfaz con los que hayas interactuado recientemente, y los nombres de usuario de otras personas. No contienen mensajes.",
"Delete avatar": "Borrar avatar",
"Flair won't be available in Spaces for the foreseeable future.": "Por ahora no está previsto que las insignias estén disponibles en los espacios.",
"Ask the <a>admins</a> of this community to make it into a Space and keep a look out for the invite.": "Pídele a los <a>admins</a> que conviertan la comunidad en un espacio y espera a que te inviten.",
"You can create a Space from this community <a>here</a>.": "Puedes crear un espacio a partir de esta comunidad desde <a>aquí</a>.",
"To create a Space from another community, just pick the community in Preferences.": "Para crear un espacio a partir de otra comunidad, escoge la comunidad en ajustes.",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Los registros de depuración contienen datos de uso de la aplicación como tu nombre de usuario, las IDs o los nombres de las salas o grupos que has visitado, con qué elementos de la interfaz has interactuado recientemente, y nombres de usuario de otras personas. No incluyen mensajes.",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Para evitar estos problemas, crea una <a>nueva sala pública</a> para la conversación que planees tener.",
"%(severalUsers)schanged the <a>pinned messages</a> for the room %(count)s times.|other": "%(severalUsers)s han cambiado los <a>mensajes anclados</a> de la sala %(count)s veces.",
"%(oneUser)schanged the <a>pinned messages</a> for the room %(count)s times.|other": "%(oneUser)s han cambiado los <a>mensajes anclados</a> de la sala %(count)s veces.",
"Cross-signing is ready but keys are not backed up.": "La firma cruzada está lista, pero no hay copia de seguridad de las claves.",
"Rooms and spaces": "Salas y espacios",
"Results": "Resultados",
"Communities won't receive further updates.": "Las comunidades no van a recibir actualizaciones.",
"Spaces are a new way to make a community, with new features coming.": "Los espacios son una nueva manera de formar una comunidad, con nuevas funcionalidades próximamente.",
"Communities can now be made into Spaces": "Ahora puedes convertir comunidades en espacios",
"This description will be shown to people when they view your space": "Esta descripción aparecerá cuando alguien vea tu espacio",
"All rooms will be added and all community members will be invited.": "Todas las salas se añadirán, e invitaremos a todos los miembros de la comunidad.",
"A link to the Space will be put in your community description.": "Pondremos un enlace al espacio en la descripción de tu comunidad.",
"Create Space from community": "Crear espacio a partir de una comunidad",
"Failed to migrate community": "Fallo al migrar la comunidad",
"<SpaceName/> has been made and everyone who was a part of the community has been invited to it.": "<SpaceName/> ha sido creado, y cualquier persona que formara parte ha sido invitada.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Tus mensajes privados están cifrados normalmente, pero esta sala no lo está. A menudo, esto pasa porque has iniciado sesión con un dispositivo o método no compatible, como las invitaciones por correo.",
"Enable encryption in settings.": "Activa el cifrado en los ajustes.",
"Are you sure you want to make this encrypted room public?": "¿Seguro que quieres activar el cifrado en esta sala pública?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Para evitar estos problemas, crea una <a>nueva sala cifrada</a> para la conversación que quieras tener.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>No recomendamos activar el cifrado para salas públicas.</b>Cualquiera puede encontrar y unirse a una sala pública, por lo también puede leer los mensajes. No aprovecharás ningún beneficio del cifrado, y no podrás desactivarlo en el futuro. Cifrar mensajes en una sala pública hará que tarden más en enviarse y recibirse.",
"If a community isn't shown you may not have permission to convert it.": "Si echas en falta alguna comunidad, es posible que no tengas permisos suficientes para convertirla.",
"Are you sure you want to add encryption to this public room?": "¿Seguro que quieres activar el cifrado en esta sala pública?",
"Low bandwidth mode (requires compatible homeserver)": "Modo de bajo consumo de datos (el servidor base debe ser compatible)",
"Multiple integration managers (requires manual setup)": "Varios gestores de integración (hay que configurarlos manualmente)",
"Threaded messaging": "Mensajes en hilos",
"Show threads": "Ver hilos",
"Thread": "Hilo",
"The above, but in any room you are joined or invited to as well": "Lo de arriba, pero en cualquier sala en la que estés o te inviten",
"The above, but in <Room /> as well": "Lo de arriba, pero también en <Room />",
"Autoplay videos": "Reproducir automáticamente los vídeos",
"Autoplay GIFs": "Reproducir automáticamente los GIFs"
} }

View file

@ -2232,7 +2232,7 @@
"For help with using %(brand)s, click <a>here</a>.": "Kui otsid lisateavet %(brand)s'i kasutamise kohta, palun vaata <a>siia</a>.", "For help with using %(brand)s, click <a>here</a>.": "Kui otsid lisateavet %(brand)s'i kasutamise kohta, palun vaata <a>siia</a>.",
"Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "Lisa siia kasutajad ja serverid, mida sa soovid eirata. Kui soovid, et %(brand)s kasutaks üldist asendamist, siis kasuta tärni. Näiteks <code>@bot:*</code> eirab kõikide serverite kasutajat 'bot'.", "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "Lisa siia kasutajad ja serverid, mida sa soovid eirata. Kui soovid, et %(brand)s kasutaks üldist asendamist, siis kasuta tärni. Näiteks <code>@bot:*</code> eirab kõikide serverite kasutajat 'bot'.",
"Use default": "Kasuta vaikimisi väärtusi", "Use default": "Kasuta vaikimisi väärtusi",
"Mentions & Keywords": "Mainimised ja võtmesõnad", "Mentions & Keywords": "Mainimised ja märksõnad",
"Notification options": "Teavituste eelistused", "Notification options": "Teavituste eelistused",
"Room options": "Jututoa eelistused", "Room options": "Jututoa eelistused",
"This room is public": "See jututuba on avalik", "This room is public": "See jututuba on avalik",
@ -3543,7 +3543,7 @@
"Spaces feedback": "Tagasiside kogukonnakeskuste kohta", "Spaces feedback": "Tagasiside kogukonnakeskuste kohta",
"Give feedback.": "Jaga tagasisidet.", "Give feedback.": "Jaga tagasisidet.",
"We're working on this, but just want to let you know.": "Me küll alles arendame seda võimalust, kuid soovisime, et tead, mis tulemas on.", "We're working on this, but just want to let you know.": "Me küll alles arendame seda võimalust, kuid soovisime, et tead, mis tulemas on.",
"All rooms you're in will appear in Home.": "Kõik sinu jututoad on nähtavad kodulehel.", "All rooms you're in will appear in Home.": "Kõik sinu jututoad on nähtavad avalehel.",
"Show all rooms": "Näita kõiki jututubasid", "Show all rooms": "Näita kõiki jututubasid",
"Leave all rooms and spaces": "Lahku kõikidest jututubadest ja kogukondadest", "Leave all rooms and spaces": "Lahku kõikidest jututubadest ja kogukondadest",
"Don't leave any": "Ära lahku ühestki", "Don't leave any": "Ära lahku ühestki",
@ -3614,12 +3614,65 @@
"Flair won't be available in Spaces for the foreseeable future.": "Me ei plaani lähitulevikus pakkuda kogukondades rinnamärkide funktsionaalsust.", "Flair won't be available in Spaces for the foreseeable future.": "Me ei plaani lähitulevikus pakkuda kogukondades rinnamärkide funktsionaalsust.",
"This description will be shown to people when they view your space": "Seda kirjeldust kuvame kõigile, kes vaatavad sinu kogukonda", "This description will be shown to people when they view your space": "Seda kirjeldust kuvame kõigile, kes vaatavad sinu kogukonda",
"Unknown failure: %(reason)s": "Tundmatu viga: %(reason)s", "Unknown failure: %(reason)s": "Tundmatu viga: %(reason)s",
"Help space members find private rooms": "Aita", "Help space members find private rooms": "Aita kogukonnakeskuse liitmetel leida privaatseid jututube",
"New in the Spaces beta": "Mida", "New in the Spaces beta": "Mida",
"We sent the others, but the below people couldn't be invited to <RoomName/>": "Teised kasutajad said kutse, kuid allpool toodud kasutajatele ei õnnestunud saata kutset <RoomName/> jututuppa", "We sent the others, but the below people couldn't be invited to <RoomName/>": "Teised kasutajad said kutse, kuid allpool toodud kasutajatele ei õnnestunud saata kutset <RoomName/> jututuppa",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Kui sa oled teatanud meile GitHub'i vahendusel veast, siis silumislogid aitavad meil seda viga kergemini parandada. Vigadega seotud logid sisaldavad rakenduse teavet, sealhulgas sinu kasutajanime, külastatud jututubade kasutajatunnuseid või aliasi, viimatikasutatud liidese funktsionaalsusi ning teiste kasutajate kasutajanimesid. Logides ei ole saadetud sõnumite sisu.", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Kui sa oled teatanud meile GitHub'i vahendusel veast, siis silumislogid aitavad meil seda viga kergemini parandada. Vigadega seotud logid sisaldavad rakenduse teavet, sealhulgas sinu kasutajanime, külastatud jututubade kasutajatunnuseid või aliasi, viimatikasutatud liidese funktsionaalsusi ning teiste kasutajate kasutajanimesid. Logides ei ole saadetud sõnumite sisu.",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Vigadega seotud logid sisaldavad rakenduse teavet, sealhulgas sinu kasutajanime, külastatud jututubade kasutajatunnuseid või aliasi, viimatikasutatud liidese funktsionaalsusi ning teiste kasutajate kasutajanimesid. Logides ei ole saadetud sõnumite sisu.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Vigadega seotud logid sisaldavad rakenduse teavet, sealhulgas sinu kasutajanime, külastatud jututubade kasutajatunnuseid või aliasi, viimatikasutatud liidese funktsionaalsusi ning teiste kasutajate kasutajanimesid. Logides ei ole saadetud sõnumite sisu.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Sinu isiklikud sõnumid on tavaliselt läbivalt krüptitud, aga see jututuba ei ole. Tavaliselt on põhjuseks, et kasutusel on mõni seade või meetod nagu e-posti põhised kutsed, mis krüptimist veel ei toeta.", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Sinu isiklikud sõnumid on tavaliselt läbivalt krüptitud, aga see jututuba ei ole. Tavaliselt on põhjuseks, et kasutusel on mõni seade või meetod nagu e-posti põhised kutsed, mis krüptimist veel ei toeta.",
"Enable encryption in settings.": "Võta seadistustes krüptimine kasutusele.", "Enable encryption in settings.": "Võta seadistustes krüptimine kasutusele.",
"Cross-signing is ready but keys are not backed up.": "Risttunnustamine on töövalmis, aga krüptovõtmed on varundamata." "Cross-signing is ready but keys are not backed up.": "Risttunnustamine on töövalmis, aga krüptovõtmed on varundamata.",
"New layout switcher (with message bubbles)": "Uue kujunduse valik (koos sõnumimullidega)",
"This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "See muudab lihtsaks, et jututoad jääksid kogukonnakeskuse piires privaatseks, kuid lasevad kogukonnakeskuses viibivatel inimestel need üles leida ja nendega liituda. Kõik kogukonnakeskuse uued jututoad on selle võimalusega.",
"To help space members find and join a private room, go to that room's Security & Privacy settings.": "Selleks, et aidata kogukonnakeskuse liikmetel leida privaatne jututuba ja sellega liituda, minge selle toa turvalisuse ja privaatsuse seadistustesse.",
"Help people in spaces to find and join private rooms": "Aita kogukonnakeskuse liikmetel leida privaatseid jututube ning nendega liituda",
"See when people join, leave, or are invited to your active room": "Näita, millal teised sinu aktiivse toaga liituvad, sealt lahkuvad või sellesse tuppa kutsutakse",
"Kick, ban, or invite people to your active room, and make you leave": "Aktiivsest toast inimeste väljalükkamine, keelamine või tuppa kutsumine",
"See when people join, leave, or are invited to this room": "Näita, millal inimesed toaga liituvad, lahkuvad või siia tuppa kutsutakse",
"Kick, ban, or invite people to this room, and make you leave": "Sellest toast inimeste väljalükkamine, keelamine või tuppa kutsumine",
"Rooms and spaces": "Jututoad ja kogukonnad",
"Results": "Tulemused",
"Error downloading audio": "Helifaili allalaadimine ei õnnestunud",
"These are likely ones other room admins are a part of.": "Ilmselt on tegemist nendega, mille liikmed on teiste jututubade haldajad.",
"& %(count)s more|other": "ja veel %(count)s",
"Add existing space": "Lisa olemasolev kogukonnakeskus",
"Image": "Pilt",
"Sticker": "Kleeps",
"An unknown error occurred": "Tekkis teadmata viga",
"Their device couldn't start the camera or microphone": "Teise osapoole seadmes ei õnnestunud sisse lülitada kaamerat või mikrofoni",
"Connection failed": "Ühendus ebaõnnestus",
"Could not connect media": "Meediaseadme ühendamine ei õnnestunud",
"Connected": "Ühendatud",
"Copy Room Link": "Kopeeri jututoa link",
"Anyone in a space can find and join. <a>Edit which spaces can access here.</a>": "Kõik kogukonnakeskuse liikmed saavad jututuba leida ja sellega liituda. <a>Muuda lubatud kogukonnakeskuste loendit.</a>",
"Currently, %(count)s spaces have access|other": "Hetkel on ligipääs %(count)s'l kogukonnakeskusel",
"Upgrade required": "Vajalik on uuendus",
"Anyone can find and join.": "Kõik saavad jututuba leida ja sellega liituda.",
"Only invited people can join.": "Liitumine toimub vaid kutse alusel.",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Võimalike probleemide vältimiseks loo oma suhtluse jaoks <a>uus krüptitud jututuba</a>.",
"Private (invite only)": "Privaatne jututuba (eeldab kutset)",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Võimalike probleemide vältimiseks loo oma suhtluse jaoks <a>uus avalik jututuba</a>.",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Me ei soovita krüptitud jututoa muutmist avalikuks.</b> See tähendaks, et kõik huvilised saavad vabalt seda jututuba leida ning temaga liituda ning seega ka kõiki selles leiduvaid sõnumeid lugeda. Olemuselt puuduvad sellises olukorras krüptimise eelised. Avalike jututubade sõnumite krüptimine teeb ka sõnumite saatmise ja vastuvõtmise aeglasemaks.",
"Are you sure you want to make this encrypted room public?": "Kas sa oled kindel, et soovid seda krüptitud jututuba muuta avalikuks?",
"This upgrade will allow members of selected spaces access to this room without an invite.": "Antud uuendusega on valitud kogukonnakeskuste liikmetel võimalik selle jututoaga ilma kutseta liituda.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Me ei soovita avalikes jututubades krüptimise kasutamist.</b> Kuna kõik huvilised saavad vabalt leida avalikke jututube ning nendega liituda, siis saavad nad niikuinii ka neis leiduvaid sõnumeid lugeda. Olemuselt puuduvad sellises olukorras krüptimise eelised ning sa ei saa hiljem krüptimist välja lülitada. Avalike jututubade sõnumite krüptimine teeb ka sõnumite saatmise ja vastuvõtmise aeglasemaks.",
"Are you sure you want to add encryption to this public room?": "Kas sa oled kindel, et soovid selles avalikus jututoas kasutada krüptimist?",
"Message bubbles": "Jutumullid",
"IRC": "IRC",
"Low bandwidth mode (requires compatible homeserver)": "Režiim kehva internetiühenduse jaoks (eeldab koduserveripoolset tuge)",
"Surround selected text when typing special characters": "Erimärkide sisestamisel märgista valitud tekst",
"Multiple integration managers (requires manual setup)": "Mitmed lõiminguhaldurid (eeldab käsitsi seadistamist)",
"Thread": "Jutulõng",
"Show threads": "Näita jutulõnga",
"Threaded messaging": "Sõnumid jutulõngana",
"Autoplay videos": "Esita automaatselt videosid",
"Autoplay GIFs": "Esita automaatselt liikuvaid pilte",
"The above, but in any room you are joined or invited to as well": "Ülaltoodu, aga samuti igas jututoas, millega oled liitunud või kuhu oled kutsutud",
"The above, but in <Room /> as well": "Ülaltoodu, aga samuti <Room /> jututoas",
"Currently, %(count)s spaces have access|one": "Hetkel sellel kogukonnal on ligipääs",
"& %(count)s more|one": "ja veel %(count)s",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s eemaldas siin jututoas klammerduse ühelt sõnumilt. Vaata kõiki klammerdatud sõnumeid.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s eemaldas siin jututoas klammerduse <a>ühelt sõnumilt</a>. Vaata kõiki <b>klammerdatud sõnumeid</b>.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s klammerdas siin jututoas <a>ühe sõnumi</a>. Vaata kõiki <b>klammerdatud sõnumeid</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s klammerdas siin jututoas ühe sõnumi. Vaata kõiki klammerdatud sõnumeid."
} }

View file

@ -3436,7 +3436,7 @@
"Show all rooms in Home": "Afficher tous les salons dans Accueil", "Show all rooms in Home": "Afficher tous les salons dans Accueil",
"%(senderName)s changed the <a>pinned messages</a> for the room.": "%(senderName)s a changé <a>les messages épinglés</a> du salon.", "%(senderName)s changed the <a>pinned messages</a> for the room.": "%(senderName)s a changé <a>les messages épinglés</a> du salon.",
"%(senderName)s kicked %(targetName)s": "%(senderName)s a expulsé %(targetName)s", "%(senderName)s kicked %(targetName)s": "%(senderName)s a expulsé %(targetName)s",
"%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s a explusé %(targetName)s : %(reason)s", "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s a expulsé %(targetName)s : %(reason)s",
"%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s a annulé linvitation de %(targetName)s", "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s a annulé linvitation de %(targetName)s",
"%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s a annulé linvitation de %(targetName)s : %(reason)s", "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s a annulé linvitation de %(targetName)s : %(reason)s",
"%(senderName)s unbanned %(targetName)s": "%(senderName)s a révoqué le bannissement de %(targetName)s", "%(senderName)s unbanned %(targetName)s": "%(senderName)s a révoqué le bannissement de %(targetName)s",
@ -3650,5 +3650,33 @@
"You can change this later.": "Vous pourrez changer ceci plus tard.", "You can change this later.": "Vous pourrez changer ceci plus tard.",
"What kind of Space do you want to create?": "Quel type despace voulez-vous créer ?", "What kind of Space do you want to create?": "Quel type despace voulez-vous créer ?",
"Delete avatar": "Supprimer lavatar", "Delete avatar": "Supprimer lavatar",
"Don't send read receipts": "Ne pas envoyer les accusés de réception" "Don't send read receipts": "Ne pas envoyer les accusés de réception",
"Rooms and spaces": "Salons et espaces",
"Results": "Résultats",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Les journaux de débogage contiennent les données dutilisation de lapplication incluant votre nom dutilisateur, les identifiants ou les alias des salons ou groupes que vous avez visités, les derniers élément de linterface avec lesquels vous avez interagis, et les noms dutilisateurs des autres utilisateurs. Ils ne contiennent pas de messages.",
"Thread": "Discussion",
"Show threads": "Afficher les fils de discussion",
"Enable encryption in settings.": "Activer le chiffrement dans les paramètres.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Vos messages privés sont normalement chiffrés, mais ce salon ne lest pas. Cela est généralement dû à un périphérique non supporté, ou à un moyen de communication non supporté comme les invitations par e-mail.",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Pour éviter ces problèmes, créez un <a>nouveau salon public</a> pour la conversation que vous souhaitez avoir.",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Il nest pas recommandé de rendre public les salons chiffrés.</b> Cela veut dire que quiconque pourra trouver et rejoindre le salon, donc tout le monde pourra lire les messages. Vous naurez plus aucun avantage lié au chiffrement. Chiffrer les messages dans un salon public ralentira la réception et lenvoi de messages.",
"Are you sure you want to make this encrypted room public?": "Êtes-vous sûr de vouloir rendre public ce salon chiffré ?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Pour éviter ces problèmes, créez un <a>nouveau salon chiffré</a> pour la conversation que vous souhaitez avoir.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Il n'est pas recommandé dajouter le chiffrement aux salons publics.</b> Tout le monde peut trouver et rejoindre les salons publics, donc tout le monde peut lire les messages qui sy trouvent. Vous naurez aucun des avantages du chiffrement, et vous ne pourrez pas le désactiver plus tard. Chiffrer les messages dans un salon public ralentira la réception et lenvoi de messages.",
"Are you sure you want to add encryption to this public room?": "Êtes-vous sûr de vouloir ajouter le chiffrement dans ce salon public ?",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Si vous avez soumis un rapport danomalie via GitHub, les journaux de débogage nous aiderons à localiser le problème. Les journaux de débogage contiennent les données dutilisation de lapplication incluant votre nom dutilisateur, les identifiants ou les alias des salons ou groupes que vous avez visités, les derniers élément de linterface avec lesquels vous avez interagis, et les noms dutilisateurs des autres utilisateurs. Ils ne contiennent pas de messages.",
"Cross-signing is ready but keys are not backed up.": "La signature croisée est prête mais les clés ne sont pas sauvegardées.",
"Low bandwidth mode (requires compatible homeserver)": "Mode faible bande passante (nécessite un serveur daccueil compatible)",
"Autoplay videos": "Jouer automatiquement les vidéos",
"Autoplay GIFs": "Jouer automatiquement les GIFs",
"Multiple integration managers (requires manual setup)": "Multiple gestionnaires dintégration (nécessite un réglage manuel)",
"Threaded messaging": "Fils de discussion",
"The above, but in <Room /> as well": "Comme ci-dessus, mais également dans <Room />",
"The above, but in any room you are joined or invited to as well": "Comme ci-dessus, mais également dans tous les salons dans lesquels vous avez été invité ou que vous avez rejoint",
"Currently, %(count)s spaces have access|one": "Actuellement, un espace a accès",
"& %(count)s more|one": "& %(count)s autres",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s a désépinglé un message de ce salon. Voir tous les messages épinglés.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s a désépinglé <a>un message</a> de ce salon. Voir tous les <b>messages épinglés</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s a épinglé un message dans ce salon. Voir tous les messages épinglés.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s a épinglé <a>un message</a> dans ce salon. Voir tous les <b>messages épinglés</b>."
} }

View file

@ -504,14 +504,14 @@
"Invite": "Tabhair cuireadh", "Invite": "Tabhair cuireadh",
"Mention": "Luaigh", "Mention": "Luaigh",
"Demote": "Bain ceadanna", "Demote": "Bain ceadanna",
"Ban": "Coisc", "Ban": "Toirmisc",
"Kick": "Caith amach", "Kick": "Caith amach",
"Disinvite": "Tarraing siar cuireadh", "Disinvite": "Tarraing siar cuireadh",
"Encrypted": "Criptithe", "Encrypted": "Criptithe",
"Encryption": "Criptiúchán", "Encryption": "Criptiúchán",
"Anyone": "Aon duine", "Anyone": "Aon duine",
"Permissions": "Ceadanna", "Permissions": "Ceadanna",
"Unban": "Bain an coisc", "Unban": "Bain an cosc",
"Browse": "Brabhsáil", "Browse": "Brabhsáil",
"Reset": "Athshocraigh", "Reset": "Athshocraigh",
"Sounds": "Fuaimeanna", "Sounds": "Fuaimeanna",
@ -671,5 +671,157 @@
"Signed Out": "Sínithe Amach", "Signed Out": "Sínithe Amach",
"Unable to query for supported registration methods.": "Ní féidir iarratas a dhéanamh faoi modhanna cláraithe tacaithe.", "Unable to query for supported registration methods.": "Ní féidir iarratas a dhéanamh faoi modhanna cláraithe tacaithe.",
"Host account on": "Óstáil cuntas ar", "Host account on": "Óstáil cuntas ar",
"Create account": "Déan cuntas a chruthú" "Create account": "Déan cuntas a chruthú",
"Deactivate Account": "Cuir cuntas as feidhm",
"Account management": "Bainistíocht cuntais",
"Phone numbers": "Uimhreacha guthán",
"Email addresses": "Seoltaí r-phost",
"Display Name": "Ainm Taispeána",
"Profile picture": "Pictiúr próifíle",
"Phone Number": "Uimhir Fóin",
"Verification code": "Cód fíoraithe",
"Notification targets": "Spriocanna fógraí",
"Results": "Torthaí",
"Hangup": "Cuir síos",
"Dialpad": "Eochaircheap",
"More": "Níos mó",
"Decrypting": "Ag Díchriptiú",
"Adding...": "Ag Cur...",
"Access": "Rochtain",
"Image": "Íomhá",
"Sticker": "Greamán",
"Connected": "Ceangailte",
"IRC": "IRC",
"Modern": "Comhaimseartha",
"Global": "Uilíoch",
"Keyword": "Eochairfhocal",
"[number]": "[uimhir]",
"Report": "Tuairiscigh",
"Forward": "Seol ar aghaidh",
"Disagree": "Easaontaigh",
"Collapse": "Cumaisc",
"Expand": "Leath",
"Visibility": "Léargas",
"Address": "Seoladh",
"Sent": "Seolta",
"Beta": "Béite",
"Connecting": "Ag Ceangal",
"Play": "Cas",
"Pause": "Cuir ar sos",
"Sending": "Ag Seoladh",
"%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s",
"Avatar": "Abhatár",
"Removing...": "Ag Baint...",
"Suggested": "Moltaí",
"The file '%(fileName)s' failed to upload.": "Níor éirigh leis an gcomhad '%(fileName)s' a uaslódáil.",
"At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Ag an am seo ní féidir freagra a thabhairt le comhad. Ar mhaith leat an comhad seo a uaslódáil gan freagra a thabhairt?",
"Replying With Files": "Ag Freagairt le Comhaid",
"You do not have permission to start a conference call in this room": "Níl cead agat glao comhdhála a thosú sa seomra seo",
"You cannot place a call with yourself.": "Ní féidir leat glaoch ort féin.",
"The user you called is busy.": "Tá an t-úsáideoir ar a ghlaoigh tú gnóthach.",
"User Busy": "Úsáideoir Gnóthach",
"Share your public space": "Roinn do spás poiblí",
"Invite to %(spaceName)s": "Tabhair cuireadh chun %(spaceName)s",
"Unnamed room": "Seomra gan ainm",
"Command error": "Earráid ordaithe",
"Server error": "Earráid freastalaí",
"Upload file": "Uaslódáil comhad",
"Video call": "Físghlao",
"Voice call": "Glao gutha",
"Admin Tools": "Uirlisí Riaracháin",
"Demote yourself?": "Tabhair ísliú céime duit féin?",
"Enable encryption?": "Cumasaigh criptiú?",
"Banned users": "Úsáideoirí toirmiscthe",
"Muted Users": "Úsáideoirí Fuaim",
"Privileged Users": "Úsáideoirí Pribhléideacha",
"Notify everyone": "Tabhair fógraí do gach duine",
"Ban users": "Toirmisc úsáideoirí",
"Kick users": "Caith úsáideoirí amach",
"Change settings": "Athraigh socruithe",
"Invite users": "Tabhair cuirí d'úsáideoirí",
"Send messages": "Seol teachtaireachtaí",
"Default role": "Gnáth-ról",
"Modify widgets": "Mionathraigh giuirléidí",
"Change topic": "Athraigh ábhar",
"Change permissions": "Athraigh ceadanna",
"Notification sound": "Fuaim fógra",
"Uploaded sound": "Fuaim uaslódáilte",
"URL Previews": "Réamhamhairc URL",
"Room Addresses": "Seoltaí Seomra",
"Open Devtools": "Oscail Devtools",
"Developer options": "Roghanna forbróra",
"Room version:": "Leagan seomra:",
"Room version": "Leagan seomra",
"Too Many Calls": "Barraíocht Glaonna",
"You cannot place VoIP calls in this browser.": "Ní féidir leat glaonna VoIP a chur sa brabhsálaí seo.",
"VoIP is unsupported": "Tá VoIP gan tacaíocht",
"No other application is using the webcam": "Níl aon fheidhmchlár eile ag úsáid an cheamara gréasáin",
"Permission is granted to use the webcam": "Tugtar cead an ceamara gréasáin a úsáid",
"A microphone and webcam are plugged in and set up correctly": "Tá micreafón agus ceamara gréasáin plugáilte isteach agus curtha ar bun i gceart",
"Call failed because webcam or microphone could not be accessed. Check that:": "Níor glaodh toisc nach raibh rochtain ar ceamara gréasáin nó mhicreafón. Seiceáil go:",
"Unable to access webcam / microphone": "Ní féidir rochtain a fháil ar ceamara gréasáin / mhicreafón",
"Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Níor glaodh toisc nach raibh rochtain ar mhicreafón. Seiceáil go bhfuil micreafón plugáilte isteach agus curtha ar bun i gceart.",
"Which rooms would you like to add to this community?": "Cé na seomraí ar mhaith leat a chur leis an bpobal seo?",
"Invite to Community": "Tabhair cuireadh chun an pobal",
"Name or Matrix ID": "Ainm nó ID Matrix",
"Invite new community members": "Tabhair cuireadh do baill nua chun an phobail",
"Who would you like to add to this community?": "Cé ba mhaith leat a chur leis an bpobal seo?",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
"Failure to create room": "Níorbh fhéidir an seomra a chruthú",
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Sáraíonn an comhad '%(fileName)s' teorainn méide an freastalaí baile seo le haghaidh uaslódálacha",
"The server does not support the room version specified.": "Ní thacaíonn an freastalaí leis an leagan seomra a shonraítear.",
"Server may be unavailable, overloaded, or you hit a bug.": "Dfhéadfadh nach mbeadh an freastalaí ar fáil, ró-ualaithe, nó fuair tú fabht.",
"Rooms and spaces": "Seomraí agus spásanna",
"Collapse reply thread": "Cuir na freagraí i bhfolach",
"Threaded messaging": "Teachtaireachtaí i snáitheanna",
"Thread": "Snáithe",
"Show threads": "Taispeáin snáitheanna",
"Low priority": "Tosaíocht íseal",
"Start chat": "Tosaigh comhrá",
"Share room": "Roinn seomra",
"Forget room": "Déan dearmad ar an seomra",
"Join Room": "Téigh isteach an seomra",
"(~%(count)s results)|one": "(~%(count)s toradh)",
"(~%(count)s results)|other": "(~%(count)s torthaí)",
"World readable": "Inléite ag gach duine",
"Communities can now be made into Spaces": "Is féidir Pobail a dhéanamh ina Spásanna anois",
"Spaces are a new way to make a community, with new features coming.": "Is bealach nua iad spásanna chun pobal a dhéanamh, le gnéithe nua ag teacht.",
"Communities won't receive further updates.": "Ní bhfaighidh pobail nuashonruithe breise.",
"Created from <Community />": "Cruthaithe ó <Community />",
"No answer": "Gan freagair",
"Unknown failure: %(reason)s": "Teip anaithnid: %(reason)s",
"Low bandwidth mode (requires compatible homeserver)": "Modh bandaleithid íseal (teastaíonn freastalaí comhoiriúnach)",
"Enable encryption in settings.": "Tosaigh criptiú sna socruithe.",
"Cross-signing is ready but keys are not backed up.": "Tá tras-sínigh réidh ach ní dhéantar cóip chúltaca d'eochracha.",
"Ask the <a>admins</a> of this community to make it into a Space and keep a look out for the invite.": "",
"You can create a Space from this community <a>here</a>.": "",
"Bans user with given id": "Toirmisc úsáideoir leis an ID áirithe",
"Failed to reject invitation": "Níorbh fhéidir an cuireadh a dhiúltú",
"Failed to reject invite": "Níorbh fhéidir an cuireadh a dhiúltú",
"Failed to mute user": "Níor ciúnaíodh an úsáideoir",
"Failed to load timeline position": "Níor lódáladh áit amlíne",
"Failed to kick": "Níor caitheadh amach é",
"Failed to forget room %(errCode)s": "Níor dhearnadh dearmad ar an seomra %(errCode)s",
"Failed to join room": "Níor éiríodh le dul isteach an seomra",
"Failed to change power level": "Níor éiríodh leis an leibhéal cumhachta a hathrú",
"Failed to change password. Is your password correct?": "Níor éiríodh leis do phasfhocal a hathrú. An bhfuil do phasfhocal ceart?",
"Failed to ban user": "Níor éiríodh leis an úsáideoir a thoirmeasc",
"Export E2E room keys": "Easpórtáil eochracha an tseomra le criptiú ó dheireadh go deireadh",
"Error decrypting attachment": "Earráid le ceangaltán a dhíchriptiú",
"Enter passphrase": "Iontráil pasfrása",
"Email address": "Seoladh ríomhphoist",
"Download %(text)s": "Íoslódáil %(text)s",
"Deops user with given id": "Bain an cumhacht oibritheora ó úsáideoir leis an ID áirithe",
"Decrypt %(text)s": "Díchriptigh %(text)s",
"Custom level": "Leibhéal saincheaptha",
"Create Room": "Déan Seomra",
"Changes your display nickname": "Athraíonn sé d'ainm taispeána",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "D'athraigh %(senderDisplayName)s an ábhar go \"%(topic)s\".",
"%(senderDisplayName)s removed the room name.": "Bhain %(senderDisplayName)s ainm an tseomra.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "D'athraigh %(senderDisplayName)s ainm an tseomra go %(roomName)s.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "D'athraigh %(senderName)s an leibhéal cumhachta %(powerLevelDiffText)s.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Ní féidir ceangal leis an bhfreastalaí baile trí HTTP nuair a bhíonn URL HTTPS i mbarra do bhrabhsálaí. Bain úsáid as HTTPS nó <a> scripteanna neamhshábháilte a chumasú </a>.",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Ní féidir ceangal leis an bhfreastalaí baile - seiceáil do nascacht le do thoil, déan cinnte go bhfuil muinín i dteastas <a>SSL do fhreastalaí baile</a>, agus nach bhfuil síneadh brabhsálaí ag cur bac ar iarratais."
} }

View file

@ -3687,5 +3687,20 @@
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Para evitar estos problemas, crea unha <a>nova sala cifrada</a> para a conversa que pretendes manter.", "To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Para evitar estos problemas, crea unha <a>nova sala cifrada</a> para a conversa que pretendes manter.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Non se recomenda engadir cifrado a salas públicas.</b> Calquera pode atopar e unirse a salas públicas, polo que tamén ler as mensaxes. Non vas ter ningún dos beneficios do cifrado, e máis tarde non poderás desactivalo. Cifrar as mensaxes nunha sala pública tamén fará máis lenta a entrega e recepción das mensaxes.", "<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Non se recomenda engadir cifrado a salas públicas.</b> Calquera pode atopar e unirse a salas públicas, polo que tamén ler as mensaxes. Non vas ter ningún dos beneficios do cifrado, e máis tarde non poderás desactivalo. Cifrar as mensaxes nunha sala pública tamén fará máis lenta a entrega e recepción das mensaxes.",
"Are you sure you want to add encryption to this public room?": "Tes a certeza de querer engadir cifrado a esta sala pública?", "Are you sure you want to add encryption to this public room?": "Tes a certeza de querer engadir cifrado a esta sala pública?",
"Cross-signing is ready but keys are not backed up.": "A sinatura-cruzada está preparada pero non hai copia das chaves." "Cross-signing is ready but keys are not backed up.": "A sinatura-cruzada está preparada pero non hai copia das chaves.",
"Low bandwidth mode (requires compatible homeserver)": "Modo de ancho de banda limitado (require servidor de inicio compatible)",
"Multiple integration managers (requires manual setup)": "Varios xestores de integración (require configuración manual)",
"Thread": "Tema",
"Show threads": "Mostrar temas",
"Currently, %(count)s spaces have access|one": "Actualmente, un espazo ten acceso",
"& %(count)s more|one": "e %(count)s máis",
"Autoplay GIFs": "Reprod. automática GIFs",
"Autoplay videos": "Reprod. automática vídeo",
"Threaded messaging": "Mensaxes fiadas",
"The above, but in <Room /> as well": "O de arriba, pero tamén en <Room />",
"The above, but in any room you are joined or invited to as well": "O de enriba, pero en calquera sala á que te uniches ou foches convidada",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s desafixou unha mensaxe desta sala. Mira tódalas mensaxes fixadas.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s deafixou <a>unha mensaxe</a> desta sala. Mira tódalas <b>mensaxes fixadas</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fixou unha mensaxe nesta sala. Mira tódalas mensaxes fixadas.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s fixou <a>unha mensaxe</a> nesta sala. Mira tódalas <b>mensaxes fixadas</b>."
} }

View file

@ -3444,7 +3444,7 @@
"Move down": "Mozgatás le", "Move down": "Mozgatás le",
"Move up": "Mozgatás fel", "Move up": "Mozgatás fel",
"Report": "Jelentés", "Report": "Jelentés",
"Collapse reply thread": "Beszélgetés szál becsukása", "Collapse reply thread": "Üzenetszál becsukása",
"Show preview": "Előnézet megjelenítése", "Show preview": "Előnézet megjelenítése",
"View source": "Forrás megtekintése", "View source": "Forrás megtekintése",
"Forward": "Továbbítás", "Forward": "Továbbítás",
@ -3670,5 +3670,31 @@
"What kind of Space do you want to create?": "Milyen típusú teret szeretne készíteni?", "What kind of Space do you want to create?": "Milyen típusú teret szeretne készíteni?",
"Delete avatar": "Profilkép törlése", "Delete avatar": "Profilkép törlése",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "A hibakereső napló alkalmazás használati adatokat tartalmaz beleértve a felhasználói nevedet, az általad meglátogatott szobák és csoportok azonosítóit alternatív neveit, az utolsó felhasználói felület elemét amit használt és más felhasználói neveket. Csevegés üzenetek szövegét nem tartalmazza.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "A hibakereső napló alkalmazás használati adatokat tartalmaz beleértve a felhasználói nevedet, az általad meglátogatott szobák és csoportok azonosítóit alternatív neveit, az utolsó felhasználói felület elemét amit használt és más felhasználói neveket. Csevegés üzenetek szövegét nem tartalmazza.",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Ha a GitHubon keresztül küldted be a hibát, a hibakeresési napló segíthet nekünk a javításban. A napló felhasználási adatokat tartalmaz mint a felhasználói neved, az általad meglátogatott szobák vagy csoportok azonosítóját vagy alternatív nevét, az utolsó felhasználói felület elemét amit használt és mások felhasználói nevét. De nem tartalmazzák az üzeneteket." "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Ha a GitHubon keresztül küldted be a hibát, a hibakeresési napló segíthet nekünk a javításban. A napló felhasználási adatokat tartalmaz mint a felhasználói neved, az általad meglátogatott szobák vagy csoportok azonosítóját vagy alternatív nevét, az utolsó felhasználói felület elemét amit használt és mások felhasználói nevét. De nem tartalmazzák az üzeneteket.",
"Are you sure you want to add encryption to this public room?": "Biztos, hogy titkosítást állít be ehhez a nyilvános szobához?",
"Cross-signing is ready but keys are not backed up.": "Eszközök közötti hitelesítés megvan de a kulcsokhoz nincs biztonsági mentés.",
"Rooms and spaces": "Szobák és terek",
"Results": "Eredmények",
"Enable encryption in settings.": "Titkosítás bekapcsolása a beállításokban.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "A privát üzenetek általában titkosítottak de ez a szoba nem az. Általában ez a titkosítást nem támogató eszköz vagy metódus használata miatt lehet, mint az e-mail meghívók.",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Az ehhez hasonló problémák elkerüléséhez készítsen <a>új nyilvános szobát</a> a tervezett beszélgetésekhez.",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Titkosított szobát nem célszerű nyilvánossá tenni.</b> Bárki megtalálhatja és csatlakozhat nyilvános szobákhoz, így bárki elolvashatja az üzeneteket bennük. A titkosítás előnyeit így nem jelentkeznek és később ezt nem lehet kikapcsolni. Nyilvános szobákban a titkosított üzenetek az üzenetküldést és fogadást csak lassítják.",
"Are you sure you want to make this encrypted room public?": "Biztos, hogy nyilvánossá teszi ezt a titkosított szobát?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Az ehhez hasonló problémák elkerüléséhez készítsen <a>új titkosított szobát</a> a tervezett beszélgetésekhez.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Nyilvános szobához nem javasolt a titkosítás beállítása.</b>Bárki megtalálhatja és csatlakozhat nyilvános szobákhoz, így bárki elolvashatja az üzeneteket bennük. A titkosítás előnyeit így nem jelentkeznek és később ezt nem lehet kikapcsolni. Nyilvános szobákban a titkosított üzenetek az üzenetküldést és fogadást csak lassítják.",
"Low bandwidth mode (requires compatible homeserver)": "Alacsony sávszélesség mód (kompatibilis matrix szervert igényel)",
"Multiple integration managers (requires manual setup)": "Több integrációs menedzser (kézi beállítást igényel)",
"Autoplay videos": "Videók automatikus lejátszása",
"Autoplay GIFs": "GIF-ek automatikus lejátszása",
"The above, but in <Room /> as well": "A felül lévő, de ebben a szobában is: <Room />",
"Show threads": "Üzenetszálak megjelenítése",
"Threaded messaging": "Üzenetszálas beszélgetés",
"The above, but in any room you are joined or invited to as well": "Ami felette van, de minden szobában amibe belépett vagy meghívták",
"Thread": "Üzenetszál",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s levett egy kitűzött üzenetet ebből a szobában. Minden kitűzött üzenet megjelenítése.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s levett egy kitűzött <a>üzenetet</a> ebből a szobában. Minden <b>kitűzött üzenet</b> megjelenítése.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s kitűzött egy üzenetet ebben a szobában. Minden kitűzött üzenet megjelenítése.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s kitűzött <a>egy üzenetet</a> ebben a szobában. Minden <b>kitűzött üzenet</b> megjelenítése.",
"Currently, %(count)s spaces have access|one": "Jelenleg a Térnek hozzáférése van",
"& %(count)s more|one": "és még %(count)s"
} }

View file

@ -3678,5 +3678,20 @@
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Se hai segnalato un errore via Github, i log di debug possono aiutarci a identificare il problema. I log di debug contengono dati di utilizzo dell'applicazione inclusi il nome utente, gli ID o alias delle stanze o gruppi visitati, gli ultimi elementi dell'interfaccia con cui hai interagito e i nomi degli altri utenti. Non contengono messaggi.", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Se hai segnalato un errore via Github, i log di debug possono aiutarci a identificare il problema. I log di debug contengono dati di utilizzo dell'applicazione inclusi il nome utente, gli ID o alias delle stanze o gruppi visitati, gli ultimi elementi dell'interfaccia con cui hai interagito e i nomi degli altri utenti. Non contengono messaggi.",
"Enable encryption in settings.": "Attiva la crittografia nelle impostazioni.", "Enable encryption in settings.": "Attiva la crittografia nelle impostazioni.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "I tuoi messaggi privati normalmente sono cifrati, ma questa stanza non lo è. Di solito ciò è dovuto ad un dispositivo non supportato o dal metodo usato, come gli inviti per email.", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "I tuoi messaggi privati normalmente sono cifrati, ma questa stanza non lo è. Di solito ciò è dovuto ad un dispositivo non supportato o dal metodo usato, come gli inviti per email.",
"Cross-signing is ready but keys are not backed up.": "La firma incrociata è pronta ma c'è un backup delle chiavi." "Cross-signing is ready but keys are not backed up.": "La firma incrociata è pronta ma c'è un backup delle chiavi.",
"Rooms and spaces": "Stanze e spazi",
"Results": "Risultati",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Per evitare questi problemi, crea una <a>nuova stanza pubblica</a> per la conversazione che vuoi avere.",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Non è consigliabile rendere pubbliche le stanze cifrate.</b> Se lo fai, chiunque potrà trovare ed entrare nella stanza, quindi chiunque potrà leggere i messaggi. Non avrai alcun beneficio dalla crittografia. Cifrare i messaggi in una stanza pubblica renderà più lenti l'invio e la ricezione dei messaggi.",
"Are you sure you want to make this encrypted room public?": "Vuoi veramente rendere pubblica questa stanza cifrata?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Per evitare questi problemi, crea una <a>nuova stanza cifrata</a> per la conversazione che vuoi avere.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Non è consigliabile aggiungere la crittografia alle stanze pubbliche.</b>Chiunque può trovare ed entrare in stanze pubbliche, quindi chiunque può leggere i messaggi. Non avrai alcun beneficio dalla crittografia e non potrai disattivarla in seguito. Cifrare i messaggi in una stanza pubblica renderà più lenti l'invio e la ricezione dei messaggi.",
"Are you sure you want to add encryption to this public room?": "Vuoi veramente aggiungere la crittografia a questa stanza pubblica?",
"Low bandwidth mode (requires compatible homeserver)": "Modalità a connessione lenta (richiede un homeserver compatibile)",
"Multiple integration managers (requires manual setup)": "Gestori di integrazione multipli (richiede configurazione manuale)",
"Thread": "Argomento",
"Show threads": "Mostra argomenti",
"Threaded messaging": "Messaggi raggruppati",
"The above, but in any room you are joined or invited to as well": "Quanto sopra, ma anche in qualsiasi stanza tu sia entrato o invitato",
"The above, but in <Room /> as well": "Quanto sopra, ma anche in <Room />"
} }

View file

@ -23,9 +23,9 @@
"New Password": "新しいパスワード", "New Password": "新しいパスワード",
"Failed to change password. Is your password correct?": "パスワード変更に失敗しました。パスワードは正しいですか?", "Failed to change password. Is your password correct?": "パスワード変更に失敗しました。パスワードは正しいですか?",
"Only people who have been invited": "この部屋に招待された人のみ参加可能", "Only people who have been invited": "この部屋に招待された人のみ参加可能",
"Always show message timestamps": "発言時刻を常に表示", "Always show message timestamps": "発言時刻を常に表示する",
"Filter room members": "部屋メンバーを検索", "Filter room members": "部屋メンバーを検索",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "発言時刻を12時間形式で表示 (例 2:30PM)", "Show timestamps in 12 hour format (e.g. 2:30pm)": "発言時刻を 12 時間形式で表示する (例: 2:30午後)",
"Upload avatar": "アイコン画像を変更", "Upload avatar": "アイコン画像を変更",
"Upload file": "ファイルのアップロード", "Upload file": "ファイルのアップロード",
"%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)sはアプリケーションを改善するために匿名の分析情報を収集しています。", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)sはアプリケーションを改善するために匿名の分析情報を収集しています。",
@ -320,11 +320,11 @@
"(no answer)": "(応答なし)", "(no answer)": "(応答なし)",
"(unknown failure: %(reason)s)": "(不明なエラー: %(reason)s)", "(unknown failure: %(reason)s)": "(不明なエラー: %(reason)s)",
"%(senderName)s ended the call.": "%(senderName)s が通話を終了しました。", "%(senderName)s ended the call.": "%(senderName)s が通話を終了しました。",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s は部屋に加わるよう %(targetDisplayName)s に招待状を送りました。", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s が %(targetDisplayName)s をこの部屋に招待しました。",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s が、部屋のメンバー全員に招待された時点からの部屋履歴を参照できるようにしました。", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「メンバーのみ (招待された時点以降)」閲覧できるようにしました。",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s が、部屋のメンバー全員に参加した時点からの部屋履歴を参照できるようにしました。", "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「メンバーのみ (参加した時点以降)」閲覧できるようにしました。",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s が、部屋のメンバー全員に部屋履歴を参照できるようにしました。", "%(senderName)s made future room history visible to all room members.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「メンバーのみ」閲覧できるようにしました。",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s が、部屋履歴を誰でも参照できるようにしました。", "%(senderName)s made future room history visible to anyone.": "%(senderName)s がこの部屋で今後送信されるメッセージの履歴を「誰でも」閲覧できるようにしました。",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s が、見知らぬ (%(visibility)s) に部屋履歴を参照できるようにしました。", "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s が、見知らぬ (%(visibility)s) に部屋履歴を参照できるようにしました。",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s は %(fromPowerLevel)s から %(toPowerLevel)s", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s は %(fromPowerLevel)s から %(toPowerLevel)s",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s が %(powerLevelDiffText)s の権限レベルを変更しました。", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s が %(powerLevelDiffText)s の権限レベルを変更しました。",
@ -479,8 +479,8 @@
"Publish this room to the public in %(domain)s's room directory?": "%(domain)s のルームディレクトリにこの部屋を公開しますか?", "Publish this room to the public in %(domain)s's room directory?": "%(domain)s のルームディレクトリにこの部屋を公開しますか?",
"Who can read history?": "誰が履歴を読むことができますか?", "Who can read history?": "誰が履歴を読むことができますか?",
"Members only (since the point in time of selecting this option)": "メンバーのみ (このオプションを選択した時点以降)", "Members only (since the point in time of selecting this option)": "メンバーのみ (このオプションを選択した時点以降)",
"Members only (since they were invited)": "メンバーのみ (招待されて以来)", "Members only (since they were invited)": "メンバーのみ (招待された時点以降)",
"Members only (since they joined)": "メンバーのみ (参加して以来)", "Members only (since they joined)": "メンバーのみ (参加した時点以降)",
"Permissions": "アクセス許可", "Permissions": "アクセス許可",
"Advanced": "詳細", "Advanced": "詳細",
"Add a topic": "トピックを追加", "Add a topic": "トピックを追加",
@ -1304,8 +1304,8 @@
"Prompt before sending invites to potentially invalid matrix IDs": "不正な可能性のある Matrix ID に招待を送るまえに確認を表示", "Prompt before sending invites to potentially invalid matrix IDs": "不正な可能性のある Matrix ID に招待を送るまえに確認を表示",
"Order rooms by name": "名前順で部屋を整列", "Order rooms by name": "名前順で部屋を整列",
"Show rooms with unread notifications first": "未読通知のある部屋をトップに表示", "Show rooms with unread notifications first": "未読通知のある部屋をトップに表示",
"Show shortcuts to recently viewed rooms above the room list": "最近表示した部屋のショートカットを部屋リストの上に表示", "Show shortcuts to recently viewed rooms above the room list": "最近表示した部屋のショートカットを部屋リストの上に表示する",
"Show previews/thumbnails for images": "画像のプレビュー/サムネイルを表示", "Show previews/thumbnails for images": "画像のプレビュー/サムネイルを表示する",
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "あなたのアカウントではクロス署名の認証情報がシークレットストレージに保存されていますが、このセッションでは信頼されていません。", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "あなたのアカウントではクロス署名の認証情報がシークレットストレージに保存されていますが、このセッションでは信頼されていません。",
"This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "このセッションでは<b>キーをバックアップしていません</b>。ですが、あなたは復元に使用したり今後キーを追加したりできるバックアップを持っています。", "This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "このセッションでは<b>キーをバックアップしていません</b>。ですが、あなたは復元に使用したり今後キーを追加したりできるバックアップを持っています。",
"Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "このセッションのみにあるキーを失なわないために、セッションをキーバックアップに接続しましょう。", "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "このセッションのみにあるキーを失なわないために、セッションをキーバックアップに接続しましょう。",
@ -1820,7 +1820,7 @@
"Send general files as you in this room": "あなたとしてファイルを部屋に送る", "Send general files as you in this room": "あなたとしてファイルを部屋に送る",
"See videos posted to this room": "部屋に送られた動画を見る", "See videos posted to this room": "部屋に送られた動画を見る",
"See videos posted to your active room": "アクティブな部屋に送られた動画を見る", "See videos posted to your active room": "アクティブな部屋に送られた動画を見る",
"Send videos as you in your active room": "あなたとしてアクティブな部屋にを送る", "Send videos as you in your active room": "あなたとしてアクティブな部屋に画を送る",
"Send videos as you in this room": "あなたとして部屋に動画を送る", "Send videos as you in this room": "あなたとして部屋に動画を送る",
"See images posted to your active room": "アクティブな部屋に送られた画像を見る", "See images posted to your active room": "アクティブな部屋に送られた画像を見る",
"See images posted to this room": "部屋に送られた画像を見る", "See images posted to this room": "部屋に送られた画像を見る",
@ -1887,7 +1887,7 @@
"%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s は部屋の禁止ルール %(glob)s を削除しました", "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s は部屋の禁止ルール %(glob)s を削除しました",
"%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s はユーザー禁止ルール %(glob)s を削除しました", "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s はユーザー禁止ルール %(glob)s を削除しました",
"%(senderName)s has updated the widget layout": "%(senderName)s はウィジェットのレイアウトを更新しました", "%(senderName)s has updated the widget layout": "%(senderName)s はウィジェットのレイアウトを更新しました",
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s は部屋 %(targetDisplayName)s への招待を取り消しました。", "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s %(targetDisplayName)s への招待を取り消しました。",
"%(senderName)s declined the call.": "%(senderName)s は通話を拒否しました。", "%(senderName)s declined the call.": "%(senderName)s は通話を拒否しました。",
"(an error occurred)": "(エラーが発生しました)", "(an error occurred)": "(エラーが発生しました)",
"(their device couldn't start the camera / microphone)": "(彼らのデバイスはカメラ/マイクを使用できませんでした)", "(their device couldn't start the camera / microphone)": "(彼らのデバイスはカメラ/マイクを使用できませんでした)",
@ -2518,5 +2518,29 @@
"Saving...": "保存しています…", "Saving...": "保存しています…",
"Failed to save space settings.": "スペースの設定を保存できませんでした。", "Failed to save space settings.": "スペースの設定を保存できませんでした。",
"Transfer Failed": "転送に失敗しました", "Transfer Failed": "転送に失敗しました",
"Unable to transfer call": "通話が転送できませんでした" "Unable to transfer call": "通話が転送できませんでした",
"All rooms you're in will appear in Home.": "ホームに、あなたが参加しているすべての部屋が表示されます。",
"Show all rooms in Home": "ホームにすべての部屋を表示する",
"Images, GIFs and videos": "画像・GIF・動画",
"Displaying time": "表示時刻",
"Use Command + F to search timeline": "Command + F でタイムラインを検索する",
"Use Ctrl + F to search timeline": "Ctrl + F でタイムラインを検索する",
"To view all keyboard shortcuts, click here.": "ここをクリックすると、すべてのキーボードショートカットを確認できます。",
"Keyboard shortcuts": "キーボードショートカット",
"Messages containing keywords": "指定のキーワードを含むメッセージ",
"Mentions & keywords": "メンションとキーワード",
"Global": "グローバル",
"New keyword": "新しいキーワード",
"Keyword": "キーワード",
"Enable for this account": "このアカウントで有効にする",
"%(targetName)s joined the room": "%(targetName)s がこの部屋に参加しました",
"Anyone can find and join.": "誰でも検索・参加できます。",
"Anyone in %(spaceName)s can find and join. You can select other spaces too.": "%(spaceName)s のメンバーが検索・参加できます。他のスペースも選択可能です。",
"Anyone in a space can find and join. You can select multiple spaces.": "スペースのメンバーが検索・参加できます。複数のスペースも選択可能です。",
"Space members": "スペースのメンバー",
"Upgrade required": "アップグレードが必要",
"Only invited people can join.": "招待された人のみ参加できます。",
"Private (invite only)": "プライベート (招待者のみ)",
"Decide who can join %(roomName)s.": "%(roomName)s に参加できる人を設定します。",
"%(senderName)s invited %(targetName)s": "%(senderName)s が %(targetName)s を招待しました"
} }

View file

@ -3564,5 +3564,31 @@
"This community has been upgraded into a Space": "Deze gemeenschap is geupgrade naar een Space", "This community has been upgraded into a Space": "Deze gemeenschap is geupgrade naar een Space",
"Unknown failure: %(reason)s": "Onbekende fout: %(reason)s", "Unknown failure: %(reason)s": "Onbekende fout: %(reason)s",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Debug logs bevatten applicatie gebruiksgegevens inclusief uw inlognaam, de ID's of aliassen van de kamers of groepen die u hebt bezocht, welke UI elementen u het laatst hebt gebruikt, en de inlognamen van andere personen. Ze bevatten geen berichten.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Debug logs bevatten applicatie gebruiksgegevens inclusief uw inlognaam, de ID's of aliassen van de kamers of groepen die u hebt bezocht, welke UI elementen u het laatst hebt gebruikt, en de inlognamen van andere personen. Ze bevatten geen berichten.",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Als u een bug hebt ingediend via GitHub, kunnen debug logs ons helpen het probleem op te sporen. Debug logs bevatten applicatie gebruiksgegevens inclusief uw inlognaam, de ID's of aliassen van de kamers of groepen die u hebt bezocht, welke UI elementen u het laatst hebt gebruikt, en de inlognamen van andere personen. Ze bevatten geen berichten." "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Als u een bug hebt ingediend via GitHub, kunnen debug logs ons helpen het probleem op te sporen. Debug logs bevatten applicatie gebruiksgegevens inclusief uw inlognaam, de ID's of aliassen van de kamers of groepen die u hebt bezocht, welke UI elementen u het laatst hebt gebruikt, en de inlognamen van andere personen. Ze bevatten geen berichten.",
"Rooms and spaces": "Kamers en spaces",
"Results": "Resultaten",
"Enable encryption in settings.": "Versleuteling inschakelen in instellingen.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Uw privéberichten zijn versleuteld, maar deze kamer niet. Dit komt vaak doordat u een niet ondersteund apparaat of methode, zoals e-mailuitnodigingen.",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Om problemen te voorkomen, maak een<a>nieuwe publieke kamer</a> voor de gesprekken die u wilt voeren.",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Het wordt afgeraden om publieke kamers te versleutelen.</b> Het betekend dat iedereen u kan vinden en aan deelnemen, dus iedereen kan al de berichten lezen. U krijgt dus geen voordelen bij versleuteling. Versleutelde berichten in een publieke kamer maakt het ontvangen en versturen van berichten langzamer.",
"Are you sure you want to make this encrypted room public?": "Weet u zeker dat u deze publieke kamer wilt versleutelen?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Om deze problemen te voorkomen, maak een <a>nieuwe versleutelde kamer</a> voor de gesprekken die u wilt voeren.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Het wordt afgeraden om versleuteling in te schakelen voor publieke kamers.</b>Iedereen kan publieke kamers vinden en aan deelnemen, dus iedereen kan de berichten lezen. U krijgt geen voordelen van de versleuteling en u kunt het later niet uitschakelen. Berichten versleutelen in een publieke kamer maakt het ontvangen en versturen van berichten langzamer.",
"Are you sure you want to add encryption to this public room?": "Weet u zeker dat u versleuteling wil inschakelen voor deze publieke kamer?",
"Cross-signing is ready but keys are not backed up.": "Kruiselings ondertekenen is klaar, maar de sleutels zijn nog niet geback-upt.",
"Low bandwidth mode (requires compatible homeserver)": "Lage bandbreedte modus (compatibele homeserver vereist)",
"Multiple integration managers (requires manual setup)": "Meerdere integratiemanagers (vereist handmatige instelling)",
"Thread": "Draad",
"Show threads": "Draad weergeven",
"Threaded messaging": "Draad berichten",
"The above, but in <Room /> as well": "Het bovenstaande, maar ook in <Room />",
"The above, but in any room you are joined or invited to as well": "Het bovenstaande, maar in elke kamer waar u aan deelneemt en voor uitgenodigd bent",
"Autoplay videos": "Videos automatisch afspelen",
"Autoplay GIFs": "GIF's automatisch afspelen",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s maakte een vastgeprikt bericht los van deze kamer. Bekijk alle vastgeprikte berichten.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s maakte <a>een vastgeprikt bericht</a> los van deze kamer. Bekijk alle <b>vastgeprikte berichten</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s prikte een bericht vast aan deze kamer. Bekijk alle vastgeprikte berichten.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s prikte <a>een bericht</a> aan deze kamer. Bekijk alle <b>vastgeprikte berichten</b>.",
"Currently, %(count)s spaces have access|one": "Momenteel heeft één ruimte toegang",
"& %(count)s more|one": "& %(count)s meer"
} }

View file

@ -2378,5 +2378,9 @@
"Identity server (%(server)s)": "Serwer tożsamości (%(server)s)", "Identity server (%(server)s)": "Serwer tożsamości (%(server)s)",
"Could not connect to identity server": "Nie można połączyć z serwerem tożsamości", "Could not connect to identity server": "Nie można połączyć z serwerem tożsamości",
"Not a valid identity server (status code %(code)s)": "Nieprawidłowy serwer tożsamości (kod statusu %(code)s)", "Not a valid identity server (status code %(code)s)": "Nieprawidłowy serwer tożsamości (kod statusu %(code)s)",
"Identity server URL must be HTTPS": "URL serwera tożsamości musi być HTTPS" "Identity server URL must be HTTPS": "URL serwera tożsamości musi być HTTPS",
"Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Twój serwer domowy odrzucił twoją próbę zalogowania się. Może być to spowodowane zbyt długim czasem oczekiwania. Prosimy spróbować ponownie. Jeśli problem się powtórzy, prosimy o kontakt z administratorem twojego serwera domowego.",
"Failed to transfer call": "Nie udało się przekazać połączenia",
"Transfer Failed": "Transfer nie powiódł się",
"Unable to transfer call": "Nie udało się przekazać połączenia"
} }

View file

@ -3667,10 +3667,28 @@
"Enable encryption in settings.": "Aktivizoni fshehtëzimin te rregullimet.", "Enable encryption in settings.": "Aktivizoni fshehtëzimin te rregullimet.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Mesazhet tuaja private normalisht fshehtëzohen, por kjo dhomë sfshehtëzohet. Zakonisht kjo vjen për shkak të përdorimit të një pajisjeje ose metode të pambuluar, bie fjala, ftesa me email.", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Mesazhet tuaja private normalisht fshehtëzohen, por kjo dhomë sfshehtëzohet. Zakonisht kjo vjen për shkak të përdorimit të një pajisjeje ose metode të pambuluar, bie fjala, ftesa me email.",
"Cross-signing is ready but keys are not backed up.": "“Cross-signing” është gati, por kyçet sjanë koperuajtur.", "Cross-signing is ready but keys are not backed up.": "“Cross-signing” është gati, por kyçet sjanë koperuajtur.",
"Rooms and spaces": "", "Rooms and spaces": "Dhoma dhe hapësira",
"Results": "", "Results": "Përfundime",
"Are you sure you want to add encryption to this public room?": "A jeni të sigurtë që dëshironi të shtoni enkriptim në këtë dhomën publike?", "Are you sure you want to add encryption to this public room?": "A jeni i sigurt se doni të shtohet fshehtëzim në këtë dhomë publike?",
"Thumbs up": "Gishtin lart", "Thumbs up": "",
"Remain on your screen while running": "Rrini në ekran për deri sa është hapur", "Remain on your screen while running": "Rrini në ekran për deri sa është hapur",
"Remain on your screen when viewing another room, when running": "Rrini në ekran për deri sa jeni duke shikuar një dhomë tjetër" "Remain on your screen when viewing another room, when running": "Rrini në ekran për deri sa jeni duke shikuar një dhomë tjetër",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Nuk rekomandohet të bëhen publike dhoma të fshehtëzuara.</b> Kjo do të thoshte se cilido mund të gjejë dhe hyjë te dhoma, pra cilido mund të lexojë mesazhet. Sdo të përfitoni asnjë nga të mirat e fshehtëzimit. Fshehtëzimi i mesazheve në një dhomë publike do ta ngadalësojë marrjen dhe dërgimin e tyre.",
"Are you sure you want to make this encrypted room public?": "Jeni i sigurt se doni ta bëni publike këtë dhomë të fshehtëzuar?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Për të shmangur këto probleme, krijoni një <a>dhomë të re të fshehtëzuar</a> për bisedën që keni në plan të bëni.",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Nuk rekomandohet të shtohet fshehtëzim në dhoma publike.</b>Dhomat publike mund ti gjejë dhe hyjë në to kushdo, që cilido të mund të lexojë mesazhet në to. Sdo të përfitoni asnjë nga të mirat e fshehtëzimit, dhe sdo të jeni në gjendje ta çaktivizoni më vonë. Fshehtëzimi i mesazheve në një dhomë publike do të ngadalësojë marrjen dhe dërgimin e mesazheve.",
"Thread": "Rrjedhë",
"Show threads": "Shfaq rrjedha",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "Për të shmangur këto probleme, krijoni për bisedën që keni në plan një <a>dhomë të re publike</a>.",
"Low bandwidth mode (requires compatible homeserver)": "Mënyra trafik me shpejtësi të ulët (lyp shërbyes Home të përputhshëm)",
"Multiple integration managers (requires manual setup)": "Përgjegjës të shumtë integrimi (lyp ujdisje dorazi)",
"Threaded messaging": "Mesazhe me rrjedha",
"The above, but in <Room /> as well": "Atë më sipër, por edhe te <Room />",
"The above, but in any room you are joined or invited to as well": "Atë më sipër, por edhe në çfarëdo dhome ku keni hyrë ose jeni ftuar",
"Autoplay videos": "Vetëluaji videot",
"Autoplay GIFs": "Vetëluaji GIF-et",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s hoqi fiksimin e një mesazhi nga kjo dhomë. Shihni krejt mesazhet e fiksuar.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s hoqi fiksimin e <a>një mesazhi</a> nga kjo dhomë. Shihni krejt <b>mesazhet e fiksuar</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fiksoi një mesazh te kjo dhomë. Shihni krejt mesazhet e fiksuar.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s fiksoi <a>një mesazh</a> te kjo dhomë. Shini krejt <b>mesazhet e fiksuar</b>."
} }

View file

@ -3606,5 +3606,18 @@
"Are you sure you want to make this encrypted room public?": "Är du säker på att du vill göra det här krypterade rummet offentligt?", "Are you sure you want to make this encrypted room public?": "Är du säker på att du vill göra det här krypterade rummet offentligt?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "För att undvika dessa problem, skapa ett <a>nytt krypterat rum</a> för konversationen du planerar att ha.", "To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "För att undvika dessa problem, skapa ett <a>nytt krypterat rum</a> för konversationen du planerar att ha.",
"Are you sure you want to add encryption to this public room?": "Är du säker på att du vill lägga till kryptering till det här offentliga rummet?", "Are you sure you want to add encryption to this public room?": "Är du säker på att du vill lägga till kryptering till det här offentliga rummet?",
"Cross-signing is ready but keys are not backed up.": "Korssignering är klart, men nycklarna är inte säkerhetskopierade än." "Cross-signing is ready but keys are not backed up.": "Korssignering är klart, men nycklarna är inte säkerhetskopierade än.",
"Low bandwidth mode (requires compatible homeserver)": "Lågbandbreddsläge (kräver kompatibel hemserver)",
"Multiple integration managers (requires manual setup)": "Flera integrationshanterare (kräver manuell inställning)",
"Thread": "Trådar",
"Show threads": "Visa trådar",
"Autoplay videos": "Autospela videor",
"Autoplay GIFs": "Autospela GIF:ar",
"Threaded messaging": "Trådat meddelande",
"The above, but in <Room /> as well": "Det ovanstående, men i <Room /> också",
"The above, but in any room you are joined or invited to as well": "Det ovanstående, men i vilket som helst rum du är med i eller inbjuden till också",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s avfäste ett meddelande i det här rummet. Se alla fästa meddelanden.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s avfäste <a>ett meddelande</a> i det här rummet. Se alla <b>fästa meddelanden</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fäste ett meddelande i det här rummet. Se alla fästa meddelanden.",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s fäste <a>ett meddelande</a> i det här rummet. Se alla <b>fästa meddelanden</b>."
} }

View file

@ -61,7 +61,7 @@
"%(senderName)s banned %(targetName)s.": "%(senderName)s заблокував/ла %(targetName)s.", "%(senderName)s banned %(targetName)s.": "%(senderName)s заблокував/ла %(targetName)s.",
"Ban": "Заблокувати", "Ban": "Заблокувати",
"Banned users": "Заблоковані користувачі", "Banned users": "Заблоковані користувачі",
"Bans user with given id": "Блокує користувача з вказаним ідентифікатором", "Bans user with given id": "Блокує користувача зі вказаним ID",
"Call Timeout": "Час очікування виклика", "Call Timeout": "Час очікування виклика",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Не вдається підключитись до домашнього серверу - перевірте підключення, переконайтесь, що ваш <a>SSL-сертифікат домашнього сервера</a> є довіреним і що розширення браузера не блокує запити.", "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Не вдається підключитись до домашнього серверу - перевірте підключення, переконайтесь, що ваш <a>SSL-сертифікат домашнього сервера</a> є довіреним і що розширення браузера не блокує запити.",
"Cannot add any more widgets": "Неможливо додати більше віджетів", "Cannot add any more widgets": "Неможливо додати більше віджетів",
@ -82,7 +82,7 @@
"This email address is already in use": "Ця е-пошта вже використовується", "This email address is already in use": "Ця е-пошта вже використовується",
"This phone number is already in use": "Цей телефонний номер вже використовується", "This phone number is already in use": "Цей телефонний номер вже використовується",
"Fetching third party location failed": "Не вдалось отримати стороннє місцеперебування", "Fetching third party location failed": "Не вдалось отримати стороннє місцеперебування",
"Messages in one-to-one chats": "Повідомлення у балачках віч-на-віч", "Messages in one-to-one chats": "Повідомлення у бесідах віч-на-віч",
"Send Account Data": "Надіслати дані облікового запису", "Send Account Data": "Надіслати дані облікового запису",
"Advanced notification settings": "Додаткові налаштування сповіщень", "Advanced notification settings": "Додаткові налаштування сповіщень",
"Uploading report": "Завантаження звіту", "Uploading report": "Завантаження звіту",
@ -90,7 +90,7 @@
"Guests can join": "Гості можуть приєднуватися", "Guests can join": "Гості можуть приєднуватися",
"Failed to add tag %(tagName)s to room": "Не вдалось додати до кімнати мітку %(tagName)s", "Failed to add tag %(tagName)s to room": "Не вдалось додати до кімнати мітку %(tagName)s",
"Notification targets": "Цілі сповіщень", "Notification targets": "Цілі сповіщень",
"Failed to set direct chat tag": "Не вдалося встановити мітку прямого чату", "Failed to set direct chat tag": "Не вдалося встановити мітку особистої бесіди",
"Today": "Сьогодні", "Today": "Сьогодні",
"You are not receiving desktop notifications": "Ви не отримуєте системні сповіщення", "You are not receiving desktop notifications": "Ви не отримуєте системні сповіщення",
"Friday": "П'ятниця", "Friday": "П'ятниця",
@ -100,9 +100,9 @@
"Changelog": "Журнал змін", "Changelog": "Журнал змін",
"Waiting for response from server": "Очікується відповідь від сервера", "Waiting for response from server": "Очікується відповідь від сервера",
"Leave": "Вийти", "Leave": "Вийти",
"Send Custom Event": "Відправити приватний захід", "Send Custom Event": "Надіслати не стандартну подію",
"All notifications are currently disabled for all targets.": "Сповіщення для усіх цілей наразі момент вимкнені.", "All notifications are currently disabled for all targets.": "Сповіщення для усіх цілей наразі момент вимкнені.",
"Failed to send logs: ": "Не вдалося відправити журнали: ", "Failed to send logs: ": "Не вдалося надіслати журнали: ",
"Forget": "Забути", "Forget": "Забути",
"World readable": "Відкрито для світу", "World readable": "Відкрито для світу",
"You cannot delete this image. (%(code)s)": "Ви не можете видалити це зображення. (%(code)s)", "You cannot delete this image. (%(code)s)": "Ви не можете видалити це зображення. (%(code)s)",
@ -142,7 +142,7 @@
"Remove %(name)s from the directory?": "Прибрати %(name)s з каталогу?", "Remove %(name)s from the directory?": "Прибрати %(name)s з каталогу?",
"%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s використовує багато новітніх функцій, деякі з яких не доступні або є експериментальними у вашому оглядачі.", "%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s використовує багато новітніх функцій, деякі з яких не доступні або є експериментальними у вашому оглядачі.",
"Developer Tools": "Інструменти розробника", "Developer Tools": "Інструменти розробника",
"Preparing to send logs": ідготовка до відправки журланлу", "Preparing to send logs": риготування до надсилання журланла",
"Unnamed room": "Неназвана кімната", "Unnamed room": "Неназвана кімната",
"Explore Account Data": "Переглянути дані облікового запису", "Explore Account Data": "Переглянути дані облікового запису",
"All messages (noisy)": "Усі повідомлення (гучно)", "All messages (noisy)": "Усі повідомлення (гучно)",
@ -170,10 +170,10 @@
"Call invitation": "Запрошення до виклику", "Call invitation": "Запрошення до виклику",
"Downloading update...": "Завантаженя оновлення…", "Downloading update...": "Завантаженя оновлення…",
"State Key": "Ключ стану", "State Key": "Ключ стану",
"Failed to send custom event.": "Не вдалося відправити приватний захід.", "Failed to send custom event.": "Не вдалося надіслати не стандартну подію.",
"What's new?": "Що нового?", "What's new?": "Що нового?",
"Notify me for anything else": "Сповіщати мене про будь-що інше", "Notify me for anything else": "Сповіщати мене про будь-що інше",
"View Source": "Переглянути джерело", "View Source": "Переглянути код",
"Can't update user notification settings": "Неможливо оновити налаштування користувацьких сповіщень", "Can't update user notification settings": "Неможливо оновити налаштування користувацьких сповіщень",
"Notify for all other messages/rooms": "Сповіщати щодо всіх повідомлень/кімнат", "Notify for all other messages/rooms": "Сповіщати щодо всіх повідомлень/кімнат",
"Unable to look up room ID from server": "Неможливо знайти ID кімнати на сервері", "Unable to look up room ID from server": "Неможливо знайти ID кімнати на сервері",
@ -188,7 +188,7 @@
"Unable to join network": "Неможливо приєднатись до мережі", "Unable to join network": "Неможливо приєднатись до мережі",
"Sorry, your browser is <b>not</b> able to run %(brand)s.": "Вибачте, ваш оглядач <b>не</b> спроможний запустити %(brand)s.", "Sorry, your browser is <b>not</b> able to run %(brand)s.": "Вибачте, ваш оглядач <b>не</b> спроможний запустити %(brand)s.",
"Uploaded on %(date)s by %(user)s": "Завантажено %(date)s користувачем %(user)s", "Uploaded on %(date)s by %(user)s": "Завантажено %(date)s користувачем %(user)s",
"Messages in group chats": "Повідомлення у групових балачках", "Messages in group chats": "Повідомлення у групових бесідах",
"Yesterday": "Вчора", "Yesterday": "Вчора",
"Error encountered (%(errorDetail)s).": "Трапилась помилка (%(errorDetail)s).", "Error encountered (%(errorDetail)s).": "Трапилась помилка (%(errorDetail)s).",
"Low Priority": "Неважливі", "Low Priority": "Неважливі",
@ -205,7 +205,7 @@
"Download this file": "Звантажити цей файл", "Download this file": "Звантажити цей файл",
"Pin Message": "Прикріпити повідомлення", "Pin Message": "Прикріпити повідомлення",
"Failed to change settings": "Не вдалось змінити налаштування", "Failed to change settings": "Не вдалось змінити налаштування",
"Event sent!": "Захід відправлено!", "Event sent!": "Подію надіслано!",
"Unhide Preview": "Відкрити попередній перегляд", "Unhide Preview": "Відкрити попередній перегляд",
"Event Content": "Зміст заходу", "Event Content": "Зміст заходу",
"Thank you!": "Дякуємо!", "Thank you!": "Дякуємо!",
@ -272,7 +272,7 @@
"A call is already in progress!": "Вже здійснюється дзвінок!", "A call is already in progress!": "Вже здійснюється дзвінок!",
"Permission Required": "Потрібен дозвіл", "Permission Required": "Потрібен дозвіл",
"You do not have permission to start a conference call in this room": "У вас немає дозволу, щоб розпочати дзвінок-конференцію в цій кімнаті", "You do not have permission to start a conference call in this room": "У вас немає дозволу, щоб розпочати дзвінок-конференцію в цій кімнаті",
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Зверніть увагу: будь-яка людина, яку ви додаєте до спільноти, буде публічно видимою тим, хто знає ідентифікатор спільноти", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Зверніть увагу: будь-яка людина, яку ви додаєте до спільноти, буде видима усім, хто знає ID спільноти",
"Invite new community members": "Запросити до спільноти", "Invite new community members": "Запросити до спільноти",
"Invite to Community": "Запросити до спільноти", "Invite to Community": "Запросити до спільноти",
"Which rooms would you like to add to this community?": "Які кімнати ви хочете додати до цієї спільноти?", "Which rooms would you like to add to this community?": "Які кімнати ви хочете додати до цієї спільноти?",
@ -287,7 +287,7 @@
"%(brand)s was not given permission to send notifications - please try again": "%(brand)s не має дозволу надсилати сповіщення — будь ласка, спробуйте ще раз", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s не має дозволу надсилати сповіщення — будь ласка, спробуйте ще раз",
"Unable to enable Notifications": "Не вдалося увімкнути сповіщення", "Unable to enable Notifications": "Не вдалося увімкнути сповіщення",
"This email address was not found": "Не знайдено адресу електронної пошти", "This email address was not found": "Не знайдено адресу електронної пошти",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Схоже, ваша адреса електронної пошти не пов'язана з жодним ідентифікатор Matrix на цьому домашньому сервері.", "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Схоже, ваша адреса е-пошти не пов'язана з жодним Matrix ID на цьому домашньому сервері.",
"Restricted": "Обмежено", "Restricted": "Обмежено",
"Moderator": "Модератор", "Moderator": "Модератор",
"Failed to invite": "Не вдалося запросити", "Failed to invite": "Не вдалося запросити",
@ -295,7 +295,7 @@
"You need to be logged in.": "Вам потрібно увійти.", "You need to be logged in.": "Вам потрібно увійти.",
"You need to be able to invite users to do that.": "Щоб це зробити, вам необхідно мати можливість запрошувати людей.", "You need to be able to invite users to do that.": "Щоб це зробити, вам необхідно мати можливість запрошувати людей.",
"Unable to create widget.": "Неможливо створити віджет.", "Unable to create widget.": "Неможливо створити віджет.",
"Missing roomId.": "Бракує ідентифікатора кімнати.", "Missing roomId.": "Бракує ID кімнати.",
"Failed to send request.": "Не вдалося надіслати запит.", "Failed to send request.": "Не вдалося надіслати запит.",
"This room is not recognised.": "Кімнату не знайдено.", "This room is not recognised.": "Кімнату не знайдено.",
"Power level must be positive integer.": "Рівень повноважень мусить бути додатним цілим числом.", "Power level must be positive integer.": "Рівень повноважень мусить бути додатним цілим числом.",
@ -309,9 +309,9 @@
"/ddg is not a command": "/ddg не є командою", "/ddg is not a command": "/ddg не є командою",
"To use it, just wait for autocomplete results to load and tab through them.": "Щоб цим скористатися, просто почекайте на підказки автодоповнення й перемикайтеся між ними клавішею TAB.", "To use it, just wait for autocomplete results to load and tab through them.": "Щоб цим скористатися, просто почекайте на підказки автодоповнення й перемикайтеся між ними клавішею TAB.",
"Changes your display nickname": "Змінює ваш нік", "Changes your display nickname": "Змінює ваш нік",
"Invites user with given id to current room": "Запрошує користувача з вказаним ідентифікатором до кімнати", "Invites user with given id to current room": "Запрошує користувача зі вказаним ID до кімнати",
"Leave room": "Залишити кімнату", "Leave room": "Залишити кімнату",
"Kicks user with given id": "Викидає з кімнати користувача з вказаним ідентифікатором", "Kicks user with given id": "Викидає з кімнати користувача зі вказаним ID",
"Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи його повідомлення від вас", "Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи його повідомлення від вас",
"Ignored user": "Зігнорований користувач", "Ignored user": "Зігнорований користувач",
"You are now ignoring %(userId)s": "Ви ігноруєте %(userId)s", "You are now ignoring %(userId)s": "Ви ігноруєте %(userId)s",
@ -319,7 +319,7 @@
"Unignored user": "Припинено ігнорування користувача", "Unignored user": "Припинено ігнорування користувача",
"You are no longer ignoring %(userId)s": "Ви більше не ігноруєте %(userId)s", "You are no longer ignoring %(userId)s": "Ви більше не ігноруєте %(userId)s",
"Define the power level of a user": "Вказати рівень повноважень користувача", "Define the power level of a user": "Вказати рівень повноважень користувача",
"Deops user with given id": "Знімає права оператора з користувача з вказаним ідентифікатором", "Deops user with given id": "Знімає права оператора з користувача зі вказаним ID",
"Opens the Developer Tools dialog": "Відкриває вікно інструментів розробника", "Opens the Developer Tools dialog": "Відкриває вікно інструментів розробника",
"Verified key": "Звірений ключ", "Verified key": "Звірений ключ",
"Displays action": "Показ дій", "Displays action": "Показ дій",
@ -340,8 +340,8 @@
"%(senderName)s kicked %(targetName)s.": "%(senderName)s викинув/ла %(targetName)s.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s викинув/ла %(targetName)s.",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s відкликав/ла запрошення %(targetName)s.", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s відкликав/ла запрошення %(targetName)s.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s надіслав(-ла) зображення.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s надіслав(-ла) зображення.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s призначив(-ла) основну адресу цієї кімнати: %(address)s.", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s встановлює основною адресою цієї кімнати %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s прибрав(-ла) основу адресу цієї кімнати.", "%(senderName)s removed the main address for this room.": "%(senderName)s вилучає основу адресу цієї кімнати.",
"Someone": "Хтось", "Someone": "Хтось",
"(not supported by this browser)": "(не підтримується цією веб-переглядачкою)", "(not supported by this browser)": "(не підтримується цією веб-переглядачкою)",
"(could not connect media)": "(не можливо під'єднати медіа)", "(could not connect media)": "(не можливо під'єднати медіа)",
@ -357,7 +357,7 @@
"%(senderName)s made future room history visible to anyone.": "%(senderName)s зробив(-ла) майбутню історію кімнати видимою для всіх.", "%(senderName)s made future room history visible to anyone.": "%(senderName)s зробив(-ла) майбутню історію кімнати видимою для всіх.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s зробив(-ла) майбутню історію видимою для невідомого значення (%(visibility)s).", "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s зробив(-ла) майбутню історію видимою для невідомого значення (%(visibility)s).",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s з %(fromPowerLevel)s до %(toPowerLevel)s", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s з %(fromPowerLevel)s до %(toPowerLevel)s",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s змінює приколоті повідомлення у кімнаті.", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s змінює прикріплені повідомлення у кімнаті.",
"%(widgetName)s widget modified by %(senderName)s": "%(senderName)s змінює знадіб %(widgetName)s", "%(widgetName)s widget modified by %(senderName)s": "%(senderName)s змінює знадіб %(widgetName)s",
"%(widgetName)s widget added by %(senderName)s": "%(senderName)s додав(-ла) знадіб %(widgetName)s", "%(widgetName)s widget added by %(senderName)s": "%(senderName)s додав(-ла) знадіб %(widgetName)s",
"%(widgetName)s widget removed by %(senderName)s": "%(senderName)s вилучив(-ла) знадіб %(widgetName)s", "%(widgetName)s widget removed by %(senderName)s": "%(senderName)s вилучив(-ла) знадіб %(widgetName)s",
@ -432,7 +432,7 @@
"Demote": "Знизити рівень прав", "Demote": "Знизити рівень прав",
"Failed to mute user": "Не вдалося заглушити користувача", "Failed to mute user": "Не вдалося заглушити користувача",
"Failed to change power level": "Не вдалося змінити рівень повноважень", "Failed to change power level": "Не вдалося змінити рівень повноважень",
"Chat with %(brand)s Bot": алачка з %(brand)s-ботом", "Chat with %(brand)s Bot": есіда з %(brand)s-ботом",
"Whether or not you're logged in (we don't record your username)": "Незалежно від того, увійшли ви чи ні (ми не записуємо ваше ім'я користувача)", "Whether or not you're logged in (we don't record your username)": "Незалежно від того, увійшли ви чи ні (ми не записуємо ваше ім'я користувача)",
"The file '%(fileName)s' failed to upload.": "Файл '%(fileName)s' не вийшло відвантажити.", "The file '%(fileName)s' failed to upload.": "Файл '%(fileName)s' не вийшло відвантажити.",
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Файл '%(fileName)s' перевищує ліміт розміру для відвантажень домашнього сервера", "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Файл '%(fileName)s' перевищує ліміт розміру для відвантажень домашнього сервера",
@ -470,7 +470,7 @@
"Sets the room name": "Встановлює назву кімнати", "Sets the room name": "Встановлює назву кімнати",
"Use an identity server": "Використовувати сервер ідентифікації", "Use an identity server": "Використовувати сервер ідентифікації",
"Use an identity server to invite by email. Manage in Settings.": "Використовувати сервер ідентифікації для запрошень через е-пошту. Керуйте у налаштуваннях.", "Use an identity server to invite by email. Manage in Settings.": "Використовувати сервер ідентифікації для запрошень через е-пошту. Керуйте у налаштуваннях.",
"Unbans user with given ID": "Розблоковує користувача з вказаним ідентифікатором", "Unbans user with given ID": "Розблоковує користувача зі вказаним ID",
"Adds a custom widget by URL to the room": "Додає власний віджет до кімнати за посиланням", "Adds a custom widget by URL to the room": "Додає власний віджет до кімнати за посиланням",
"Please supply a https:// or http:// widget URL": "Вкажіть посилання на віджет — https:// або http://", "Please supply a https:// or http:// widget URL": "Вкажіть посилання на віджет — https:// або http://",
"You cannot modify widgets in this room.": "Ви не можете змінювати віджети у цій кімнаті.", "You cannot modify widgets in this room.": "Ви не можете змінювати віджети у цій кімнаті.",
@ -514,7 +514,7 @@
"Liberate your communication": "Вивільни своє спілкування", "Liberate your communication": "Вивільни своє спілкування",
"Send a Direct Message": "Надіслати особисте повідомлення", "Send a Direct Message": "Надіслати особисте повідомлення",
"Explore Public Rooms": "Дослідити прилюдні кімнати", "Explore Public Rooms": "Дослідити прилюдні кімнати",
"Create a Group Chat": "Створити групову балачку", "Create a Group Chat": "Створити групову бесіду",
"Explore": "Дослідити", "Explore": "Дослідити",
"Filter": "Фільтрувати", "Filter": "Фільтрувати",
"Filter rooms…": "Фільтрувати кімнати…", "Filter rooms…": "Фільтрувати кімнати…",
@ -562,7 +562,7 @@
"Legal": "Правова інформація", "Legal": "Правова інформація",
"Credits": "Подяки", "Credits": "Подяки",
"For help with using %(brand)s, click <a>here</a>.": "Якщо необхідна допомога у користуванні %(brand)s'ом, клацніть <a>тут</a>.", "For help with using %(brand)s, click <a>here</a>.": "Якщо необхідна допомога у користуванні %(brand)s'ом, клацніть <a>тут</a>.",
"For help with using %(brand)s, click <a>here</a> or start a chat with our bot using the button below.": "Якщо необхідна допомога у користуванні %(brand)s'ом, клацніть <a>тут</a> або розпочніть балачку з нашим ботом, клацнувши на кнопці нижче.", "For help with using %(brand)s, click <a>here</a> or start a chat with our bot using the button below.": "Якщо необхідна допомога у користуванні %(brand)s, клацніть <a>тут</a> або розпочніть бесіду з нашим ботом, клацнувши на кнопку внизу.",
"Join the conversation with an account": "Приєднатись до бесіди з обліковим записом", "Join the conversation with an account": "Приєднатись до бесіди з обліковим записом",
"Unable to restore session": "Не вдалося відновити сеанс", "Unable to restore session": "Не вдалося відновити сеанс",
"We encountered an error trying to restore your previous session.": "Ми натрапили на помилку, намагаючись відновити ваш попередній сеанс.", "We encountered an error trying to restore your previous session.": "Ми натрапили на помилку, намагаючись відновити ваш попередній сеанс.",
@ -578,9 +578,9 @@
"Forget this room": "Забути цю кімнату", "Forget this room": "Забути цю кімнату",
"Re-join": "Перепід'єднатись", "Re-join": "Перепід'єднатись",
"This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Це запрошення до %(roomName)s було надіслане на %(email)s, яка не пов'язана з вашим обліковим записом", "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Це запрошення до %(roomName)s було надіслане на %(email)s, яка не пов'язана з вашим обліковим записом",
"Link this email with your account in Settings to receive invites directly in %(brand)s.": "Зв'яжіть цю е-пошту з вашим обліковим записом у Налаштуваннях щоб отримувати сповіщення прямо у %(brand)s.", "Link this email with your account in Settings to receive invites directly in %(brand)s.": "Пов'яжіть цю е-пошту з вашим обліковим записом у Налаштуваннях, щоб отримувати запрошення безпосередньо в %(brand)s.",
"This invite to %(roomName)s was sent to %(email)s": "Це запрошення до %(roomName)s було надіслане на %(email)s", "This invite to %(roomName)s was sent to %(email)s": "Це запрошення до %(roomName)s було надіслане на %(email)s",
"Use an identity server in Settings to receive invites directly in %(brand)s.": "Використовувати сервер ідентифікації у Налаштуваннях щоб отримувати запрошення прямо у %(brand)s.", "Use an identity server in Settings to receive invites directly in %(brand)s.": "Використовувати сервер ідентифікації у Налаштуваннях, щоб отримувати запрошення безпосередньо в %(brand)s.",
"Are you sure you want to deactivate your account? This is irreversible.": "Ви впевнені у тому, що бажаєте знедіяти ваш обліковий запис? Ця дія безповоротна.", "Are you sure you want to deactivate your account? This is irreversible.": "Ви впевнені у тому, що бажаєте знедіяти ваш обліковий запис? Ця дія безповоротна.",
"Confirm account deactivation": "Підтвердьте знедіювання облікового запису", "Confirm account deactivation": "Підтвердьте знедіювання облікового запису",
"To continue, please enter your password:": "Щоб продовжити, введіть, будь ласка, ваш пароль:", "To continue, please enter your password:": "Щоб продовжити, введіть, будь ласка, ваш пароль:",
@ -627,17 +627,17 @@
"Sends a message as html, without interpreting it as markdown": "Надсилає повідомлення у вигляді HTML, не інтерпретуючи його як розмітку", "Sends a message as html, without interpreting it as markdown": "Надсилає повідомлення у вигляді HTML, не інтерпретуючи його як розмітку",
"Failed to set topic": "Не вдалося встановити тему", "Failed to set topic": "Не вдалося встановити тему",
"Once enabled, encryption cannot be disabled.": "Після увімкнення шифрування не можна буде вимкнути.", "Once enabled, encryption cannot be disabled.": "Після увімкнення шифрування не можна буде вимкнути.",
"Please enter verification code sent via text.": "Будь ласка, введіть звірювальний код, відправлений у текстовому повідомленні.", "Please enter verification code sent via text.": "Введіть код перевірки, надісланий у текстовому повідомленні.",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Текстове повідомлення було відправлено на номер +%(msisdn)s. Будь ласка, введіть звірювальний код, який воно містить.", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Текстове повідомлення надіслано на номер +%(msisdn)s. Введіть код перевірки з нього.",
"Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Повідомлення у цій кімнаті захищені наскрізним шифруванням. Тільки ви та одержувачі мають ключі для прочитання цих повідомлень.", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Повідомлення у цій кімнаті захищені наскрізним шифруванням. Тільки ви та одержувачі мають ключі для прочитання цих повідомлень.",
"Messages in this room are end-to-end encrypted.": "Повідомлення у цій кімнаті наскрізно зашифровані.", "Messages in this room are end-to-end encrypted.": "Повідомлення у цій кімнаті наскрізно зашифровані.",
"Messages in this room are not end-to-end encrypted.": "Повідомлення у цій кімнаті не захищено наскрізним шифруванням.", "Messages in this room are not end-to-end encrypted.": "Повідомлення у цій кімнаті не захищено наскрізним шифруванням.",
"Encryption enabled": "Шифрування увімкнено", "Encryption enabled": "Шифрування увімкнено",
"Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Повідомлення у цій кімнаті наскрізно зашифровані. Дізнайтеся більше та звіртеся з цим користувачем через його профіль.", "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Повідомлення у цій кімнаті наскрізно зашифровані. Дізнайтеся більше та звіртеся з цим користувачем через його профіль.",
"You sent a verification request": "Ви відправили звірювальний запит", "You sent a verification request": "Ви надіслали запит перевірки",
"Direct Messages": "Особисті повідомлення", "Direct Messages": "Особисті повідомлення",
"Room Settings - %(roomName)s": "Налаштування кімнати - %(roomName)s", "Room Settings - %(roomName)s": "Налаштування кімнати - %(roomName)s",
"A verification email will be sent to your inbox to confirm setting your new password.": "Ми відправимо перевіряльний електронний лист до вас для підтвердження зміни пароля.", "A verification email will be sent to your inbox to confirm setting your new password.": "Ми надішлемо вам електронний лист перевірки для підтвердження зміни пароля.",
"To return to your account in future you need to set a password": "Щоб повернутися до своєї обліківки в майбутньому, вам потрібно встановити пароль", "To return to your account in future you need to set a password": "Щоб повернутися до своєї обліківки в майбутньому, вам потрібно встановити пароль",
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Використовувати сервер ідентифікації, щоб запрошувати через е-пошту. Натисніть \"Продовжити\", щоб використовувати типовий сервер ідентифікації (%(defaultIdentityServerName)s) або змініть його у налаштуваннях.", "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Використовувати сервер ідентифікації, щоб запрошувати через е-пошту. Натисніть \"Продовжити\", щоб використовувати типовий сервер ідентифікації (%(defaultIdentityServerName)s) або змініть його у налаштуваннях.",
"Joins room with given address": "Приєднатися до кімнати зі вказаною адресою", "Joins room with given address": "Приєднатися до кімнати зі вказаною адресою",
@ -655,7 +655,7 @@
"Displays list of commands with usages and descriptions": "Відбиває перелік команд із прикладами вжитку та описом", "Displays list of commands with usages and descriptions": "Відбиває перелік команд із прикладами вжитку та описом",
"Displays information about a user": "Показує відомості про користувача", "Displays information about a user": "Показує відомості про користувача",
"Send a bug report with logs": "Надіслати звіт про ваду разом з журналами", "Send a bug report with logs": "Надіслати звіт про ваду разом з журналами",
"Opens chat with the given user": "Відкриває балачку з вказаним користувачем", "Opens chat with the given user": "Відкриває бесіду з вказаним користувачем",
"Sends a message to the given user": "Надсилає повідомлення вказаному користувачеві", "Sends a message to the given user": "Надсилає повідомлення вказаному користувачеві",
"%(senderName)s made no change.": "%(senderName)s не запровадив(-ла) жодних змін.", "%(senderName)s made no change.": "%(senderName)s не запровадив(-ла) жодних змін.",
"%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s змінює назву кімнати з %(oldRoomName)s на %(newRoomName)s.", "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s змінює назву кімнати з %(oldRoomName)s на %(newRoomName)s.",
@ -668,10 +668,10 @@
"%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s змінює гостьовий доступ на \"%(rule)s\"", "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s змінює гостьовий доступ на \"%(rule)s\"",
"%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s увімкнув(-ла) значок для %(groups)s у цій кімнаті.", "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s увімкнув(-ла) значок для %(groups)s у цій кімнаті.",
"%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s вимкнув(-ла) значок для %(groups)s в цій кімнаті.", "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s вимкнув(-ла) значок для %(groups)s в цій кімнаті.",
"%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s додав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s додає альтернативні адреси %(addresses)s для цієї кімнати.",
"%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s додав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s додає альтернативні адреси %(addresses)s для цієї кімнати.",
"%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s прибрав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s вилучає альтернативні адреси %(addresses)s для цієї кімнати.",
"%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s прибрав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.", "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s вилучає альтернативні адреси %(addresses)s для цієї кімнати.",
"%(senderName)s changed the alternative addresses for this room.": "%(senderName)s змінює альтернативні адреси для цієї кімнати.", "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s змінює альтернативні адреси для цієї кімнати.",
"%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s змінює головні та альтернативні адреси для цієї кімнати.", "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s змінює головні та альтернативні адреси для цієї кімнати.",
"%(senderName)s changed the addresses for this room.": "%(senderName)s змінює адреси для цієї кімнати.", "%(senderName)s changed the addresses for this room.": "%(senderName)s змінює адреси для цієї кімнати.",
@ -680,18 +680,18 @@
"%(senderName)s placed a video call.": "%(senderName)s розпочинає відеовиклик.", "%(senderName)s placed a video call.": "%(senderName)s розпочинає відеовиклик.",
"%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s розпочинає відеовиклик. (не підтримується цим переглядачем)", "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s розпочинає відеовиклик. (не підтримується цим переглядачем)",
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s відкликав(-ла) запрошення %(targetDisplayName)s приєднання до кімнати.", "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s відкликав(-ла) запрошення %(targetDisplayName)s приєднання до кімнати.",
"%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування користувачів зі збігом з %(glob)s", "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s вилучає правило заборони користувачів зі збігом з %(glob)s",
"%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування кімнат зі збігом з %(glob)s", "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s вилучає правило блокування кімнат зі збігом з %(glob)s",
"%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування серверів зі збігом з %(glob)s", "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s вилучає правило блокування серверів зі збігом з %(glob)s",
"%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s видалив(-ла) правило блокування зі збігом з %(glob)s", "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s вилучає правило блокування зі збігом з %(glob)s",
"%(senderName)s updated an invalid ban rule": "%(senderName)s оновив(-ла) хибне правило блокування", "%(senderName)s updated an invalid ban rule": "%(senderName)s оновлює хибне правило блокування",
"%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування користувачів зі збігом з %(glob)s через %(reason)s", "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування користувачів зі збігом з %(glob)s через %(reason)s",
"%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування кімнат зі збігом з %(glob)s через %(reason)s", "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування кімнат зі збігом з %(glob)s через %(reason)s",
"%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування серверів зі збігом з %(glob)s через %(reason)s", "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування серверів зі збігом з %(glob)s через %(reason)s",
"%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s оновив(-ла) правило блокування зі збігом з %(glob)s через %(reason)s", "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s оновлює правило блокування зі збігом з %(glob)s через %(reason)s",
"%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування користувачів зі збігом з %(glob)s через %(reason)s", "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування користувачів зі збігом з %(glob)s через %(reason)s",
"%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування кімнат зі збігом з %(glob)s через %(reason)s", "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування кімнат зі збігом з %(glob)s через %(reason)s",
"%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування серверів зі збігом з %(glob)s через %(reason)s", "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування серверів зі збігом з %(glob)s через %(reason)s",
"Light": "Світла", "Light": "Світла",
"Dark": "Темна", "Dark": "Темна",
"You signed in to a new session without verifying it:": "Ви увійшли в новий сеанс, не підтвердивши його:", "You signed in to a new session without verifying it:": "Ви увійшли в новий сеанс, не підтвердивши його:",
@ -731,7 +731,7 @@
"You do not have permission to invite people to this room.": "У вас немає прав запрошувати людей у цю кімнату.", "You do not have permission to invite people to this room.": "У вас немає прав запрошувати людей у цю кімнату.",
"User %(userId)s is already in the room": "Користувач %(userId)s вже перебуває в кімнаті", "User %(userId)s is already in the room": "Користувач %(userId)s вже перебуває в кімнаті",
"User %(user_id)s does not exist": "Користувача %(user_id)s не існує", "User %(user_id)s does not exist": "Користувача %(user_id)s не існує",
"The user must be unbanned before they can be invited.": "Користувач має бути розблокованим(ою), перед тим як може бути запрошений(ая).", "The user must be unbanned before they can be invited.": "Потрібно розблокувати користувача перед тим як їх можна буде запросити.",
"The user's homeserver does not support the version of the room.": "Домашній сервер користувача не підтримує версію кімнати.", "The user's homeserver does not support the version of the room.": "Домашній сервер користувача не підтримує версію кімнати.",
"Unknown server error": "Невідома помилка з боку сервера", "Unknown server error": "Невідома помилка з боку сервера",
"Use a few words, avoid common phrases": "Використовуйте декілька слів, уникайте звичайних фраз", "Use a few words, avoid common phrases": "Використовуйте декілька слів, уникайте звичайних фраз",
@ -824,7 +824,7 @@
"Error adding ignored user/server": "Помилка при додаванні ігнорованого користувача/сервера", "Error adding ignored user/server": "Помилка при додаванні ігнорованого користувача/сервера",
"Something went wrong. Please try again or view your console for hints.": "Щось пішло не так. Спробуйте знову, або пошукайте підказки в консолі.", "Something went wrong. Please try again or view your console for hints.": "Щось пішло не так. Спробуйте знову, або пошукайте підказки в консолі.",
"Error subscribing to list": "Помилка при підписці на список", "Error subscribing to list": "Помилка при підписці на список",
"Please verify the room ID or address and try again.": "Перевірте ID кімнати, або адресу та попробуйте знову.", "Please verify the room ID or address and try again.": "Перевірте ID кімнати, або адресу та повторіть спробу.",
"Error removing ignored user/server": "Помилка при видаленні ігнорованого користувача/сервера", "Error removing ignored user/server": "Помилка при видаленні ігнорованого користувача/сервера",
"Error unsubscribing from list": "Не вдалося відписатися від списку", "Error unsubscribing from list": "Не вдалося відписатися від списку",
"Please try again or view your console for hints.": "Спробуйте знову, або подивіться повідомлення в консолі.", "Please try again or view your console for hints.": "Спробуйте знову, або подивіться повідомлення в консолі.",
@ -838,16 +838,16 @@
"View rules": "Подивитись правила", "View rules": "Подивитись правила",
"You are currently subscribed to:": "Ви підписані на:", "You are currently subscribed to:": "Ви підписані на:",
"⚠ These settings are meant for advanced users.": "⚠ Ці налаштування розраховані на досвідчених користувачів.", "⚠ These settings are meant for advanced users.": "⚠ Ці налаштування розраховані на досвідчених користувачів.",
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ігнорування людей реалізовано через списки правил блокування. Підписка на список блокування призведе до приховування від вас користувачів і серверів, які в ньому перераховані.", "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Нехтування людей реалізовано через списки правил блокування. Підписка на список блокування призведе до приховування від вас перелічених у ньому користувачів і серверів.",
"Personal ban list": "Особистий бан-ліст", "Personal ban list": "Особистий список блокування",
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Ваш особистий список блокування містить всіх користувачів і сервери, повідомлення яких ви не хочете бачити. Після внесення туди першого користувача / сервера в списку кімнат з'явиться нова кімната 'Мій список блокування' - не покидає цю кімнату, щоб список блокування працював.", "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Ваш особистий список блокування містить усіх користувачів і сервери, повідомлення яких ви не хочете бачити. Після внесення туди першого користувача/сервера в списку кімнат з'явиться нова кімната «Мій список блокування» — не залишайте цю кімнату, щоб список блокування працював.",
"Server or user ID to ignore": "Сервер або ID користувача для ігнорування", "Server or user ID to ignore": "Сервер або ID користувача для ігнорування",
"eg: @bot:* or example.org": "наприклад: @bot:* або example.org", "eg: @bot:* or example.org": "наприклад: @bot:* або example.org",
"Ignore": "Ігнорувати", "Ignore": "Ігнорувати",
"Subscribed lists": "Підписані списки", "Subscribed lists": "Підписані списки",
"Subscribing to a ban list will cause you to join it!": ри підписці на список блокування ви приєднаєтесь до нього!", "Subscribing to a ban list will cause you to join it!": ідписавшись на список блокування ви приєднаєтесь до нього!",
"If this isn't what you want, please use a different tool to ignore users.": "Якщо вас це не влаштовує, спробуйте інший інструмент для ігнорування користувачів.", "If this isn't what you want, please use a different tool to ignore users.": "Якщо вас це не влаштовує, спробуйте інший інструмент для ігнорування користувачів.",
"Room ID or address of ban list": "Ідентифікатор номера або адреса бан-лісту", "Room ID or address of ban list": "ID кімнати або адреса списку блокування",
"Subscribe": "Підписатись", "Subscribe": "Підписатись",
"Start automatically after system login": "Автозапуск при вході в систему", "Start automatically after system login": "Автозапуск при вході в систему",
"Always show the window menu bar": "Завжди показувати рядок меню", "Always show the window menu bar": "Завжди показувати рядок меню",
@ -855,7 +855,7 @@
"Preferences": "Параметри", "Preferences": "Параметри",
"Room list": "Перелік кімнат", "Room list": "Перелік кімнат",
"Composer": "Редактор", "Composer": "Редактор",
"Security & Privacy": "Безпека та конфіденційність", "Security & Privacy": "Безпека й приватність",
"Where youre logged in": "Де ви ввійшли", "Where youre logged in": "Де ви ввійшли",
"Skip": "Пропустити", "Skip": "Пропустити",
"Notification settings": "Налаштування сповіщень", "Notification settings": "Налаштування сповіщень",
@ -869,7 +869,7 @@
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (повноваження %(powerLevelNumber)s)", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (повноваження %(powerLevelNumber)s)",
"Send a message…": "Надіслати повідомлення…", "Send a message…": "Надіслати повідомлення…",
"People": "Люди", "People": "Люди",
"Share this email in Settings to receive invites directly in %(brand)s.": "Поширте цю адресу е-пошти у налаштуваннях щоб отримувати запрошення прямо у %(brand)s.", "Share this email in Settings to receive invites directly in %(brand)s.": "Поширте цю адресу е-пошти у налаштуваннях, щоб отримувати запрошення безпосередньо в %(brand)s.",
"Room options": "Параметри кімнати", "Room options": "Параметри кімнати",
"Send as message": "Надіслати як повідомлення", "Send as message": "Надіслати як повідомлення",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Ви не зможете скасувати цю зміну через те, що ви підвищуєте рівень повноважень користувача до свого рівня.", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Ви не зможете скасувати цю зміну через те, що ви підвищуєте рівень повноважень користувача до свого рівня.",
@ -895,11 +895,11 @@
"Cat": "Кіт", "Cat": "Кіт",
"Lion": "Лев", "Lion": "Лев",
"Horse": "Кінь", "Horse": "Кінь",
"Pig": "Свиня", "Pig": "Порося",
"Elephant": "Слон", "Elephant": "Слон",
"Rabbit": "Кріль", "Rabbit": "Кріль",
"Panda": "Панда", "Panda": "Панда",
"Rooster": "Когут", "Rooster": "Півень",
"Penguin": "Пінгвін", "Penguin": "Пінгвін",
"Turtle": "Черепаха", "Turtle": "Черепаха",
"Fish": "Риба", "Fish": "Риба",
@ -935,11 +935,11 @@
"Bicycle": "Велоcипед", "Bicycle": "Велоcипед",
"Aeroplane": "Літак", "Aeroplane": "Літак",
"Rocket": "Ракета", "Rocket": "Ракета",
"Trophy": "Приз", "Trophy": "Кубок",
"Ball": "М'яч", "Ball": "М'яч",
"Guitar": "Гітара", "Guitar": "Гітара",
"Trumpet": "Труба", "Trumpet": "Труба",
"Bell": "Дзвін", "Bell": "Дзвінок",
"Anchor": "Якір", "Anchor": "Якір",
"Headphones": "Навушники", "Headphones": "Навушники",
"Folder": "Тека", "Folder": "Тека",
@ -1047,24 +1047,24 @@
"Revoke invite": "Відкликати запрошення", "Revoke invite": "Відкликати запрошення",
"Security": "Безпека", "Security": "Безпека",
"Report bugs & give feedback": "Відзвітувати про вади та залишити відгук", "Report bugs & give feedback": "Відзвітувати про вади та залишити відгук",
"Report Content to Your Homeserver Administrator": "Поскаржитись на зміст адміністратору вашого домашнього сервера", "Report Content to Your Homeserver Administrator": "Поскаржитися на вміст адміністратору вашого домашнього сервера",
"Failed to upgrade room": "Не вдалось поліпшити кімнату", "Failed to upgrade room": "Не вдалось поліпшити кімнату",
"The room upgrade could not be completed": "Поліпшення кімнати не може бути завершене", "The room upgrade could not be completed": "Поліпшення кімнати не може бути завершене",
"Upgrade this room to version %(version)s": "Поліпшити цю кімнату до версії %(version)s", "Upgrade this room to version %(version)s": "Поліпшити цю кімнату до версії %(version)s",
"Upgrade Room Version": "Поліпшити версію кімнати", "Upgrade Room Version": "Поліпшити версію кімнати",
"Upgrade private room": "Поліпшити закриту кімнату", "Upgrade private room": "Поліпшити закриту кімнату",
"You'll upgrade this room from <oldVersion /> to <newVersion />.": "Ви поліпшите цю кімнату з <oldVersion /> до <newVersion /> версії.", "You'll upgrade this room from <oldVersion /> to <newVersion />.": "Ви поліпшите цю кімнату з <oldVersion /> до <newVersion /> версії.",
"Share Room Message": "Поширити повідомлення кімнати", "Share Room Message": "Поділитися повідомленням кімнати",
"Report Content": "Поскаржитись на зміст", "Report Content": "Поскаржитись на вміст",
"Feedback": "Зворотній зв'язок", "Feedback": "Зворотній зв'язок",
"General failure": "Загальний збій", "General failure": "Загальний збій",
"Enter your account password to confirm the upgrade:": "Введіть пароль вашого облікового запису щоб підтвердити поліпшення:", "Enter your account password to confirm the upgrade:": "Введіть пароль вашого облікового запису щоб підтвердити поліпшення:",
"Security & privacy": "Безпека та конфіденційність", "Security & privacy": "Безпека й приватність",
"Secret storage public key:": "Таємне сховище відкритого ключа:", "Secret storage public key:": "Таємне сховище відкритого ключа:",
"Key backup": "Резервне копіювання ключів", "Key backup": "Резервне копіювання ключів",
"Message search": "Пошук повідомлень", "Message search": "Пошук повідомлень",
"Cross-signing": "Перехресне підписування", "Cross-signing": "Перехресне підписування",
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Адміністратором вашого сервера було вимкнено початкове наскрізне шифрування у закритих кімнатах та особистих повідомленнях.", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Адміністратором вашого сервера було вимкнено автоматичне наскрізне шифрування у приватних кімнатах і особистих повідомленнях.",
"Something went wrong!": "Щось пішло не так!", "Something went wrong!": "Щось пішло не так!",
"expand": "розгорнути", "expand": "розгорнути",
"Wrong Recovery Key": "Неправильний відновлювальний ключ", "Wrong Recovery Key": "Неправильний відновлювальний ключ",
@ -1102,7 +1102,7 @@
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Поліпште цей сеанс щоб уможливити звіряння інших сеансів, надаючи їм доступ до зашифрованих повідомлень та позначуючи їх довіреними для інших користувачів.", "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Поліпште цей сеанс щоб уможливити звіряння інших сеансів, надаючи їм доступ до зашифрованих повідомлень та позначуючи їх довіреними для інших користувачів.",
"Upgrade your encryption": "Поліпшити ваше шифрування", "Upgrade your encryption": "Поліпшити ваше шифрування",
"Show a placeholder for removed messages": "Показувати замісну позначку замість видалених повідомлень", "Show a placeholder for removed messages": "Показувати замісну позначку замість видалених повідомлень",
"Show join/leave messages (invites/kicks/bans unaffected)": "Показувати повідомлення про приєднання/залишення (не впливає на запрошення/викидання/заборону)", "Show join/leave messages (invites/kicks/bans unaffected)": "Показувати повідомлення про приєднання/залишення (не впливає на запрошення/викидання/блокування)",
"Show avatar changes": "Показувати зміни личини", "Show avatar changes": "Показувати зміни личини",
"Show display name changes": "Показувати зміни видимого імені", "Show display name changes": "Показувати зміни видимого імені",
"Show read receipts sent by other users": "Показувати мітки прочитання, надіслані іншими користувачами", "Show read receipts sent by other users": "Показувати мітки прочитання, надіслані іншими користувачами",
@ -1112,8 +1112,8 @@
"Never send encrypted messages to unverified sessions in this room from this session": "Ніколи не надсилати зашифровані повідомлення до незвірених сеансів у цій кімнаті з цього сеансу", "Never send encrypted messages to unverified sessions in this room from this session": "Ніколи не надсилати зашифровані повідомлення до незвірених сеансів у цій кімнаті з цього сеансу",
"Enable message search in encrypted rooms": "Увімкнути шукання повідомлень у зашифрованих кімнатах", "Enable message search in encrypted rooms": "Увімкнути шукання повідомлень у зашифрованих кімнатах",
"IRC display name width": "Ширина видимого імені IRC", "IRC display name width": "Ширина видимого імені IRC",
"Encrypted messages in one-to-one chats": "Зашифровані повідомлення у балачках віч-на-віч", "Encrypted messages in one-to-one chats": "Зашифровані повідомлення у бесідах віч-на-віч",
"Encrypted messages in group chats": "Зашифровані повідомлення у групових балачках", "Encrypted messages in group chats": "Зашифровані повідомлення у групових бесідах",
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Захищені повідомлення з цим користувачем є наскрізно зашифрованими та непрочитними для сторонніх осіб.", "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Захищені повідомлення з цим користувачем є наскрізно зашифрованими та непрочитними для сторонніх осіб.",
"Securely cache encrypted messages locally for them to appear in search results.": "Безпечно локально кешувати зашифровані повідомлення щоб вони з'являлись у результатах пошуку.", "Securely cache encrypted messages locally for them to appear in search results.": "Безпечно локально кешувати зашифровані повідомлення щоб вони з'являлись у результатах пошуку.",
"%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.": "%(brand)s'ові бракує деяких складників, необхідних для безпечного локального кешування зашифрованих повідомлень. Якщо ви хочете поекспериментувати з цією властивістю, зберіть спеціальну збірку %(brand)s Desktop із <nativeLink>доданням пошукових складників</nativeLink>.", "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.": "%(brand)s'ові бракує деяких складників, необхідних для безпечного локального кешування зашифрованих повідомлень. Якщо ви хочете поекспериментувати з цією властивістю, зберіть спеціальну збірку %(brand)s Desktop із <nativeLink>доданням пошукових складників</nativeLink>.",
@ -1152,12 +1152,12 @@
"I don't want my encrypted messages": "Мені не потрібні мої зашифровані повідомлення", "I don't want my encrypted messages": "Мені не потрібні мої зашифровані повідомлення",
"You'll lose access to your encrypted messages": "Ви втратите доступ до ваших зашифрованих повідомлень", "You'll lose access to your encrypted messages": "Ви втратите доступ до ваших зашифрованих повідомлень",
"Use this session to verify your new one, granting it access to encrypted messages:": "Використати цей сеанс для звірення вашого нового сеансу, надаючи йому доступ до зашифрованих повідомлень:", "Use this session to verify your new one, granting it access to encrypted messages:": "Використати цей сеанс для звірення вашого нового сеансу, надаючи йому доступ до зашифрованих повідомлень:",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Скарження на це повідомлення надішле його унікальний 'ідентифікатор події (event ID)' адміністраторові вашого домашнього сервера. Якщо повідомлення у цій кімнаті зашифровані, то адміністратор не зможе бачити ані тексту повідомлень, ані жодних файлів чи зображень.", "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Зі скаргою на це повідомлення буде надіслано його унікальний «ID події» адміністраторові вашого домашнього сервера. Якщо повідомлення у цій кімнаті зашифровані, то адміністратор не зможе побачити ані тексту повідомлень, ані жодних файлів чи зображень.",
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Бракує деяких даних сеансу, включно з ключами зашифрованих повідомлень. Вийдіть та зайдіть знову щоб виправити цю проблему, відновлюючи ключі з дубля.", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Бракує деяких даних сеансу, включно з ключами зашифрованих повідомлень. Вийдіть та зайдіть знову щоб виправити цю проблему, відновлюючи ключі з дубля.",
"Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Було виявлено дані зі старої версії %(brand)s. Це призведе до збоїння наскрізного шифрування у старій версії. Наскрізно зашифровані повідомлення, що обмінювані нещодавно, під час використання старої версії, можуть бути недешифровними у цій версії. Це може призвести до збоїв повідомлень, обмінюваних також і з цією версією. У разі виникнення проблем вийдіть з програми та зайдіть знову. Задля збереження історії повідомлень експортуйте та переімпортуйте ваші ключі.", "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Було виявлено дані зі старої версії %(brand)s. Це призведе до збоїння наскрізного шифрування у старій версії. Наскрізно зашифровані повідомлення, що обмінювані нещодавно, під час використання старої версії, можуть бути недешифровними у цій версії. Це може призвести до збоїв повідомлень, обмінюваних також і з цією версією. У разі виникнення проблем вийдіть з програми та зайдіть знову. Задля збереження історії повідомлень експортуйте та переімпортуйте ваші ключі.",
"Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Змінення паролю скине всі ключі наскрізного шифрування в усіх ваших сеансах, роблячи зашифровану історію листувань нечитабельною. Налагодьте дублювання ключів або експортуйте ключі кімнат з іншого сеансу перед скиданням пароля.", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Змінення паролю скине всі ключі наскрізного шифрування в усіх ваших сеансах, роблячи зашифровану історію листувань нечитабельною. Налагодьте дублювання ключів або експортуйте ключі кімнат з іншого сеансу перед скиданням пароля.",
"Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Підтвердьте вашу особу шляхом звіряння цього входу з одного з інших ваших сеансів, надаючи йому доступ до зашифрованих повідомлень.", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Підтвердьте вашу особу шляхом звіряння цього входу з одного з інших ваших сеансів, надаючи йому доступ до зашифрованих повідомлень.",
"Enable big emoji in chat": "Увімкнути великі емодзі у балачках", "Enable big emoji in chat": "Увімкнути великі емоджі у бесідах",
"Show typing notifications": "Сповіщати про друкування", "Show typing notifications": "Сповіщати про друкування",
"Show rooms with unread notifications first": "Спочатку показувати кімнати з непрочитаними сповіщеннями", "Show rooms with unread notifications first": "Спочатку показувати кімнати з непрочитаними сповіщеннями",
"Show shortcuts to recently viewed rooms above the room list": "Показувати нещодавно бачені кімнати вгорі понад переліком кімнат", "Show shortcuts to recently viewed rooms above the room list": "Показувати нещодавно бачені кімнати вгорі понад переліком кімнат",
@ -1181,11 +1181,11 @@
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Ваш новий сеанс тепер є звірений. Він має доступ до ваших зашифрованих повідомлень, а інші користувачі бачитимуть його як довірений.", "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Ваш новий сеанс тепер є звірений. Він має доступ до ваших зашифрованих повідомлень, а інші користувачі бачитимуть його як довірений.",
"Emoji": "Емодзі", "Emoji": "Емодзі",
"Emoji Autocomplete": "Самодоповнення емодзі", "Emoji Autocomplete": "Самодоповнення емодзі",
"%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s створив(-ла) правило блокування зі збігом з %(glob)s через %(reason)s", "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s створює правило блокування зі збігом з %(glob)s через %(reason)s",
"%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування користувачів зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування користувачів зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s",
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування кімнат зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування кімнат зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s",
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування серверів зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування серверів зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s",
"%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінєю правило блокування зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s", "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s змінює правило блокування зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s",
"Enable Community Filter Panel": "Увімкнути фільтр панелі спільнот", "Enable Community Filter Panel": "Увімкнути фільтр панелі спільнот",
"Messages containing my username": "Повідомлення, що містять моє користувацьке ім'я", "Messages containing my username": "Повідомлення, що містять моє користувацьке ім'я",
"Messages containing @room": "Повідомлення, що містять @room", "Messages containing @room": "Повідомлення, що містять @room",
@ -1237,9 +1237,9 @@
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)", "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
"Unexpected server error trying to leave the room": "Виникла неочікувана помилка серверу під час спроби залишити кімнату", "Unexpected server error trying to leave the room": "Виникла неочікувана помилка серверу під час спроби залишити кімнату",
"Unknown App": "Невідомий додаток", "Unknown App": "Невідомий додаток",
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Відправляти <UsageDataLink>анонімну статистику користування</UsageDataLink>, що дозволяє нам покращувати %(brand)s. Це використовує <PolicyLink>кукі</PolicyLink>.", "Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Надсилати <UsageDataLink>анонімну статистику користування</UsageDataLink>, що дозволяє нам вдосконалювати %(brand)s. Це використовує <PolicyLink>кукі</PolicyLink>.",
"Set up Secure Backup": "Налаштувати захищене резервне копіювання", "Set up Secure Backup": "Налаштувати захищене резервне копіювання",
"Safeguard against losing access to encrypted messages & data": "Захист від втрати доступу до зашифрованих повідомлень та даних", "Safeguard against losing access to encrypted messages & data": "Захистіться від втрати доступу до зашифрованих повідомлень і даних",
"The person who invited you already left the room.": "Особа, що вас запросила, вже залишила кімнату.", "The person who invited you already left the room.": "Особа, що вас запросила, вже залишила кімнату.",
"The person who invited you already left the room, or their server is offline.": "Особа, що вас запросила вже залишила кімнату, або її сервер відімкнено.", "The person who invited you already left the room, or their server is offline.": "Особа, що вас запросила вже залишила кімнату, або її сервер відімкнено.",
"Change notification settings": "Змінити налаштування сповіщень", "Change notification settings": "Змінити налаштування сповіщень",
@ -1633,10 +1633,10 @@
"Sends the given message as a spoiler": "Надсилає вказане повідомлення згорненим", "Sends the given message as a spoiler": "Надсилає вказане повідомлення згорненим",
"Integration manager": "Менеджер інтеграцій", "Integration manager": "Менеджер інтеграцій",
"Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Ваш %(brand)s не дозволяє вам користуватись для цього менеджером інтеграцій. Зверніться до адміністратора.", "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Ваш %(brand)s не дозволяє вам користуватись для цього менеджером інтеграцій. Зверніться до адміністратора.",
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your integration manager.": "Користування цим знадобом може призвести до поширення ваших даних <helpIcon /> з %(widgetDomain)s та вашим менеджером інтеграцій.", "Using this widget may share data <helpIcon /> with %(widgetDomain)s & your integration manager.": "Користування цим віджетом може призвести до поширення ваших даних <helpIcon /> через %(widgetDomain)s і ваш менеджер інтеграцій.",
"Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Менеджери інтеграцій отримують дані конфігурації та можуть змінювати знадоби, надсилати запрошення у кімнати й встановлювати рівні повноважень від вашого імені.", "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Менеджери інтеграцій отримують дані конфігурації та можуть змінювати знадоби, надсилати запрошення у кімнати й встановлювати рівні повноважень від вашого імені.",
"Use an integration manager to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій для керування ботами, знадобами та паками наліпок.", "Use an integration manager to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій для керування ботами, віджетами й пакунками наліпок.",
"Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій <b>%(serverName)s</b> для керування ботами, знадобами та паками наліпок.", "Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs.": "Використовувати менеджер інтеграцій <b>%(serverName)s</b> для керування ботами, віджетами й пакунками наліпок.",
"Identity server": "Сервер ідентифікації", "Identity server": "Сервер ідентифікації",
"Identity server (%(server)s)": "Сервер ідентифікації (%(server)s)", "Identity server (%(server)s)": "Сервер ідентифікації (%(server)s)",
"Could not connect to identity server": "Не вдалося під'єднатись до сервера ідентифікації", "Could not connect to identity server": "Не вдалося під'єднатись до сервера ідентифікації",
@ -1710,5 +1710,143 @@
"edited": "змінено", "edited": "змінено",
"Edited at %(date)s. Click to view edits.": "Змінено %(date)s. Натисніть, щоб переглянути зміни.", "Edited at %(date)s. Click to view edits.": "Змінено %(date)s. Натисніть, щоб переглянути зміни.",
"Edited at %(date)s": "Змінено %(date)s", "Edited at %(date)s": "Змінено %(date)s",
"%(senderName)s changed their profile picture": "%(senderName)s змінює зображення профілю" "%(senderName)s changed their profile picture": "%(senderName)s змінює зображення профілю",
"Phone Number": "Телефонний номер",
"%(oneUser)sleft %(count)s times|one": "%(oneUser)sвиходить",
"%(oneUser)sleft %(count)s times|other": "%(oneUser)sвийшли %(count)s разів",
"%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sвийшли",
"%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sвийшли %(count)s разів",
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
"Language Dropdown": "Спадне меню мов",
"Information": "Відомості",
"Rotate Right": "Обернути праворуч",
"Rotate Left": "Обернути ліворуч",
"Zoom in": "Збільшити",
"Zoom out": "Зменшити",
"collapse": "згорнути",
"No results": "Немає результатів",
"Application window": "Вікно застосунку",
"Error - Mixed content": "Помилка — змішаний вміст",
"Widget ID": "ID віджета",
"%(brand)s URL": "URL-адреса %(brand)s",
"Your theme": "Ваша тема",
"Your user ID": "Ваш ID користувача",
"Your avatar URL": "URL-адреса вашого аватара",
"Unknown Address": "Невідома адреса",
"Cancel search": "Скасувати пошук",
"Quick Reactions": "Швидкі реакції",
"Categories": "Категорії",
"Flags": "Прапори",
"Symbols": "Символи",
"Objects": "Об'єкти",
"Travel & Places": "Подорожі та місця",
"Food & Drink": "Їжа та напої",
"Animals & Nature": "Тварини та природа",
"Smileys & People": "Емоджі та люди",
"Frequently Used": "Часто використовувані",
"Activities": "Діяльність",
"Failed to unban": "Не вдалося розблокувати",
"Banned by %(displayName)s": "Блокує %(displayName)s",
"Ban users": "Блокування користувачів",
"You were banned from %(roomName)s by %(memberName)s": "%(memberName)s блокує вас у %(roomName)s",
"were banned %(count)s times|other": "заблоковані %(count)s разів",
"were banned %(count)s times|one": "заблоковані",
"was banned %(count)s times|other": "заблоковано %(count)s разів",
"was banned %(count)s times|one": "заблоковано",
"were unbanned %(count)s times|other": "розблоковані %(count)s разів",
"were unbanned %(count)s times|one": "розблоковані",
"was unbanned %(count)s times|other": "розблоковано %(count)s разів",
"was unbanned %(count)s times|one": "розблоковано",
"This is the beginning of your direct message history with <displayName/>.": "Це початок історії вашого особистого спілкування з <displayName/>.",
"Publish this room to the public in %(domain)s's room directory?": "Опублікувати цю кімнату для всіх у каталозі кімнат %(domain)s?",
"Direct message": "Особисте повідомлення",
"Recently Direct Messaged": "Недавно надіслані особисті повідомлення",
"User Directory": "Каталог користувачів",
"Room version:": "Версія кімнати:",
"Change topic": "Змінити тему",
"Change the topic of this room": "Змінити тему цієї кімнати",
"%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)sзмінює серверні права доступу",
"%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)sзмінює серверні права доступу %(count)s разів",
"%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)sзмінює серверні права доступу",
"%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)sзмінює серверні права доступу %(count)s разів",
"Change server ACLs": "Змінити серверні права доступу",
"Change permissions": "Змінити дозволи",
"Change room name": "Змінити назву кімнати",
"Change the name of this room": "Змінити назву цієї кімнати",
"Change history visibility": "Змінити видимість історії",
"Change main address for the room": "Змінити основну адресу кімнати",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s змінює аватар кімнати на <img/>",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s змінює аватар %(roomName)s",
"Change room avatar": "Змінити аватар кімнати",
"Change the avatar of this room": "Змінює аватар цієї кімнати",
"Modify widgets": "Змінити віджети",
"Notify everyone": "Сповістити всіх",
"Remove messages sent by others": "Вилучити повідомлення надіслані іншими",
"Kick users": "Викинути користувачів",
"Invite users": "Запросити користувачів",
"Select the roles required to change various parts of the room": "Виберіть ролі, необхідні для зміни різних частин кімнати",
"Default role": "Типова роль",
"Privileged Users": "Привілейовані користувачі",
"Roles & Permissions": "Ролі й дозволи",
"Main address": "Основна адреса",
"Error updating main address": "Помилка оновлення основної адреси",
"No other published addresses yet, add one below": "Поки немає загальнодоступних адрес, додайте їх унизу",
"Other published addresses:": "Інші загальнодоступні адреси:",
"Published addresses can be used by anyone on any server to join your room.": "Загальнодоступні адреси можуть бути використані будь-ким на будь-якому сервері для приєднання до вашої кімнати.",
"Published addresses can be used by anyone on any server to join your space.": "Загальнодоступні адреси можуть бути використані будь-ким на будь-якому сервері для приєднання до вашого простору.",
"Published Addresses": "Загальнодоступні адреси",
"Room Addresses": "Адреси кімнати",
"Your Security Key is in your <b>Downloads</b> folder.": "Ваш ключ безпеки перебуває у теці <b>Завантажень</b>.",
"Error downloading audio": "Помилка завантаження аудіо",
"Download logs": "Завантажити журнали",
"Preparing to download logs": "Приготування до завантаження журналів",
"Download %(text)s": "Завантажити %(text)s",
"Download": "Завантажити",
"Error downloading theme information.": "Помилка завантаження відомостей теми.",
"Matrix Room ID": "Matrix ID кімнати",
"Room ID": "ID кімнати",
"Failed to remove '%(roomName)s' from %(groupId)s": "Не вдалося вилучити «%(roomName)s» з %(groupId)s",
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Ви впевнені, що хочете вилучити «%(roomName)s» з %(groupId)s?",
"Decide who can join %(roomName)s.": "Вкажіть, хто може приєднуватися до %(roomName)s.",
"Internal room ID:": "Внутрішній ID кімнати:",
"User %(userId)s is already invited to the room": "Користувача %(userId)s вже запрошено до кімнати",
"Original event source": "Оригінальний початковий код",
"View source": "Переглянути код",
"Report": "Поскаржитися",
"Send report": "Надіслати звіт",
"Report the entire room": "Поскаржитися на всю кімнату",
"What this user is writing is wrong.\nThis will be reported to the room moderators.": "Те, що пише цей користувач, неправильно.\nПро це буде повідомлено модераторам кімнати.",
"Please fill why you're reporting.": "Будь ласка, вкажіть, чому ви скаржитеся.",
"Report a bug": "Повідомити про ваду",
"Share %(name)s": "Поділитися %(name)s",
"Share Community": "Поділитися спільнотою",
"Share User": "Поділитися користувачем",
"Share content": "Поділитися вмістом",
"Share entire screen": "Поділитися всім екраном",
"Any of the following data may be shared:": "Можна поділитися будь-якими з цих даних:",
"Unable to share phone number": "Не вдалося надіслати телефонний номер",
"Share": "Поділитись",
"Unable to share email address": "Не вдалося надіслати адресу е-пошти",
"Share invite link": "Надіслати запрошувальне посилання",
"%(sharerName)s is presenting": "%(sharerName)s показує",
"Yes": "Так",
"Invite to %(spaceName)s": "Запросити до %(spaceName)s",
"Share your public space": "Поділитися своїм загальнодоступним простором",
"Forward": "Переслати",
"Forward message": "Переслати повідомлення",
"Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Бета-версія доступна для переглядачів інтернету, настільних ПК та Android. Деякі функції можуть бути недоступні на вашому домашньому сервері.",
"Join the beta": "Долучитися до beta",
"To join %(spaceName)s, turn on the <a>Spaces beta</a>": "Щоб приєднатися до %(spaceName)s, увімкніть <a>Простори beta</a>",
"Spaces are a new way to group rooms and people.": "Простори — це новий спосіб згуртувати кімнати та людей.",
"Communities are changing to Spaces": "Спільноти змінюються на Простори",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Створіть спільноту, щоб об’єднати користувачів та кімнати! Створіть власну домашню сторінку, щоб позначити своє місце у всесвіті Matrix.",
"Some suggestions may be hidden for privacy.": "Деякі пропозиції можуть бути сховані для приватності.",
"Privacy Policy": "Політика приватності",
"Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Приватність важлива для нас, тому ми не збираємо жодних особистих або ідентифікаційних даних для нашої аналітики.",
"Privacy": "Приватність",
"To help space members find and join a private room, go to that room's Security & Privacy settings.": "Щоб допомогти учасникам простору знайти та приєднатися до приватної кімнати, перейдіть у налаштування безпеки й приватності цієї кімнати.",
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Захистіться від втрати доступу до зашифрованих повідомлень і даних створенням резервної копії ключів шифрування на своєму сервері.",
"Secure Backup": "Безпечне резервне копіювання",
"Give feedback.": "Надіслати відгук.",
"You may contact me if you have any follow up questions": "Можете зв’язатися зі мною, якщо у вас виникнуть додаткові запитання"
} }

View file

@ -71,7 +71,7 @@
"%(senderName)s set a profile picture.": "%(senderName)s 设置了头像。", "%(senderName)s set a profile picture.": "%(senderName)s 设置了头像。",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s 将昵称改为了 %(displayName)s。", "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s 将昵称改为了 %(displayName)s。",
"Settings": "设置", "Settings": "设置",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "用12小时制显示时间戳 (如:下午 2:30", "Show timestamps in 12 hour format (e.g. 2:30pm)": "使用 12 小时制显示时间戳 下午 2:30",
"Signed Out": "已退出登录", "Signed Out": "已退出登录",
"Sign in": "登录", "Sign in": "登录",
"Sign out": "注销", "Sign out": "注销",
@ -344,7 +344,7 @@
"(~%(count)s results)|one": "~%(count)s 个结果)", "(~%(count)s results)|one": "~%(count)s 个结果)",
"(~%(count)s results)|other": "~%(count)s 个结果)", "(~%(count)s results)|other": "~%(count)s 个结果)",
"Please select the destination room for this message": "请选择此消息的目标聊天室", "Please select the destination room for this message": "请选择此消息的目标聊天室",
"Start automatically after system login": "在系统登录后自动启动", "Start automatically after system login": "开机自启",
"Analytics": "统计分析服务", "Analytics": "统计分析服务",
"Reject all %(invitedRooms)s invites": "拒绝所有 %(invitedRooms)s 的邀请", "Reject all %(invitedRooms)s invites": "拒绝所有 %(invitedRooms)s 的邀请",
"Sun": "周日", "Sun": "周日",
@ -513,7 +513,7 @@
"Stickerpack": "贴纸包", "Stickerpack": "贴纸包",
"You don't currently have any stickerpacks enabled": "你目前未启用任何贴纸包", "You don't currently have any stickerpacks enabled": "你目前未启用任何贴纸包",
"Key request sent.": "已发送密钥共享请求。", "Key request sent.": "已发送密钥共享请求。",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "如果你是房间中最后一位有权限的用户,在你降低自己的权限等级后将无法撤回此修改,因为你将无法重新获得权限。", "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "如果你是聊天室中最后一位拥有权限的用户,在你降低自己的权限等级后将无法撤销此修改,你将无法重新获得权限。",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "你将无法撤回此修改,因为此用户的等级将与你相同。", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "你将无法撤回此修改,因为此用户的等级将与你相同。",
"Unmute": "取消静音", "Unmute": "取消静音",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s特权等级 %(powerLevelNumber)s", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s特权等级 %(powerLevelNumber)s",
@ -538,7 +538,7 @@
"Failed to remove user from community": "移除用户失败", "Failed to remove user from community": "移除用户失败",
"Filter community members": "过滤社群成员", "Filter community members": "过滤社群成员",
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "你确定要从 %(groupId)s 中移除 %(roomName)s 吗?", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "你确定要从 %(groupId)s 中移除 %(roomName)s 吗?",
"Removing a room from the community will also remove it from the community page.": "从社群中移除房间时,同时也会将其从社群页面中移除。", "Removing a room from the community will also remove it from the community page.": "从社群中移除聊天室时,同时也会将其从社群页面中移除。",
"Failed to remove room from community": "从社群中移除聊天室失败", "Failed to remove room from community": "从社群中移除聊天室失败",
"Failed to remove '%(roomName)s' from %(groupId)s": "从 %(groupId)s 中移除 “%(roomName)s” 失败", "Failed to remove '%(roomName)s' from %(groupId)s": "从 %(groupId)s 中移除 “%(roomName)s” 失败",
"Only visible to community members": "仅对社群成员可见", "Only visible to community members": "仅对社群成员可见",
@ -658,8 +658,8 @@
"This Room": "此聊天室", "This Room": "此聊天室",
"Noisy": "响铃", "Noisy": "响铃",
"Error saving email notification preferences": "保存电子邮件通知选项时出错", "Error saving email notification preferences": "保存电子邮件通知选项时出错",
"Messages containing my display name": "消息中含有我的显示名称", "Messages containing my display name": "当消息包含我的昵称时",
"Messages in one-to-one chats": "一对一聊天中的消息", "Messages in one-to-one chats": "私聊中的消息",
"Unavailable": "无法获得", "Unavailable": "无法获得",
"View Decrypted Source": "查看解密的来源", "View Decrypted Source": "查看解密的来源",
"Failed to update keywords": "无法更新关键词", "Failed to update keywords": "无法更新关键词",
@ -712,7 +712,7 @@
"Quote": "引述", "Quote": "引述",
"Send logs": "发送日志", "Send logs": "发送日志",
"All messages": "全部消息", "All messages": "全部消息",
"Call invitation": "语音邀请", "Call invitation": "当受到通话邀请时",
"Downloading update...": "正在下载更新…", "Downloading update...": "正在下载更新…",
"State Key": "状态键State Key", "State Key": "状态键State Key",
"Failed to send custom event.": "自定义事件发送失败。", "Failed to send custom event.": "自定义事件发送失败。",
@ -735,7 +735,7 @@
"Unable to join network": "无法加入网络", "Unable to join network": "无法加入网络",
"Sorry, your browser is <b>not</b> able to run %(brand)s.": "抱歉,您的浏览器 <b>无法</b> 运行 %(brand)s.", "Sorry, your browser is <b>not</b> able to run %(brand)s.": "抱歉,您的浏览器 <b>无法</b> 运行 %(brand)s.",
"Uploaded on %(date)s by %(user)s": "由 %(user)s 在 %(date)s 上传", "Uploaded on %(date)s by %(user)s": "由 %(user)s 在 %(date)s 上传",
"Messages in group chats": "群组聊天中的消息", "Messages in group chats": "群中的消息",
"Yesterday": "昨天", "Yesterday": "昨天",
"Error encountered (%(errorDetail)s).": "遇到错误 (%(errorDetail)s)。", "Error encountered (%(errorDetail)s).": "遇到错误 (%(errorDetail)s)。",
"Low Priority": "低优先级", "Low Priority": "低优先级",
@ -866,7 +866,7 @@
"There was an error joining the room": "加入聊天室时发生错误", "There was an error joining the room": "加入聊天室时发生错误",
"Custom user status messages": "自定义用户状态信息", "Custom user status messages": "自定义用户状态信息",
"Show developer tools": "显示开发者工具", "Show developer tools": "显示开发者工具",
"Messages containing @room": "含有 @room 的消息", "Messages containing @room": "当消息包含 @room 时",
"Delete Backup": "删除备份", "Delete Backup": "删除备份",
"Backup version: ": "备份版本: ", "Backup version: ": "备份版本: ",
"Algorithm: ": "算法: ", "Algorithm: ": "算法: ",
@ -944,7 +944,7 @@
"Render simple counters in room header": "在聊天室标题中显示简单计数", "Render simple counters in room header": "在聊天室标题中显示简单计数",
"Enable Emoji suggestions while typing": "启用实时表情符号建议", "Enable Emoji suggestions while typing": "启用实时表情符号建议",
"Show a placeholder for removed messages": "已移除的消息显示为一个占位符", "Show a placeholder for removed messages": "已移除的消息显示为一个占位符",
"Show join/leave messages (invites/kicks/bans unaffected)": "显示 加入/离开 消息(邀请/移除/封禁 不受影响)", "Show join/leave messages (invites/kicks/bans unaffected)": "显示加入/离开提示(邀请/移除/封禁提示不受此影响)",
"Show avatar changes": "显示头像更改", "Show avatar changes": "显示头像更改",
"Show display name changes": "显示昵称更改", "Show display name changes": "显示昵称更改",
"Show read receipts sent by other users": "显示其他用户发送的已读回执", "Show read receipts sent by other users": "显示其他用户发送的已读回执",
@ -955,8 +955,8 @@
"Enable Community Filter Panel": "启用社群筛选器面板", "Enable Community Filter Panel": "启用社群筛选器面板",
"Allow Peer-to-Peer for 1:1 calls": "允许一对一通话使用 P2P", "Allow Peer-to-Peer for 1:1 calls": "允许一对一通话使用 P2P",
"Prompt before sending invites to potentially invalid matrix IDs": "在发送邀请之前提示可能无效的 Matrix ID", "Prompt before sending invites to potentially invalid matrix IDs": "在发送邀请之前提示可能无效的 Matrix ID",
"Messages containing my username": "包含我的用户名的消息", "Messages containing my username": "当消息包含我的用户名时",
"Encrypted messages in one-to-one chats": "一对一聊天中的加密消息", "Encrypted messages in one-to-one chats": "私聊中的加密消息",
"Encrypted messages in group chats": "群聊中的加密消息", "Encrypted messages in group chats": "群聊中的加密消息",
"The other party cancelled the verification.": "另一方取消了验证。", "The other party cancelled the verification.": "另一方取消了验证。",
"Verified!": "已验证!", "Verified!": "已验证!",
@ -1050,14 +1050,14 @@
"Set a new account password...": "设置一个新的账号密码...", "Set a new account password...": "设置一个新的账号密码...",
"Email addresses": "电子邮箱地址", "Email addresses": "电子邮箱地址",
"Phone numbers": "电话号码", "Phone numbers": "电话号码",
"Language and region": "语言与", "Language and region": "语言与区",
"Theme": "主题", "Theme": "主题",
"Account management": "账号管理", "Account management": "账号管理",
"Deactivating your account is a permanent action - be careful!": "停用你的账号是一项永久性操作 - 请小心!", "Deactivating your account is a permanent action - be careful!": "停用你的账号是一项永久性操作 - 请小心!",
"General": "通用", "General": "通用",
"Credits": "感谢", "Credits": "感谢",
"For help with using %(brand)s, click <a>here</a>.": "对使用 %(brand)s 的说明,请点击 <a>这里</a>。", "For help with using %(brand)s, click <a>here</a>.": "关于 %(brand)s 的<a>使用说明</a>。",
"For help with using %(brand)s, click <a>here</a> or start a chat with our bot using the button below.": "对使用 %(brand)s 的说明,请点击 <a>这里</a> 或者使用下面的按钮开始与我们的机器人聊聊。", "For help with using %(brand)s, click <a>here</a> or start a chat with our bot using the button below.": "关于 %(brand)s 的使用说明,请点击<a>这里</a>或者通过下方按钮同我们的机器人聊聊。",
"Chat with %(brand)s Bot": "与 %(brand)s 机器人聊天", "Chat with %(brand)s Bot": "与 %(brand)s 机器人聊天",
"Help & About": "帮助及关于", "Help & About": "帮助及关于",
"Bug reporting": "错误上报", "Bug reporting": "错误上报",
@ -1108,7 +1108,7 @@
"Invite anyway": "还是邀请", "Invite anyway": "还是邀请",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "在提交日志之前,你必须<a>创建一个GitHub issue</a> 来描述你的问题。", "Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "在提交日志之前,你必须<a>创建一个GitHub issue</a> 来描述你的问题。",
"Unable to load commit detail: %(msg)s": "无法加载提交详情:%(msg)s", "Unable to load commit detail: %(msg)s": "无法加载提交详情:%(msg)s",
"To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "为避免丢失聊天记录,你必须在登出前导出房间密钥。 你需要回到较新版本的 %(brand)s 才能执行此操作", "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "为避免丢失聊天记录,你必须在登出前导出聊天室密钥。你需要切换至新版 %(brand)s 方可继续执行此操作",
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "验证此用户并将其标记为已信任。在收发端到端加密消息时,信任用户可让你更加放心。", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "验证此用户并将其标记为已信任。在收发端到端加密消息时,信任用户可让你更加放心。",
"Waiting for partner to confirm...": "等待对方确认中...", "Waiting for partner to confirm...": "等待对方确认中...",
"Incoming Verification Request": "收到验证请求", "Incoming Verification Request": "收到验证请求",
@ -1205,7 +1205,7 @@
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "在纯文本消息开头添加 ¯\\_(ツ)_/¯", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "在纯文本消息开头添加 ¯\\_(ツ)_/¯",
"User %(userId)s is already in the room": "用户 %(userId)s 已在聊天室中", "User %(userId)s is already in the room": "用户 %(userId)s 已在聊天室中",
"The user must be unbanned before they can be invited.": "用户必须先解封才能被邀请。", "The user must be unbanned before they can be invited.": "用户必须先解封才能被邀请。",
"<a>Upgrade</a> to your own domain": "<a>升级</a> 到你自己的域名", "<a>Upgrade</a> to your own domain": "<a>切换</a>至自有域名",
"Accept all %(invitedRooms)s invites": "接受所有 %(invitedRooms)s 邀请", "Accept all %(invitedRooms)s invites": "接受所有 %(invitedRooms)s 邀请",
"Change room avatar": "更改聊天室头像", "Change room avatar": "更改聊天室头像",
"Change room name": "更改聊天室名称", "Change room name": "更改聊天室名称",
@ -1253,7 +1253,7 @@
"You have %(count)s unread notifications in a prior version of this room.|one": "你在此聊天室的先前版本中有 %(count)s 条未读通知。", "You have %(count)s unread notifications in a prior version of this room.|one": "你在此聊天室的先前版本中有 %(count)s 条未读通知。",
"Add Email Address": "添加 Email 地址", "Add Email Address": "添加 Email 地址",
"Add Phone Number": "添加电话号码", "Add Phone Number": "添加电话号码",
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "是否使用“面包屑”功能(最近访问的房间的图标在房间列表上方显示", "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "是否使用「面包屑」功能(显示在聊天室列表的头像",
"Call failed due to misconfigured server": "因为服务器配置错误通话失败", "Call failed due to misconfigured server": "因为服务器配置错误通话失败",
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "请联系你主服务器(<code>%(homeserverDomain)s</code>)的管理员设置 TURN 服务器来确保通话运作稳定。", "Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "请联系你主服务器(<code>%(homeserverDomain)s</code>)的管理员设置 TURN 服务器来确保通话运作稳定。",
"Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "你也可以尝试使用<code>turn.matrix.org</code>公共服务器,但通话质量稍差,并且其将会得知你的 IP。你可以在设置中更改此选项。", "Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "你也可以尝试使用<code>turn.matrix.org</code>公共服务器,但通话质量稍差,并且其将会得知你的 IP。你可以在设置中更改此选项。",
@ -1368,8 +1368,8 @@
"Ensure you have a stable internet connection, or get in touch with the server admin": "确保你的网络连接稳定,或与服务器管理员联系", "Ensure you have a stable internet connection, or get in touch with the server admin": "确保你的网络连接稳定,或与服务器管理员联系",
"Ask your %(brand)s admin to check <a>your config</a> for incorrect or duplicate entries.": "跟你的%(brand)s管理员确认<a>你的配置</a>不正确或重复的条目。", "Ask your %(brand)s admin to check <a>your config</a> for incorrect or duplicate entries.": "跟你的%(brand)s管理员确认<a>你的配置</a>不正确或重复的条目。",
"Cannot reach identity server": "不可连接到身份服务器", "Cannot reach identity server": "不可连接到身份服务器",
"Room name or address": "房间名称或地址", "Room name or address": "聊天室名称或地址",
"Joins room with given address": "使用给定地址加入房间", "Joins room with given address": "使用指定地址加入聊天室",
"Verify this login": "验证此登录名", "Verify this login": "验证此登录名",
"Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "通过从其他会话之一验证此登录名并授予其访问加密信息的权限来确认您的身份。", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "通过从其他会话之一验证此登录名并授予其访问加密信息的权限来确认您的身份。",
"Which officially provided instance you are using, if any": "如果有的话,你正在使用官方所提供的哪个实例", "Which officially provided instance you are using, if any": "如果有的话,你正在使用官方所提供的哪个实例",
@ -1451,12 +1451,12 @@
"Use a system font": "使用系统字体", "Use a system font": "使用系统字体",
"System font name": "系统字体名称", "System font name": "系统字体名称",
"Never send encrypted messages to unverified sessions from this session": "永不从本会话向未验证的会话发送加密信息", "Never send encrypted messages to unverified sessions from this session": "永不从本会话向未验证的会话发送加密信息",
"Never send encrypted messages to unverified sessions in this room from this session": "永不从本会话在本房间中向未验证的会话发送加密信息", "Never send encrypted messages to unverified sessions in this room from this session": "永不从此会话向此聊天室中未验证的会话发送加密信息",
"Order rooms by name": "按名称排列房间", "Order rooms by name": "按名称排列聊天室顺序",
"Show rooms with unread notifications first": "优先显示有未读通知的房间", "Show rooms with unread notifications first": "优先显示有未读通知的聊天室",
"Show previews/thumbnails for images": "显示图片的预览图", "Show previews/thumbnails for images": "显示图片的预览图",
"Enable message search in encrypted rooms": "在加密房间中启用消息搜索", "Enable message search in encrypted rooms": "在加密聊天室中启用消息搜索",
"Enable experimental, compact IRC style layout": "启用实验性、紧凑的IRC式布局", "Enable experimental, compact IRC style layout": "启用实验性、紧凑的 IRC 式布局",
"Verify this session by completing one of the following:": "完成以下之一以验证这一会话:", "Verify this session by completing one of the following:": "完成以下之一以验证这一会话:",
"or": "或者", "or": "或者",
"Start": "开始", "Start": "开始",
@ -1587,7 +1587,7 @@
"Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "你的密码已成功更改。在你重新登录别的会话之前,你将不会在那里收到推送通知", "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "你的密码已成功更改。在你重新登录别的会话之前,你将不会在那里收到推送通知",
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "同意身份服务器(%(serverName)s的服务协议以允许自己被通过邮件地址或电话号码发现。", "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "同意身份服务器(%(serverName)s的服务协议以允许自己被通过邮件地址或电话号码发现。",
"Discovery": "发现", "Discovery": "发现",
"Clear cache and reload": "清除缓存重新加载", "Clear cache and reload": "清理缓存并重载",
"Keyboard Shortcuts": "键盘快捷键", "Keyboard Shortcuts": "键盘快捷键",
"Customise your experience with experimental labs features. <a>Learn more</a>.": "通过实验功能自定义您的体验。<a>了解更多</a>。", "Customise your experience with experimental labs features. <a>Learn more</a>.": "通过实验功能自定义您的体验。<a>了解更多</a>。",
"Ignored/Blocked": "已忽略/已屏蔽", "Ignored/Blocked": "已忽略/已屏蔽",
@ -1643,7 +1643,7 @@
"To link to this room, please add an address.": "要链接至此聊天室,请添加一个地址。", "To link to this room, please add an address.": "要链接至此聊天室,请添加一个地址。",
"Unable to share email address": "无法共享邮件地址", "Unable to share email address": "无法共享邮件地址",
"Your email address hasn't been verified yet": "你的邮件地址尚未被验证", "Your email address hasn't been verified yet": "你的邮件地址尚未被验证",
"Click the link in the email you received to verify and then click continue again.": "点击你收到的邮件中的链接后再点击继续。", "Click the link in the email you received to verify and then click continue again.": "点击你收到的电子邮件中的链接进行验证,然后再点击继续。",
"Verify the link in your inbox": "验证你的收件箱中的链接", "Verify the link in your inbox": "验证你的收件箱中的链接",
"Complete": "完成", "Complete": "完成",
"Share": "共享", "Share": "共享",
@ -1758,7 +1758,7 @@
"%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s 不能被预览。你想加入吗?", "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s 不能被预览。你想加入吗?",
"This room doesn't exist. Are you sure you're at the right place?": "此聊天室不存在。你确定你在正确的地方吗?", "This room doesn't exist. Are you sure you're at the right place?": "此聊天室不存在。你确定你在正确的地方吗?",
"Try again later, or ask a room admin to check if you have access.": "请稍后重试,或询问聊天室管理员以检查你是否有权限。", "Try again later, or ask a room admin to check if you have access.": "请稍后重试,或询问聊天室管理员以检查你是否有权限。",
"%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "尝试访问该房间是返回了 %(errcode)s。如果你认为你看到此消息是个错误请<issueLink>提交一个错误报告</issueLink>。", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "尝试访问此聊天室时返回错误 %(errcode)s。如果你认为这是个错误请<issueLink>提交错误报告</issueLink>。",
"Appearance": "外观", "Appearance": "外观",
"Show rooms with unread messages first": "优先显示有未读消息的聊天室", "Show rooms with unread messages first": "优先显示有未读消息的聊天室",
"Show previews of messages": "显示消息预览", "Show previews of messages": "显示消息预览",
@ -1772,7 +1772,7 @@
"Show %(count)s more|other": "多显示 %(count)s 个", "Show %(count)s more|other": "多显示 %(count)s 个",
"Show %(count)s more|one": "多显示 %(count)s 个", "Show %(count)s more|one": "多显示 %(count)s 个",
"Use default": "使用默认", "Use default": "使用默认",
"Mentions & Keywords": "提及关键词", "Mentions & Keywords": "提及&关键词",
"Notification options": "通知选项", "Notification options": "通知选项",
"Leave Room": "离开聊天室", "Leave Room": "离开聊天室",
"Forget Room": "忘记聊天室", "Forget Room": "忘记聊天室",
@ -1962,7 +1962,7 @@
"Reminder: Your browser is unsupported, so your experience may be unpredictable.": "提醒:你的浏览器不被支持,所以你的体验可能不可预料。", "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "提醒:你的浏览器不被支持,所以你的体验可能不可预料。",
"GitHub issue": "GitHub 上的 issue", "GitHub issue": "GitHub 上的 issue",
"Notes": "提示", "Notes": "提示",
"If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "如果有额外的上下文可以帮助我们分析问题,比如你当时在做什么、房间 ID、用户 ID 等等,请将其列于此处。", "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "如果有额外的上下文可以帮助我们分析问题,比如你当时在做什么、聊天室 ID、用户 ID 等等,请将其列于此处。",
"Removing…": "正在移除…", "Removing…": "正在移除…",
"Destroy cross-signing keys?": "销毁交叉签名密钥?", "Destroy cross-signing keys?": "销毁交叉签名密钥?",
"Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "删除交叉签名密钥是永久的。所有你验证过的人都会看到安全警报。除非你丢失了所有可以交叉签名的设备,否则几乎可以确定你不想这么做。", "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "删除交叉签名密钥是永久的。所有你验证过的人都会看到安全警报。除非你丢失了所有可以交叉签名的设备,否则几乎可以确定你不想这么做。",
@ -2311,7 +2311,7 @@
"Space": "空格", "Space": "空格",
"How fast should messages be downloaded.": "消息下载速度。", "How fast should messages be downloaded.": "消息下载速度。",
"IRC display name width": "IRC 显示名称宽度", "IRC display name width": "IRC 显示名称宽度",
"When rooms are upgraded": "聊天室升级时", "When rooms are upgraded": "聊天室升级时",
"Unexpected server error trying to leave the room": "试图离开聊天室时发生意外服务器错误", "Unexpected server error trying to leave the room": "试图离开聊天室时发生意外服务器错误",
"Error leaving room": "离开聊天室时出错", "Error leaving room": "离开聊天室时出错",
"New spinner design": "新的下拉列表设计", "New spinner design": "新的下拉列表设计",
@ -2824,7 +2824,7 @@
"Value in this room": "此聊天室中的值", "Value in this room": "此聊天室中的值",
"Settings Explorer": "设置浏览器", "Settings Explorer": "设置浏览器",
"with state key %(stateKey)s": "附带有状态键state key%(stateKey)s", "with state key %(stateKey)s": "附带有状态键state key%(stateKey)s",
"Your server requires encryption to be enabled in private rooms.": "你的服务器要求私人房间启用加密。", "Your server requires encryption to be enabled in private rooms.": "你的服务器要求私有聊天室得启用加密。",
"An image will help people identify your community.": "图片可以让人们辨识你的社群。", "An image will help people identify your community.": "图片可以让人们辨识你的社群。",
"What's the name of your community or team?": "你的社群或者团队的名称是什么?", "What's the name of your community or team?": "你的社群或者团队的名称是什么?",
"You can change this later if needed.": "如果需要,你可以稍后更改。", "You can change this later if needed.": "如果需要,你可以稍后更改。",
@ -3115,8 +3115,8 @@
"What do you want to organise?": "你想要组织什么?", "What do you want to organise?": "你想要组织什么?",
"Skip for now": "暂时跳过", "Skip for now": "暂时跳过",
"Failed to create initial space rooms": "创建初始空间聊天室失败", "Failed to create initial space rooms": "创建初始空间聊天室失败",
"To join %(spaceName)s, turn on the <a>Spaces beta</a>": "加入 %(spaceName)s 前,请开启<a>空间测试版</a>", "To join %(spaceName)s, turn on the <a>Spaces beta</a>": "加入 %(spaceName)s 前,需加入<a>空间测试</a>",
"To view %(spaceName)s, turn on the <a>Spaces beta</a>": "查看 %(spaceName)s 前,请开启<a>空间测试版</a>", "To view %(spaceName)s, turn on the <a>Spaces beta</a>": "查看 %(spaceName)s 前,需加入<a>空间测试</a>",
"Private space": "私有空间", "Private space": "私有空间",
"Public space": "公开空间", "Public space": "公开空间",
"Spaces are a beta feature.": "空间为测试版功能。", "Spaces are a beta feature.": "空间为测试版功能。",
@ -3126,9 +3126,9 @@
"Search names and descriptions": "搜索名称和描述", "Search names and descriptions": "搜索名称和描述",
"You may want to try a different search or check for typos.": "你可能要尝试其他搜索或检查是否有错别字。", "You may want to try a different search or check for typos.": "你可能要尝试其他搜索或检查是否有错别字。",
"You may contact me if you have any follow up questions": "如果你有任何后续问题,可以联系我", "You may contact me if you have any follow up questions": "如果你有任何后续问题,可以联系我",
"To leave the beta, visit your settings.": "要退出测试,请访问你的设置。", "To leave the beta, visit your settings.": "要退出测试,请访问你的设置。",
"Your platform and username will be noted to help us use your feedback as much as we can.": "我们将会记录你的平台及用户名,以帮助我们尽我们所能地使用你的反馈。", "Your platform and username will be noted to help us use your feedback as much as we can.": "我们将会记录你的平台及用户名,以帮助我们尽我们所能地使用你的反馈。",
"%(featureName)s beta feedback": "%(featureName)s 测试反馈", "%(featureName)s beta feedback": "%(featureName)s 测试反馈",
"Thank you for your feedback, we really appreciate it.": "感谢你的反馈,我们衷心地感谢。", "Thank you for your feedback, we really appreciate it.": "感谢你的反馈,我们衷心地感谢。",
"Beta feedback": "测试版反馈", "Beta feedback": "测试版反馈",
"Want to add a new room instead?": "想要添加一个新的聊天室吗?", "Want to add a new room instead?": "想要添加一个新的聊天室吗?",
@ -3231,9 +3231,9 @@
"Open the link in the email to continue registration.": "打开电子邮件中的链接以继续注册。", "Open the link in the email to continue registration.": "打开电子邮件中的链接以继续注册。",
"A confirmation email has been sent to %(emailAddress)s": "确认电子邮件以发送至 %(emailAddress)s", "A confirmation email has been sent to %(emailAddress)s": "确认电子邮件以发送至 %(emailAddress)s",
"Avatar": "头像", "Avatar": "头像",
"Join the beta": "加入测试", "Join the beta": "加入测试",
"Leave the beta": "退出测试", "Leave the beta": "退出测试",
"Beta": "测试", "Beta": "测试",
"Tap for more info": "点击以获取更多信息", "Tap for more info": "点击以获取更多信息",
"Spaces is a beta feature": "空间为测试功能", "Spaces is a beta feature": "空间为测试功能",
"Start audio stream": "开始音频流", "Start audio stream": "开始音频流",
@ -3273,7 +3273,7 @@
"See when anyone posts a sticker to your active room": "查看何时有人发送贴纸到你所活跃的聊天室", "See when anyone posts a sticker to your active room": "查看何时有人发送贴纸到你所活跃的聊天室",
"Send stickers to your active room as you": "发送贴纸到你所活跃的聊天室", "Send stickers to your active room as you": "发送贴纸到你所活跃的聊天室",
"See when people join, leave, or are invited to your active room": "查看人们何时加入、离开或被邀请到你所活跃的聊天室", "See when people join, leave, or are invited to your active room": "查看人们何时加入、离开或被邀请到你所活跃的聊天室",
"See when people join, leave, or are invited to this room": "查看人们何时加入、离开或被邀请到这个房间", "See when people join, leave, or are invited to this room": "查看人们加入、离开或被邀请到此聊天室的时间",
"Kick, ban, or invite people to this room, and make you leave": "移除、封禁或邀请用户到此聊天室,并让你离开", "Kick, ban, or invite people to this room, and make you leave": "移除、封禁或邀请用户到此聊天室,并让你离开",
"Currently joining %(count)s rooms|one": "目前正在加入 %(count)s 个聊天室", "Currently joining %(count)s rooms|one": "目前正在加入 %(count)s 个聊天室",
"Currently joining %(count)s rooms|other": "目前正在加入 %(count)s 个聊天室", "Currently joining %(count)s rooms|other": "目前正在加入 %(count)s 个聊天室",
@ -3413,7 +3413,7 @@
"Open Space": "打开空间", "Open Space": "打开空间",
"Olm version:": "Olm 版本:", "Olm version:": "Olm 版本:",
"There was an error loading your notification settings.": "加载你的通知设置时出错。", "There was an error loading your notification settings.": "加载你的通知设置时出错。",
"Mentions & keywords": "提及关键词", "Mentions & keywords": "提及&关键词",
"Global": "全局", "Global": "全局",
"New keyword": "新的关键词", "New keyword": "新的关键词",
"Keyword": "关键词", "Keyword": "关键词",
@ -3421,7 +3421,7 @@
"Enable for this account": "为此帐号启用", "Enable for this account": "为此帐号启用",
"An error occurred whilst saving your notification preferences.": "保存你的通知首选项时出错。", "An error occurred whilst saving your notification preferences.": "保存你的通知首选项时出错。",
"Error saving notification preferences": "保存通知设置时出错", "Error saving notification preferences": "保存通知设置时出错",
"Messages containing keywords": "包含关键字的消息", "Messages containing keywords": "当消息包含关键词时",
"Message bubbles": "消息气泡", "Message bubbles": "消息气泡",
"IRC": "IRC", "IRC": "IRC",
"Show all rooms": "显示所有聊天室", "Show all rooms": "显示所有聊天室",
@ -3461,7 +3461,7 @@
"Unable to transfer call": "无法转移通话", "Unable to transfer call": "无法转移通话",
"Anyone can find and join.": "任何人都可以找到并加入。", "Anyone can find and join.": "任何人都可以找到并加入。",
"We're working on this, but just want to let you know.": "我们正在为此努力,但只是想让你知道。", "We're working on this, but just want to let you know.": "我们正在为此努力,但只是想让你知道。",
"Search for rooms or spaces": "搜索房间或聊天室", "Search for rooms or spaces": "搜索聊天室或空间",
"Created from <Community />": "从 <Community /> 创建", "Created from <Community />": "从 <Community /> 创建",
"Unable to copy a link to the room to the clipboard.": "无法将聊天室的链接复制到剪贴板。", "Unable to copy a link to the room to the clipboard.": "无法将聊天室的链接复制到剪贴板。",
"Unable to copy room link": "无法复制聊天室链接", "Unable to copy room link": "无法复制聊天室链接",
@ -3521,7 +3521,7 @@
"Anyone will be able to find and join this room, not just members of <SpaceName/>.": "任何人都可以找到并加入这个聊天室,而不仅仅是 <SpaceName/> 的成员。", "Anyone will be able to find and join this room, not just members of <SpaceName/>.": "任何人都可以找到并加入这个聊天室,而不仅仅是 <SpaceName/> 的成员。",
"Everyone in <SpaceName/> will be able to find and join this room.": "<SpaceName/> 中的每个人都可以找到并加入这个聊天室。", "Everyone in <SpaceName/> will be able to find and join this room.": "<SpaceName/> 中的每个人都可以找到并加入这个聊天室。",
"You can change this at any time from room settings.": "你可以随时从聊天室设置中更改此设置。", "You can change this at any time from room settings.": "你可以随时从聊天室设置中更改此设置。",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "调试日志包含应用程序使用数据,包括你的用户名、你访问过的房间或群组的 ID 或别名、你上次与之交互的 UI 元素,以及其他用户的用户名。它们不包含消息。", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "调试日志包含应用程序使用数据,包括你的用户名、你访问过的聊天室或群组的 ID 或别名、你上次与之交互的 UI 元素,以及其他用户的用户名。它们不包含消息。",
"Adding spaces has moved.": "新增空间已移动。", "Adding spaces has moved.": "新增空间已移动。",
"Search for rooms": "搜索聊天室", "Search for rooms": "搜索聊天室",
"Search for spaces": "搜索空间", "Search for spaces": "搜索空间",
@ -3563,10 +3563,36 @@
"Spaces with access": "可访问的空间", "Spaces with access": "可访问的空间",
"Anyone in a space can find and join. <a>Edit which spaces can access here.</a>": "空间中的任何人都可以找到并加入。<a>在此处编辑哪些空间可以访问。</a>", "Anyone in a space can find and join. <a>Edit which spaces can access here.</a>": "空间中的任何人都可以找到并加入。<a>在此处编辑哪些空间可以访问。</a>",
"Currently, %(count)s spaces have access|other": "目前,%(count)s 个空间可以访问", "Currently, %(count)s spaces have access|other": "目前,%(count)s 个空间可以访问",
"& %(count)s more|other": "以及更多 %(count)s", "& %(count)s more|other": "以及 %(count)s",
"Upgrade required": "需要升级", "Upgrade required": "需要升级",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "如果你通过 GitHub 提交了错误,调试日志可以帮助我们追踪问题。 调试日志包含应用程序使用数据、你的用户名、你访问过的聊天室或群组的 ID 或别名、你上次与之交互的 UI 元素,以及其他用户的用户名。 它们不包含消息。", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "如果你通过 GitHub 提交了错误,调试日志可以帮助我们追踪问题。 调试日志包含应用程序使用数据、你的用户名、你访问过的聊天室或群组的 ID 或别名、你上次与之交互的 UI 元素,以及其他用户的用户名。 它们不包含消息。",
"%(sharerName)s is presenting": "%(sharerName)s 正在展示", "%(sharerName)s is presenting": "%(sharerName)s 正在展示",
"You are presenting": "你正在展示", "You are presenting": "你正在展示",
"Surround selected text when typing special characters": "输入特殊字符时圈出选定的文本" "Surround selected text when typing special characters": "输入特殊字符时圈出选定的文本",
"Rooms and spaces": "聊天室与空间",
"Results": "结果",
"Enable encryption in settings.": "在设置中启用加密。",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "你的私人信息通常是加密的,但此聊天室不是。这通常是因为使用了不受支持的设备或方法,例如电子邮件邀请。",
"To avoid these issues, create a <a>new public room</a> for the conversation you plan to have.": "为避免这些问题,请为计划中的对话创建一个<a>新的加密聊天室</a>。",
"<b>It's not recommended to make encrypted rooms public.</b> It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>不建议公开加密聊天室。</b>这意味着任何人都可以找到并加入聊天室,因此任何人都可以阅读消息。你您将无法享受加密带来的任何好处。 在公共聊天室加密消息会导致接收和发送消息的速度变慢。",
"Are you sure you want to make this encrypted room public?": "你确定要公开此加密聊天室吗?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "为避免这些问题,请为计划中的对话创建一个<a>新的加密聊天室</a>。",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>不建议为公开聊天室开启加密。</b>任何人都可以找到并加入公开聊天室,因此任何人都可以阅读其中的消息。 你将无法从中体验加密的任何好处,且以后也无法将其关闭。 在公开聊天室中加密消息会导致接收和发送消息的速度变慢。",
"Are you sure you want to add encryption to this public room?": "你确定要为此公开聊天室开启加密吗?",
"Cross-signing is ready but keys are not backed up.": "交叉签名已就绪,但尚未备份密钥。",
"Low bandwidth mode (requires compatible homeserver)": "低带宽模式(需要主服务器兼容)",
"Multiple integration managers (requires manual setup)": "多个集成管理器(需要手动设置)",
"Show threads": "显示主题帖",
"Thread": "主题帖",
"Threaded messaging": "按主题排列的消息",
"The above, but in <Room /> as well": "以上,但也包括 <Room />",
"The above, but in any room you are joined or invited to as well": "以上,但也包括您加入或被邀请加入的任何房间中",
"Autoplay videos": "自动播放视频",
"Autoplay GIFs": "自动播放 GIF",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s从此聊天室中取消固定了一条消息。查看所有固定消息。",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s 从此聊天室中取消固定了<a>一条消息</a>。查看所有<b>固定消息</b>。",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s将一条消息固定到此聊天室。查看所有固定信息。",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s 将<a>一条消息</a>固定到此聊天室。查看所有<b>固定消息</b>。",
"Currently, %(count)s spaces have access|one": "目前,一个空间有访问权限",
"& %(count)s more|one": "& 另外 %(count)s"
} }

View file

@ -3690,5 +3690,20 @@
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "為了避免這些問題,請為您計畫中的對話建立<a>新的加密聊天室</a>。", "To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "為了避免這些問題,請為您計畫中的對話建立<a>新的加密聊天室</a>。",
"<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>不建議加密公開聊天室。</b>任何人都可以尋找並加入公開聊天室,所以任何人都可以閱讀其中的訊息。您不會得到任何加密的好處,而您也無法在稍後關閉加密功能。在公開聊天室中加密訊息會讓接收與傳送訊息更慢。", "<b>It's not recommended to add encryption to public rooms.</b>Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>不建議加密公開聊天室。</b>任何人都可以尋找並加入公開聊天室,所以任何人都可以閱讀其中的訊息。您不會得到任何加密的好處,而您也無法在稍後關閉加密功能。在公開聊天室中加密訊息會讓接收與傳送訊息更慢。",
"Are you sure you want to add encryption to this public room?": "您確定您要在此公開聊天室新增加密?", "Are you sure you want to add encryption to this public room?": "您確定您要在此公開聊天室新增加密?",
"Cross-signing is ready but keys are not backed up.": "已準備好交叉簽署但金鑰未備份。" "Cross-signing is ready but keys are not backed up.": "已準備好交叉簽署但金鑰未備份。",
"Low bandwidth mode (requires compatible homeserver)": "低頻寬模式(需要相容的家伺服器)",
"Multiple integration managers (requires manual setup)": "多個整合管理程式(需要手動設定)",
"Thread": "討論串",
"Show threads": "顯示討論串",
"Threaded messaging": "討論串訊息",
"The above, but in <Room /> as well": "以上,但也在 <Room /> 中",
"The above, but in any room you are joined or invited to as well": "以上,但在任何您已加入或被邀請的聊天室中",
"Autoplay videos": "自動播放影片",
"Autoplay GIFs": "自動播放 GIF",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s 從此聊天室取消釘選了訊息。檢視所有釘選的訊息。",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s 從此聊天室取消釘選了<a>訊息</a>。檢視所有<b>釘選的訊息</b>。",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s 釘選了訊息到此聊天室。檢視所有已釘選的訊息。",
"%(senderName)s pinned <a>a message</a> to this room. See all <b>pinned messages</b>.": "%(senderName)s 釘選了<a>訊息</a>到此聊天室。檢視所有<b>釘選的訊息</b>。",
"Currently, %(count)s spaces have access|one": "目前1 個空間可存取",
"& %(count)s more|one": "與其他 %(count)s 個"
} }

View file

@ -0,0 +1,46 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "matrix-js-sdk/src";
/**
* Decorates the given event content object with the "send start time". The
* object will be modified in-place.
* @param {object} content The event content.
*/
export function decorateStartSendingTime(content: object) {
content['io.element.performance_metrics'] = {
sendStartTs: Date.now(),
};
}
/**
* Called when an event decorated with `decorateStartSendingTime()` has been sent
* by the server (the client now knows the event ID).
* @param {MatrixClient} client The client to send as.
* @param {string} inRoomId The room ID where the original event was sent.
* @param {string} forEventId The event ID for the decorated event.
*/
export function sendRoundTripMetric(client: MatrixClient, inRoomId: string, forEventId: string) {
// noinspection JSIgnoredPromiseFromCall
client.sendEvent(inRoomId, 'io.element.performance_metric', {
"io.element.performance_metrics": {
forEventId: forEventId,
responseTs: Date.now(),
kind: 'send_time',
},
});
}

View file

@ -759,6 +759,10 @@ export const SETTINGS: {[setting: string]: ISetting} = {
default: true, default: true,
controller: new ReducedMotionController(), controller: new ReducedMotionController(),
}, },
"Performance.addSendMessageTimingMetadata": {
supportedLevels: [SettingLevel.CONFIG],
default: false,
},
"Widgets.pinned": { // deprecated "Widgets.pinned": { // deprecated
supportedLevels: LEVELS_ROOM_OR_ACCOUNT, supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
default: {}, default: {},

View file

@ -257,7 +257,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
"go to that room's Security & Privacy settings.") }</p> "go to that room's Security & Privacy settings.") }</p>
{ /* Reuses classes from TabbedView for simplicity, non-interactive */ } { /* Reuses classes from TabbedView for simplicity, non-interactive */ }
<div style={{ width: "190px" }}> <div className="mx_TabbedView_tabsOnLeft" style={{ width: "190px", position: "relative" }}>
<div className="mx_TabbedView_tabLabel"> <div className="mx_TabbedView_tabLabel">
<span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_settingsIcon" /> <span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_settingsIcon" />
<span className="mx_TabbedView_tabLabel_text">{ _t("General") }</span> <span className="mx_TabbedView_tabLabel_text">{ _t("General") }</span>
@ -366,16 +366,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
public getParents(roomId: string, canonicalOnly = false): Room[] { public getParents(roomId: string, canonicalOnly = false): Room[] {
const userId = this.matrixClient?.getUserId();
const room = this.matrixClient?.getRoom(roomId); const room = this.matrixClient?.getRoom(roomId);
return room?.currentState.getStateEvents(EventType.SpaceParent) return room?.currentState.getStateEvents(EventType.SpaceParent)
.filter(ev => { .map(ev => {
const content = ev.getContent(); const content = ev.getContent();
if (!content?.via?.length) return false; if (Array.isArray(content?.via) && (!canonicalOnly || content?.canonical)) {
// TODO apply permissions check to verify that the parent mapping is valid const parent = this.matrixClient.getRoom(ev.getStateKey());
if (canonicalOnly && !content?.canonical) return false; // only respect the relationship if the sender has sufficient permissions in the parent to set
return true; // child relations, as per MSC1772.
// https://github.com/matrix-org/matrix-doc/blob/main/proposals/1772-groups-as-rooms.md#relationship-between-rooms-and-spaces
if (parent?.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
return parent;
}
}
// else implicit undefined which causes this element to be filtered out
}) })
.map(ev => this.matrixClient.getRoom(ev.getStateKey()))
.filter(Boolean) || []; .filter(Boolean) || [];
} }
@ -530,6 +536,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}); });
} }
const hiddenChildren = new EnhancedMap<string, Set<string>>();
visibleRooms.forEach(room => {
if (room.getMyMembership() !== "join") return;
this.getParents(room.roomId).forEach(parent => {
hiddenChildren.getOrCreate(parent.roomId, new Set()).add(room.roomId);
});
});
this.rootSpaces.forEach(s => { this.rootSpaces.forEach(s => {
// traverse each space tree in DFS to build up the supersets as you go up, // traverse each space tree in DFS to build up the supersets as you go up,
// reusing results from like subtrees. // reusing results from like subtrees.
@ -559,6 +573,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
roomIds.add(roomId); roomIds.add(roomId);
}); });
}); });
hiddenChildren.get(spaceId)?.forEach(roomId => {
roomIds.add(roomId);
});
this.spaceFilteredRooms.set(spaceId, roomIds); this.spaceFilteredRooms.set(spaceId, roomIds);
return roomIds; return roomIds;
}; };
@ -690,6 +707,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
this.emit(room.roomId); this.emit(room.roomId);
break; break;
case EventType.RoomPowerLevels:
if (room.isSpaceRoom()) {
this.onRoomsUpdate();
}
break;
} }
}; };

View file

@ -44,7 +44,7 @@ import { containsEmoji } from "../../effects/utils";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permalinks"; import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permalinks";
import { IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk"; import { Room } from "matrix-js-sdk/src/models/room";
// TODO: Purge this from the universe // TODO: Purge this from the universe

View file

@ -22,6 +22,7 @@ import Modal from "../Modal";
import { _t } from "../languageHandler"; import { _t } from "../languageHandler";
import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import ErrorDialog from "../components/views/dialogs/ErrorDialog";
import SpaceStore from "../stores/SpaceStore"; import SpaceStore from "../stores/SpaceStore";
import Spinner from "../components/views/elements/Spinner";
export async function upgradeRoom( export async function upgradeRoom(
room: Room, room: Room,
@ -29,8 +30,10 @@ export async function upgradeRoom(
inviteUsers = false, inviteUsers = false,
handleError = true, handleError = true,
updateSpaces = true, updateSpaces = true,
awaitRoom = false,
): Promise<string> { ): Promise<string> {
const cli = room.client; const cli = room.client;
const spinnerModal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner");
let newRoomId: string; let newRoomId: string;
try { try {
@ -46,14 +49,27 @@ export async function upgradeRoom(
throw e; throw e;
} }
if (awaitRoom || inviteUsers) {
await new Promise<void>(resolve => {
// already have the room
if (room.client.getRoom(newRoomId)) {
resolve();
return;
}
// We have to wait for the js-sdk to give us the room back so // We have to wait for the js-sdk to give us the room back so
// we can more effectively abuse the MultiInviter behaviour // we can more effectively abuse the MultiInviter behaviour
// which heavily relies on the Room object being available. // which heavily relies on the Room object being available.
if (inviteUsers) { const checkForRoomFn = (newRoom: Room) => {
const checkForUpgradeFn = async (newRoom: Room): Promise<void> => {
// The upgradePromise should be done by the time we await it here.
if (newRoom.roomId !== newRoomId) return; if (newRoom.roomId !== newRoomId) return;
resolve();
cli.off("Room", checkForRoomFn);
};
cli.on("Room", checkForRoomFn);
});
}
if (inviteUsers) {
const toInvite = [ const toInvite = [
...room.getMembersWithMembership("join"), ...room.getMembersWithMembership("join"),
...room.getMembersWithMembership("invite"), ...room.getMembersWithMembership("invite"),
@ -63,10 +79,6 @@ export async function upgradeRoom(
// Errors are handled internally to this function // Errors are handled internally to this function
await inviteUsersToRoom(newRoomId, toInvite); await inviteUsersToRoom(newRoomId, toInvite);
} }
cli.removeListener('Room', checkForUpgradeFn);
};
cli.on('Room', checkForUpgradeFn);
} }
if (updateSpaces) { if (updateSpaces) {
@ -89,5 +101,6 @@ export async function upgradeRoom(
} }
} }
spinnerModal.close();
return newRoomId; return newRoomId;
} }

View file

@ -276,10 +276,12 @@ describe("SpaceStore", () => {
describe("test fixture 1", () => { describe("test fixture 1", () => {
beforeEach(async () => { beforeEach(async () => {
[fav1, fav2, fav3, dm1, dm2, dm3, orphan1, orphan2, invite1, invite2, room1].forEach(mkRoom); [fav1, fav2, fav3, dm1, dm2, dm3, orphan1, orphan2, invite1, invite2, room1, room2, room3]
.forEach(mkRoom);
mkSpace(space1, [fav1, room1]); mkSpace(space1, [fav1, room1]);
mkSpace(space2, [fav1, fav2, fav3, room1]); mkSpace(space2, [fav1, fav2, fav3, room1]);
mkSpace(space3, [invite2]); mkSpace(space3, [invite2]);
// client.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId));
[fav1, fav2, fav3].forEach(roomId => { [fav1, fav2, fav3].forEach(roomId => {
client.getRoom(roomId).tags = { client.getRoom(roomId).tags = {
@ -329,6 +331,48 @@ describe("SpaceStore", () => {
]); ]);
// dmPartner3 is not in any common spaces with you // dmPartner3 is not in any common spaces with you
// room 2 claims to be a child of space2 and is so via a valid m.space.parent
const cliRoom2 = client.getRoom(room2);
cliRoom2.currentState.getStateEvents.mockImplementation(testUtils.mockStateEventImplementation([
mkEvent({
event: true,
type: EventType.SpaceParent,
room: room2,
user: client.getUserId(),
skey: space2,
content: { via: [], canonical: true },
ts: Date.now(),
}),
]));
const cliSpace2 = client.getRoom(space2);
cliSpace2.currentState.maySendStateEvent.mockImplementation((evType: string, userId: string) => {
if (evType === EventType.SpaceChild) {
return userId === client.getUserId();
}
return true;
});
// room 3 claims to be a child of space3 but is not due to invalid m.space.parent (permissions)
const cliRoom3 = client.getRoom(room3);
cliRoom3.currentState.getStateEvents.mockImplementation(testUtils.mockStateEventImplementation([
mkEvent({
event: true,
type: EventType.SpaceParent,
room: room3,
user: client.getUserId(),
skey: space3,
content: { via: [], canonical: true },
ts: Date.now(),
}),
]));
const cliSpace3 = client.getRoom(space3);
cliSpace3.currentState.maySendStateEvent.mockImplementation((evType: string, userId: string) => {
if (evType === EventType.SpaceChild) {
return false;
}
return true;
});
await run(); await run();
}); });
@ -445,6 +489,14 @@ describe("SpaceStore", () => {
expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(room1)).toBeTruthy(); expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(room1)).toBeTruthy();
expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(room1)).toBeFalsy(); expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(room1)).toBeFalsy();
}); });
it("honours m.space.parent if sender has permission in parent space", () => {
expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(room2)).toBeTruthy();
});
it("does not honour m.space.parent if sender does not have permission in parent space", () => {
expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(room3)).toBeFalsy();
});
}); });
}); });

View file

@ -2036,10 +2036,10 @@ ajv@^8.0.1:
require-from-string "^2.0.2" require-from-string "^2.0.2"
uri-js "^4.2.2" uri-js "^4.2.2"
allchange@^1.0.2: allchange@^1.0.3:
version "1.0.2" version "1.0.3"
resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.2.tgz#86b9190e12b7ede4f230ae763cbd504c48fd907b" resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.3.tgz#f8814ddfbcfe39a01bf4570778ee7e6d9ff0ebb3"
integrity sha512-qJv1t2yvBThkes8g/dPMt8CGu+04U+q5QjCJn2Ngp92edZU8DJBfKGyGXo7w1iV48LVuQKQDfMsdIWhP7zHdlQ== integrity sha512-UZkfz5SkNEMFQFLr8vZcXHaph2EbJxmkVNF5Nt6D9RIa5pmAar7oAMfNdda714jg7IQijvaFty5PYazXLgd5WA==
dependencies: dependencies:
"@actions/core" "^1.4.0" "@actions/core" "^1.4.0"
"@actions/github" "^5.0.0" "@actions/github" "^5.0.0"