From d30c46a641a061c6f54694ef642d469dcf175f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 14 Jan 2020 11:20:56 +0100 Subject: [PATCH 01/65] FilePanel: Refactor out the file panel and convert the methods to async ones. --- src/components/structures/FilePanel.js | 62 +++++++++++++++----------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 61b3d2d4b9..6728472a6c 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -40,42 +40,50 @@ const FilePanel = createReactClass({ }; }, - componentDidMount: function() { - this.updateTimelineSet(this.props.roomId); + async componentDidMount() { + await this.updateTimelineSet(this.props.roomId); }, - updateTimelineSet: function(roomId) { + async fetchFileEventsServer(room) { + const client = MatrixClientPeg.get(); + + const filter = new Matrix.Filter(client.credentials.userId); + filter.setDefinition( + { + "room": { + "timeline": { + "contains_url": true, + "types": [ + "m.room.message", + ], + }, + }, + }, + ); + + // FIXME: we shouldn't be doing this every time we change room - see comment above. + // TODO: Remove this stale comment? Which comment above? + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter) + filter.filterId = filterId; + const timelineSet = room.getOrCreateFilteredTimelineSet(filter); + + return timelineSet; + }, + + async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); this.noRoom = !room; if (room) { - const filter = new Matrix.Filter(client.credentials.userId); - filter.setDefinition( - { - "room": { - "timeline": { - "contains_url": true, - "types": [ - "m.room.message", - ], - }, - }, - }, - ); + try { + let timelineSet = await this.fetchFileEventsServer(room) + this.setState({ timelineSet: timelineSet }); - // FIXME: we shouldn't be doing this every time we change room - see comment above. - client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then( - (filterId)=>{ - filter.filterId = filterId; - const timelineSet = room.getOrCreateFilteredTimelineSet(filter); - this.setState({ timelineSet: timelineSet }); - }, - (error)=>{ - console.error("Failed to get or create file panel filter", error); - }, - ); + } catch (error) { + console.error("Failed to get or create file panel filter", error); + } } else { console.error("Failed to add filtered timelineSet for FilePanel as no room!"); } From 4f63b10465c28db5f9abb6e5d04fd241b03e74f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 10:41:33 +0100 Subject: [PATCH 02/65] EventIndex: Live events can be unencrypted as well. --- src/indexing/EventIndex.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c912e31fa5..0980413eb5 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -170,7 +170,12 @@ export default class EventIndex { return; } - const e = ev.toJSON().decrypted; + const jsonEvent = ev.toJSON(); + + let e; + if (ev.isEncrypted()) e = jsonEvent.decrypted; + else e = jsonEvent; + const profile = { displayname: ev.sender.rawDisplayName, avatar_url: ev.sender.getMxcAvatarUrl(), From 263370c9ae6d13038157417e71220a9c5e5cfd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:04:27 +0100 Subject: [PATCH 03/65] BaseEventIndexManager: Add a method to load file events of a room. --- src/indexing/BaseEventIndexManager.js | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index 5e8ca668ad..d7b322bb1a 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -62,11 +62,18 @@ export interface SearchArgs { room_id: ?string; } -export interface HistoricEvent { +export interface EventAndProfile { event: MatrixEvent; profile: MatrixProfile; } +export interface LoadArgs { + roomId: string; + limit: number; + fromEvent: string; + direction: string; +} + /** * Base class for classes that provide platform-specific event indexing. * @@ -145,7 +152,7 @@ export default class BaseEventIndexManager { * * This is used to add a batch of events to the index. * - * @param {[HistoricEvent]} events The list of events and profiles that + * @param {[EventAndProfile]} events The list of events and profiles that * should be added to the event index. * @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that * should be stored in the index which should be used to continue crawling @@ -158,7 +165,7 @@ export default class BaseEventIndexManager { * were already added to the index, false otherwise. */ async addHistoricEvents( - events: [HistoricEvent], + events: [EventAndProfile], checkpoint: CrawlerCheckpoint | null, oldCheckpoint: CrawlerCheckpoint | null, ): Promise { @@ -201,6 +208,23 @@ export default class BaseEventIndexManager { throw new Error("Unimplemented"); } + /** Load events that contain an mxc URL to a file from the index. + * + * @param {object} args Arguments object for the method. + * @param {string} args.roomId The ID of the room for which the events + * should be loaded. + * @param {number} args.limit The maximum number of events to return. + * @param {string} args.fromEvent An event id of a previous event returned + * by this method. If set events that are older than the event with the + * given event ID will be returned. + * + * @return {Promise<[EventAndProfile]>} A promise that will resolve to an array + * of Matrix events that contain mxc URLs. + */ + async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> { + throw new Error("Unimplemented"); + } + /** * close our event index. * From 8a17c73b79d13eddd31418de59716870a5b2cfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:05:02 +0100 Subject: [PATCH 04/65] EventIndex: Add a method to populate an event timeline with file events. --- src/indexing/EventIndex.js | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 0980413eb5..501e21b29d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -17,6 +17,12 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; +import Matrix from 'matrix-js-sdk'; +import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; +import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; +import MatrixEvent from 'matrix-js-sdk/lib/models/event'; +import RoomMember from 'matrix-js-sdk/lib/models/room-member'; + /* * Event indexing class that wraps the platform specific event indexing. */ @@ -411,4 +417,66 @@ export default class EventIndex { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.searchEventIndex(searchArgs); } + + async populateFileTimeline(room, timelineSet) { + const client = MatrixClientPeg.get(); + const indexManager = PlatformPeg.get().getEventIndexingManager(); + + // Get our events from the event index. + const events = await indexManager.loadFileEvents( + { + roomId: room.roomId, + limit: 10 + } + ); + + let eventMapper = client.getEventMapper(); + + // Turn the events into MatrixEvent objects. + const matrixEvents = events.map(e => { + const matrixEvent = eventMapper(e.event); + + const member = new RoomMember(room.roomId, matrixEvent.getSender()); + + // We can't really reconstruct the whole room state from our + // EventIndex to calculate the correct display name. Use the + // disambiguated form always instead. + member.name = e.profile.displayname + " (" + matrixEvent.getSender() + ")"; + + // This is sets the avatar URL. + const memberEvent = eventMapper( + { + content: { + membership: "join", + avatar_url: e.profile.avatar_url, + displayname: e.profile.displayname, + }, + type: "m.room.member", + event_id: matrixEvent.getId() + ":eventIndex", + room_id: matrixEvent.getRoomId(), + sender: matrixEvent.getSender(), + origin_server_ts: matrixEvent.getTs(), + state_key: matrixEvent.getSender() + } + ); + + // We set this manually to avoid emitting RoomMember.membership and + // RoomMember.name events. + member.events.member = memberEvent; + matrixEvent.sender = member; + + return matrixEvent; + }); + + // Add the events to the live timeline of the file panel. + matrixEvents.forEach(e => { + if (!timelineSet.eventIdToTimeline(e.getId())) { + const liveTimeline = timelineSet.getLiveTimeline(); + timelineSet.addEventToTimeline(e, liveTimeline, true) + } + }); + + // Set the pagination token to the oldest event that we retrieved. + timelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS); + } } From a1cbff3c8cefa3945c5ebedc85e31dd305d2b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:05:43 +0100 Subject: [PATCH 05/65] FilePanel: Use the event index in encrypted rooms to populate the panel. --- src/components/structures/FilePanel.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6728472a6c..56bc9dee64 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -22,6 +22,7 @@ import PropTypes from 'prop-types'; import Matrix from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; +import {EventIndexPeg} from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; /* @@ -73,12 +74,20 @@ const FilePanel = createReactClass({ async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); + const eventIndex = EventIndexPeg.get(); this.noRoom = !room; if (room) { + let timelineSet; + try { - let timelineSet = await this.fetchFileEventsServer(room) + timelineSet = await this.fetchFileEventsServer(room) + + if (client.isRoomEncrypted(roomId) && eventIndex !== null) { + await eventIndex.populateFileTimeline(room, timelineSet); + } + this.setState({ timelineSet: timelineSet }); } catch (error) { From 7fb3645e940073a7c7db4e022e1655ca7facde7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:06:29 +0100 Subject: [PATCH 06/65] LifeCycle: Start the event index before the client. --- src/Lifecycle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 0796e326a0..aab7884b2e 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -588,8 +588,8 @@ async function startMatrixClient(startSyncing=true) { Mjolnir.sharedInstance().start(); if (startSyncing) { - await MatrixClientPeg.start(); await EventIndexPeg.init(); + await MatrixClientPeg.start(); } else { console.warn("Caller requested only auxiliary services be started"); await MatrixClientPeg.assign(); From 49c1dbe42133dd8103e1d0d8036d5bcc29c32c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 10:04:53 +0100 Subject: [PATCH 07/65] FilePanel: Implement pagination requesting using the EventIndex. --- src/components/structures/FilePanel.js | 18 +++- src/components/structures/TimelinePanel.js | 14 ++- src/indexing/EventIndex.js | 99 +++++++++++++++++++--- 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 56bc9dee64..74b434cdbf 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -71,6 +71,20 @@ const FilePanel = createReactClass({ return timelineSet; }, + onPaginationRequest(timelineWindow, direction, limit) { + const client = MatrixClientPeg.get(); + const eventIndex = EventIndexPeg.get(); + const roomId = this.props.roomId; + + const room = client.getRoom(roomId); + + if (client.isRoomEncrypted(roomId) && eventIndex !== null) { + return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit); + } else { + return timelineWindow.paginate(direction, limit); + } + }, + async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); @@ -85,7 +99,8 @@ const FilePanel = createReactClass({ timelineSet = await this.fetchFileEventsServer(room) if (client.isRoomEncrypted(roomId) && eventIndex !== null) { - await eventIndex.populateFileTimeline(room, timelineSet); + const timeline = timelineSet.getLiveTimeline(); + await eventIndex.populateFileTimeline(timelineSet, timeline, room, 1); } this.setState({ timelineSet: timelineSet }); @@ -128,6 +143,7 @@ const FilePanel = createReactClass({ manageReadMarkers={false} timelineSet={this.state.timelineSet} showUrlPreview = {false} + onPaginationRequest={this.onPaginationRequest} tileShape="file_grid" resizeNotifier={this.props.resizeNotifier} empty={_t('There are no visible files in this room')} diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 30b02bfcca..41b1b6f675 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -94,6 +94,10 @@ const TimelinePanel = createReactClass({ // callback which is called when the read-up-to mark is updated. onReadMarkerUpdated: PropTypes.func, + // callback which is called when we wish to paginate the timeline + // window. + onPaginationRequest: PropTypes.func, + // maximum number of events to show in a timeline timelineCap: PropTypes.number, @@ -338,6 +342,14 @@ const TimelinePanel = createReactClass({ } }, + onPaginationRequest(timelineWindow, direction, size) { + if (this.props.onPaginationRequest) { + return this.props.onPaginationRequest(timelineWindow, direction, size); + } else { + return timelineWindow.paginate(direction, size); + } + }, + // set off a pagination request. onMessageListFillRequest: function(backwards) { if (!this._shouldPaginate()) return Promise.resolve(false); @@ -360,7 +372,7 @@ const TimelinePanel = createReactClass({ debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards); this.setState({[paginatingKey]: true}); - return this._timelineWindow.paginate(dir, PAGINATE_SIZE).then((r) => { + return this.onPaginationRequest(this._timelineWindow, dir, PAGINATE_SIZE).then((r) => { if (this.unmounted) { return; } debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 501e21b29d..7263d8b2c4 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -418,17 +418,29 @@ export default class EventIndex { return indexManager.searchEventIndex(searchArgs); } - async populateFileTimeline(room, timelineSet) { + async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); + let loadArgs = { + roomId: room.roomId, + limit: limit + } + + if (fromEvent) { + loadArgs.fromEvent = fromEvent; + loadArgs.direction = direction; + } + + let events + // Get our events from the event index. - const events = await indexManager.loadFileEvents( - { - roomId: room.roomId, - limit: 10 - } - ); + try { + events = await indexManager.loadFileEvents(loadArgs); + } catch (e) { + console.log("EventIndex: Error getting file events", e); + return [] + } let eventMapper = client.getEventMapper(); @@ -468,15 +480,82 @@ export default class EventIndex { return matrixEvent; }); + return matrixEvents; + } + + async populateFileTimeline(timelineSet, timeline, room, limit = 10, + fromEvent = null, direction = EventTimeline.BACKWARDS) { + let matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { - const liveTimeline = timelineSet.getLiveTimeline(); - timelineSet.addEventToTimeline(e, liveTimeline, true) + timelineSet.addEventToTimeline(e, timeline, + direction == EventTimeline.BACKWARDS) } }); // Set the pagination token to the oldest event that we retrieved. - timelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS); + if (matrixEvents.length > 0) { + timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), + EventTimeline.BACKWARDS); + return true; + } else { + timeline.setPaginationToken("", EventTimeline.BACKWARDS); + return false; + } + } + + paginateTimelineWindow(room, timelineWindow, direction, limit) { + let tl; + + // TODO this is from the js-sdk, this should probably be exposed to + // us through the js-sdk. + const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { + var count = (direction == EventTimeline.BACKWARDS) ? + timeline.retreat(limit) : timeline.advance(limit); + + if (count) { + timelineWindow._eventCount += count; + var excess = timelineWindow._eventCount - timelineWindow._windowLimit; + + if (excess > 0) { + timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); + } + return true; + } + + return false; + }; + + // TODO these private fields should be somehow exposed in the js-sdk. + if (direction == EventTimeline.BACKWARDS) tl = timelineWindow._start; + else if (direction == EventTimeline.FORWARDS) tl = timelineWindow._end; + + if (!tl) return Promise.resolve(false); + if (tl.pendingPaginate) return tl.pendingPaginate; + + if (moveWindowCap(timelineWindow, tl, direction, limit)) { + return Promise.resolve(true); + } + + const paginationMethod = async (timelineWindow, timeline, room, direction, limit) => { + const timelineSet = timelineWindow._timelineSet; + const token = timeline.timeline.getPaginationToken(direction); + + const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, + room, limit, token, direction); + + moveWindowCap(timelineWindow, timeline, direction, limit) + timeline.pendingPaginate = null; + + return ret; + }; + + const paginationPromise = paginationMethod(timelineWindow, tl, room, + direction, limit); + tl.pendingPaginate = paginationPromise; + + return paginationPromise; } } From 70d394e668615d431a0c9f9ed4f2c16cad1c2a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 10:35:33 +0100 Subject: [PATCH 08/65] EventIndex: Update the imports for the new build system. --- src/components/structures/FilePanel.js | 2 +- src/indexing/EventIndex.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 74b434cdbf..0ef9331338 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import Matrix from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; -import {EventIndexPeg} from "../../indexing/EventIndexPeg"; +import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; /* diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 7263d8b2c4..cb2f646d07 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -17,11 +17,9 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; -import Matrix from 'matrix-js-sdk'; -import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; -import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; -import MatrixEvent from 'matrix-js-sdk/lib/models/event'; -import RoomMember from 'matrix-js-sdk/lib/models/room-member'; +import * as Matrix from 'matrix-js-sdk'; +import {EventTimelineSet} from 'matrix-js-sdk'; +import {EventTimeline} from 'matrix-js-sdk'; /* * Event indexing class that wraps the platform specific event indexing. @@ -448,7 +446,7 @@ export default class EventIndex { const matrixEvents = events.map(e => { const matrixEvent = eventMapper(e.event); - const member = new RoomMember(room.roomId, matrixEvent.getSender()); + const member = new Matrix.RoomMember(room.roomId, matrixEvent.getSender()); // We can't really reconstruct the whole room state from our // EventIndex to calculate the correct display name. Use the From 95b86b42d00b1b23cfaed2f1aa474b962d55fe2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 11:06:05 +0100 Subject: [PATCH 09/65] BaseEventIndexManager: Update the docs for the loadFileEvents method. --- src/indexing/BaseEventIndexManager.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index d7b322bb1a..c4758bcaa3 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -215,11 +215,14 @@ export default class BaseEventIndexManager { * should be loaded. * @param {number} args.limit The maximum number of events to return. * @param {string} args.fromEvent An event id of a previous event returned - * by this method. If set events that are older than the event with the - * given event ID will be returned. + * by this method. Passing this means that we are going to continue loading + * events from this point in the history. + * @param {string} args.direction The direction to which we should continue + * loading events from. This is used only if fromEvent is used as well. * - * @return {Promise<[EventAndProfile]>} A promise that will resolve to an array - * of Matrix events that contain mxc URLs. + * @return {Promise<[EventAndProfile]>} A promise that will resolve to an + * array of Matrix events that contain mxc URLs accompanied with the + * historic profile of the sender. */ async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> { throw new Error("Unimplemented"); From ccfe3c7e70e9b9d5e8beddf7e8dc4c3e111bab56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 11:52:20 +0100 Subject: [PATCH 10/65] FilePanel/EventIndex: Fix lint errors. --- src/components/structures/FilePanel.js | 6 +++--- src/indexing/EventIndex.js | 27 +++++++++++++------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 0ef9331338..6faec27284 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -64,7 +64,8 @@ const FilePanel = createReactClass({ // FIXME: we shouldn't be doing this every time we change room - see comment above. // TODO: Remove this stale comment? Which comment above? - const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter) + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, + filter); filter.filterId = filterId; const timelineSet = room.getOrCreateFilteredTimelineSet(filter); @@ -96,7 +97,7 @@ const FilePanel = createReactClass({ let timelineSet; try { - timelineSet = await this.fetchFileEventsServer(room) + timelineSet = await this.fetchFileEventsServer(room); if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); @@ -104,7 +105,6 @@ const FilePanel = createReactClass({ } this.setState({ timelineSet: timelineSet }); - } catch (error) { console.error("Failed to get or create file panel filter", error); } diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index cb2f646d07..0e48af749c 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -18,7 +18,6 @@ import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; import * as Matrix from 'matrix-js-sdk'; -import {EventTimelineSet} from 'matrix-js-sdk'; import {EventTimeline} from 'matrix-js-sdk'; /* @@ -420,27 +419,27 @@ export default class EventIndex { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); - let loadArgs = { + const loadArgs = { roomId: room.roomId, - limit: limit - } + limit: limit, + }; if (fromEvent) { loadArgs.fromEvent = fromEvent; loadArgs.direction = direction; } - let events + let events; // Get our events from the event index. try { events = await indexManager.loadFileEvents(loadArgs); } catch (e) { console.log("EventIndex: Error getting file events", e); - return [] + return []; } - let eventMapper = client.getEventMapper(); + const eventMapper = client.getEventMapper(); // Turn the events into MatrixEvent objects. const matrixEvents = events.map(e => { @@ -466,8 +465,8 @@ export default class EventIndex { room_id: matrixEvent.getRoomId(), sender: matrixEvent.getSender(), origin_server_ts: matrixEvent.getTs(), - state_key: matrixEvent.getSender() - } + state_key: matrixEvent.getSender(), + }, ); // We set this manually to avoid emitting RoomMember.membership and @@ -483,13 +482,13 @@ export default class EventIndex { async populateFileTimeline(timelineSet, timeline, room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { - let matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { timelineSet.addEventToTimeline(e, timeline, - direction == EventTimeline.BACKWARDS) + direction == EventTimeline.BACKWARDS); } }); @@ -510,12 +509,12 @@ export default class EventIndex { // TODO this is from the js-sdk, this should probably be exposed to // us through the js-sdk. const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - var count = (direction == EventTimeline.BACKWARDS) ? + const count = (direction == EventTimeline.BACKWARDS) ? timeline.retreat(limit) : timeline.advance(limit); if (count) { timelineWindow._eventCount += count; - var excess = timelineWindow._eventCount - timelineWindow._windowLimit; + const excess = timelineWindow._eventCount - timelineWindow._windowLimit; if (excess > 0) { timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); @@ -544,7 +543,7 @@ export default class EventIndex { const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); - moveWindowCap(timelineWindow, timeline, direction, limit) + moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; return ret; From 9978fee51208b2c6031f611fdbc25f8752d8d7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 10:06:20 +0100 Subject: [PATCH 11/65] Lifecycle: Comment why we need to initialize the index before the client. --- src/Lifecycle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index aab7884b2e..b52e5e9da5 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -588,6 +588,9 @@ async function startMatrixClient(startSyncing=true) { Mjolnir.sharedInstance().start(); if (startSyncing) { + // The client might want to populate some views with events from the + // index (e.g. the FilePanel), therefore initialize the event index + // before the client. await EventIndexPeg.init(); await MatrixClientPeg.start(); } else { From 0c854fce9b19543a57e2494b24b494faefcca35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 10:09:58 +0100 Subject: [PATCH 12/65] FilePanel: Remove a stale comment. --- src/components/structures/FilePanel.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6faec27284..cb788cd6d2 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -62,8 +62,6 @@ const FilePanel = createReactClass({ }, ); - // FIXME: we shouldn't be doing this every time we change room - see comment above. - // TODO: Remove this stale comment? Which comment above? const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter); filter.filterId = filterId; From b4c8a686cec16290f56603d3fe925ddc1d975830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:41:55 +0100 Subject: [PATCH 13/65] EventIndex: Don't import the whole js-sdk. --- src/indexing/EventIndex.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 0e48af749c..67a3d4cace 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -16,9 +16,7 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; - -import * as Matrix from 'matrix-js-sdk'; -import {EventTimeline} from 'matrix-js-sdk'; +import {EventTimeline, RoomMember} from 'matrix-js-sdk'; /* * Event indexing class that wraps the platform specific event indexing. @@ -445,7 +443,7 @@ export default class EventIndex { const matrixEvents = events.map(e => { const matrixEvent = eventMapper(e.event); - const member = new Matrix.RoomMember(room.roomId, matrixEvent.getSender()); + const member = new RoomMember(room.roomId, matrixEvent.getSender()); // We can't really reconstruct the whole room state from our // EventIndex to calculate the correct display name. Use the From 0b4b9d8d5d166ed7454b7d729fc5a08478ac6fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:42:46 +0100 Subject: [PATCH 14/65] EventIndex: Simplify the json event getting logic. --- src/indexing/EventIndex.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 67a3d4cace..e361f66edc 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -172,10 +172,7 @@ export default class EventIndex { } const jsonEvent = ev.toJSON(); - - let e; - if (ev.isEncrypted()) e = jsonEvent.decrypted; - else e = jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; const profile = { displayname: ev.sender.rawDisplayName, @@ -311,10 +308,7 @@ export default class EventIndex { // consume. const events = filteredEvents.map((ev) => { const jsonEvent = ev.toJSON(); - - let e; - if (ev.isEncrypted()) e = jsonEvent.decrypted; - else e = jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; let profile = {}; if (e.sender in profiles) profile = profiles[e.sender]; From 4cf44cf5a561f801c107b121f109e18236db0e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:43:20 +0100 Subject: [PATCH 15/65] EventIndex/FilePanel: Allow longer lines. --- src/components/structures/FilePanel.js | 3 +-- src/indexing/EventIndex.js | 16 ++++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index cb788cd6d2..71e8143f0a 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -62,8 +62,7 @@ const FilePanel = createReactClass({ }, ); - const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, - filter); + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter); filter.filterId = filterId; const timelineSet = room.getOrCreateFilteredTimelineSet(filter); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index e361f66edc..cb77d92c27 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -479,15 +479,13 @@ export default class EventIndex { // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { - timelineSet.addEventToTimeline(e, timeline, - direction == EventTimeline.BACKWARDS); + timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS); } }); // Set the pagination token to the oldest event that we retrieved. if (matrixEvents.length > 0) { - timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), - EventTimeline.BACKWARDS); + timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), EventTimeline.BACKWARDS); return true; } else { timeline.setPaginationToken("", EventTimeline.BACKWARDS); @@ -501,7 +499,7 @@ export default class EventIndex { // TODO this is from the js-sdk, this should probably be exposed to // us through the js-sdk. const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - const count = (direction == EventTimeline.BACKWARDS) ? + const count = (direction === EventTimeline.BACKWARDS) ? timeline.retreat(limit) : timeline.advance(limit); if (count) { @@ -509,7 +507,7 @@ export default class EventIndex { const excess = timelineWindow._eventCount - timelineWindow._windowLimit; if (excess > 0) { - timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); + timelineWindow.unpaginate(3, direction !== EventTimeline.BACKWARDS); } return true; } @@ -532,8 +530,7 @@ export default class EventIndex { const timelineSet = timelineWindow._timelineSet; const token = timeline.timeline.getPaginationToken(direction); - const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, - room, limit, token, direction); + const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; @@ -541,8 +538,7 @@ export default class EventIndex { return ret; }; - const paginationPromise = paginationMethod(timelineWindow, tl, room, - direction, limit); + const paginationPromise = paginationMethod(timelineWindow, tl, room, direction, limit); tl.pendingPaginate = paginationPromise; return paginationPromise; From a0599dedf0b39e66c3ee49f4e772e3380667cd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 15:45:29 +0100 Subject: [PATCH 16/65] EventIndex: Use the newly exposed TimelineWindow methods. --- src/indexing/EventIndex.js | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index cb77d92c27..c081440233 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -494,35 +494,12 @@ export default class EventIndex { } paginateTimelineWindow(room, timelineWindow, direction, limit) { - let tl; - - // TODO this is from the js-sdk, this should probably be exposed to - // us through the js-sdk. - const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - const count = (direction === EventTimeline.BACKWARDS) ? - timeline.retreat(limit) : timeline.advance(limit); - - if (count) { - timelineWindow._eventCount += count; - const excess = timelineWindow._eventCount - timelineWindow._windowLimit; - - if (excess > 0) { - timelineWindow.unpaginate(3, direction !== EventTimeline.BACKWARDS); - } - return true; - } - - return false; - }; - - // TODO these private fields should be somehow exposed in the js-sdk. - if (direction == EventTimeline.BACKWARDS) tl = timelineWindow._start; - else if (direction == EventTimeline.FORWARDS) tl = timelineWindow._end; + const tl = timelineWindow.getTimelineIndex(direction); if (!tl) return Promise.resolve(false); if (tl.pendingPaginate) return tl.pendingPaginate; - if (moveWindowCap(timelineWindow, tl, direction, limit)) { + if (timelineWindow.extend(direction, limit)) { return Promise.resolve(true); } @@ -532,8 +509,8 @@ export default class EventIndex { const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); - moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; + timelineWindow.extend(direction, limit); return ret; }; From 735ba4fd33d7fcdfb5e6e36141176f20f99395dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:11:54 +0100 Subject: [PATCH 17/65] EventIndex: Correctly populate events on initial fill requests. --- src/components/structures/FilePanel.js | 2 +- src/indexing/EventIndex.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 71e8143f0a..c96f8770ad 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -98,7 +98,7 @@ const FilePanel = createReactClass({ if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); - await eventIndex.populateFileTimeline(timelineSet, timeline, room, 1); + await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10); } this.setState({ timelineSet: timelineSet }); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c081440233..93c640cf8e 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -476,6 +476,16 @@ export default class EventIndex { fromEvent = null, direction = EventTimeline.BACKWARDS) { const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + // If this is a normal fill request, not a pagination request, we need + // to get our events in the BACKWARDS direction but populate them in the + // forwards direction. + // This needs to happen because a fill request might come with an + // exisitng timeline e.g. if you close and re-open the FilePanel. + if (fromEvent === null) { + matrixEvents.reverse(); + direction = direction == EventTimeline.BACKWARDS ? EventTimeline.FORWARDS: EventTimeline.BACKWARDS; + } + // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { From f917c2faea6fabd118bbf08251b9e341c793c740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:21:11 +0100 Subject: [PATCH 18/65] FilePanel: Listen for live events and add them to an open FilePanel. --- src/components/structures/FilePanel.js | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index c96f8770ad..a25f8babd1 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -30,6 +30,7 @@ import { _t } from '../../languageHandler'; */ const FilePanel = createReactClass({ displayName: 'FilePanel', + decryptingEvents: new Set(), propTypes: { roomId: PropTypes.string.isRequired, @@ -41,8 +42,64 @@ const FilePanel = createReactClass({ }; }, + onRoomTimeline (ev, room, toStartOfTimeline, removed, data) { + if (room.roomId !== this.props.roomId) return; + if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; + + if (ev.isBeingDecrypted()) { + this.decryptingEvents.add(ev.getId()); + } else { + this.addEncryptedLiveEvent(ev); + } + }, + + onEventDecrypted (ev, err) { + if (ev.getRoomId() !== this.props.roomId) return; + const eventId = ev.getId(); + + if (!this.decryptingEvents.delete(eventId)) return; + if (err) return; + + this.addEncryptedLiveEvent(ev); + }, + + addEncryptedLiveEvent(ev, toStartOfTimeline) { + if (!this.state.timelineSet) return; + + const timeline = this.state.timelineSet.getLiveTimeline(); + if (ev.getType() !== "m.room.message") return; + if (["m.file", "m.image", "m.video", "m.audio"].indexOf(ev.getContent().msgtype) == -1) { + return; + } + + if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) { + this.state.timelineSet.addEventToTimeline(ev, timeline, false); + } + }, + async componentDidMount() { + const client = MatrixClientPeg.get(); + await this.updateTimelineSet(this.props.roomId); + + if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + + if (EventIndexPeg.get() !== null) { + client.on('Room.timeline', this.onRoomTimeline.bind(this)); + client.on('Event.decrypted', this.onEventDecrypted.bind(this)); + } + }, + + componentWillUnmount() { + const client = MatrixClientPeg.get(); + if (client === null) return; + + if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + + if (EventIndexPeg.get() !== null) { + client.removeListener('Room.timeline', this.onRoomTimeline.bind(this)); + client.removeListener('Event.decrypted', this.onEventDecrypted.bind(this)); + } }, async fetchFileEventsServer(room) { From c5e8753b0535d898961e4e8a0d3d469a86d39fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:26:40 +0100 Subject: [PATCH 19/65] FilePanel: Don't import the whole of the js-sdk. --- src/components/structures/FilePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index a25f8babd1..7ee86436a5 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -19,7 +19,7 @@ import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import Matrix from 'matrix-js-sdk'; +import {Filter} from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; @@ -105,7 +105,7 @@ const FilePanel = createReactClass({ async fetchFileEventsServer(room) { const client = MatrixClientPeg.get(); - const filter = new Matrix.Filter(client.credentials.userId); + const filter = new Filter(client.credentials.userId); filter.setDefinition( { "room": { From c3418df9197ba46b9275f22e600997ae089f26f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:31:49 +0100 Subject: [PATCH 20/65] FilePanel: Remove whitespace before two function definitions. --- src/components/structures/FilePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 7ee86436a5..e03c587e61 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -42,7 +42,7 @@ const FilePanel = createReactClass({ }; }, - onRoomTimeline (ev, room, toStartOfTimeline, removed, data) { + onRoomTimeline(ev, room, toStartOfTimeline, removed, data) { if (room.roomId !== this.props.roomId) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; @@ -53,7 +53,7 @@ const FilePanel = createReactClass({ } }, - onEventDecrypted (ev, err) { + onEventDecrypted(ev, err) { if (ev.getRoomId() !== this.props.roomId) return; const eventId = ev.getId(); From 9706114bb571a903fd607838229717fe767fe465 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 16:54:31 +0000 Subject: [PATCH 21/65] move E2E_STATE to E2EIcon to simplify imports --- src/components/views/rooms/E2EIcon.js | 18 +++++++++++++----- src/components/views/rooms/EventTile.js | 8 +------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 545d1fd7ed..6ee20023ff 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -14,22 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from "react"; import classNames from 'classnames'; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import SettingsStore from '../../../settings/SettingsStore'; +export const E2E_STATE = { + VERIFIED: "verified", + WARNING: "warning", + UNKNOWN: "unknown", + NORMAL: "normal", +}; + export default function(props) { - const { isUser } = props; - const isNormal = props.status === "normal"; - const isWarning = props.status === "warning"; - const isVerified = props.status === "verified"; + const { isUser, status, className } = props; + const isNormal = status === E2E_STATE.NORMAL; + const isWarning = status === E2E_STATE.WARNING; + const isVerified = status === E2E_STATE.VERIFIED; const e2eIconClasses = classNames({ mx_E2EIcon: true, mx_E2EIcon_warning: isWarning, mx_E2EIcon_normal: isNormal, mx_E2EIcon_verified: isVerified, - }, props.className); + }, className); let e2eTitle; const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing"); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 634b77c9e1..940515f02e 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -33,6 +33,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {ALL_RULE_TYPES} from "../../../mjolnir/BanList"; import * as ObjectUtils from "../../../ObjectUtils"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {E2E_STATE} from "./E2EIcon"; const eventTileTypes = { 'm.room.message': 'messages.MessageEvent', @@ -66,13 +67,6 @@ const stateEventTileTypes = { 'm.room.related_groups': 'messages.TextualEvent', }; -const E2E_STATE = { - VERIFIED: "verified", - WARNING: "warning", - UNKNOWN: "unknown", - NORMAL: "normal", -}; - // Add all the Mjolnir stuff to the renderer for (const evType of ALL_RULE_TYPES) { stateEventTileTypes[evType] = 'messages.TextualEvent'; From b7d1c17ad1453c237e99e3bd87d1fdb722bb01d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 16:56:27 +0000 Subject: [PATCH 22/65] simple optimization to bail out of check on first failure --- src/components/structures/RoomView.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9b02f6d503..23d3002faa 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -812,10 +812,10 @@ export default createReactClass({ /* Check all verified user devices. */ for (const userId of verified) { const devices = await cli.getStoredDevicesForUser(userId); - const allDevicesVerified = devices.every(({deviceId}) => { - return cli.checkDeviceTrust(userId, deviceId).isVerified(); + const anyDeviceNotVerified = devices.some(({deviceId}) => { + return !cli.checkDeviceTrust(userId, deviceId).isVerified(); }); - if (!allDevicesVerified) { + if (anyDeviceNotVerified) { this.setState({ e2eStatus: "warning", }); From 139b5663fe4015934d3833bdfe0cdf9b4e756ada Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 21:14:53 -0700 Subject: [PATCH 23/65] Remove user lists feature flag, making it the default Fixes https://github.com/vector-im/riot-web/issues/11201 --- src/RoomInvite.js | 156 +++------------------------------------ src/settings/Settings.js | 6 -- 2 files changed, 12 insertions(+), 150 deletions(-) diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 2eccf69b0f..839d677069 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -20,13 +20,8 @@ import React from 'react'; import {MatrixClientPeg} from './MatrixClientPeg'; import MultiInviter from './utils/MultiInviter'; import Modal from './Modal'; -import { getAddressType } from './UserAddress'; -import createRoom from './createRoom'; import * as sdk from './'; -import dis from './dispatcher'; -import DMRoomMap from './utils/DMRoomMap'; import { _t } from './languageHandler'; -import SettingsStore from "./settings/SettingsStore"; import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog"; /** @@ -44,64 +39,21 @@ export function inviteMultipleToRoom(roomId, addrs) { } export function showStartChatInviteDialog() { - if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) { - // This new dialog handles the room creation internally - we don't need to worry about it. - const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); - Modal.createTrackedDialog( - 'Start DM', '', InviteDialog, {kind: KIND_DM}, - /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, - ); - return; - } - - const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); - - Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, { - title: _t('Start a chat'), - description: _t("Who would you like to communicate with?"), - placeholder: (validAddressTypes) => { - // The set of valid address type can be mutated inside the dialog - // when you first have no IS but agree to use one in the dialog. - if (validAddressTypes.includes('email')) { - return _t("Email, name or Matrix ID"); - } - return _t("Name or Matrix ID"); - }, - validAddressTypes: ['mx-user-id', 'email'], - button: _t("Start Chat"), - onFinished: _onStartDmFinished, - }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); + // This dialog handles the room creation internally - we don't need to worry about it. + const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); + Modal.createTrackedDialog( + 'Start DM', '', InviteDialog, {kind: KIND_DM}, + /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, + ); } export function showRoomInviteDialog(roomId) { - if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) { - // This new dialog handles the room creation internally - we don't need to worry about it. - const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); - Modal.createTrackedDialog( - 'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId}, - /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, - ); - return; - } - - const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); - - Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, { - title: _t('Invite new room members'), - button: _t('Send Invites'), - placeholder: (validAddressTypes) => { - // The set of valid address type can be mutated inside the dialog - // when you first have no IS but agree to use one in the dialog. - if (validAddressTypes.includes('email')) { - return _t("Email, name or Matrix ID"); - } - return _t("Name or Matrix ID"); - }, - validAddressTypes: ['mx-user-id', 'email'], - onFinished: (shouldInvite, addrs) => { - _onRoomInviteFinished(roomId, shouldInvite, addrs); - }, - }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); + // This dialog handles the room creation internally - we don't need to worry about it. + const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); + Modal.createTrackedDialog( + 'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId}, + /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, + ); } /** @@ -122,60 +74,6 @@ export function isValid3pidInvite(event) { return true; } -// TODO: Canonical DMs replaces this -function _onStartDmFinished(shouldInvite, addrs) { - if (!shouldInvite) return; - - const addrTexts = addrs.map((addr) => addr.address); - - if (_isDmChat(addrTexts)) { - const rooms = _getDirectMessageRooms(addrTexts[0]); - if (rooms.length > 0) { - // A Direct Message room already exists for this user, so reuse it - dis.dispatch({ - action: 'view_room', - room_id: rooms[0], - should_peek: false, - joining: false, - }); - } else { - // Start a new DM chat - createRoom({dmUserId: addrTexts[0]}).catch((err) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to start chat', '', ErrorDialog, { - title: _t("Failed to start chat"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - }); - } - } else if (addrTexts.length === 1) { - // Start a new DM chat - createRoom({dmUserId: addrTexts[0]}).catch((err) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to start chat', '', ErrorDialog, { - title: _t("Failed to start chat"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - }); - } else { - // Start multi user chat - let room; - createRoom().then((roomId) => { - room = MatrixClientPeg.get().getRoom(roomId); - return inviteMultipleToRoom(roomId, addrTexts); - }).then((result) => { - return _showAnyInviteErrors(result.states, room, result.inviter); - }).catch((err) => { - console.error(err.stack); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { - title: _t("Failed to invite"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - }); - } -} - export function inviteUsersToRoom(roomId, userIds) { return inviteMultipleToRoom(roomId, userIds).then((result) => { const room = MatrixClientPeg.get().getRoom(roomId); @@ -190,24 +88,6 @@ export function inviteUsersToRoom(roomId, userIds) { }); } -function _onRoomInviteFinished(roomId, shouldInvite, addrs) { - if (!shouldInvite) return; - - const addrTexts = addrs.map((addr) => addr.address); - - // Invite new users to a room - inviteUsersToRoom(roomId, addrTexts); -} - -// TODO: Immutable DMs replaces this -function _isDmChat(addrTexts) { - if (addrTexts.length === 1 && getAddressType(addrTexts[0]) === 'mx-user-id') { - return true; - } else { - return false; - } -} - function _showAnyInviteErrors(addrs, room, inviter) { // Show user any errors const failedUsers = Object.keys(addrs).filter(a => addrs[a] === 'error'); @@ -243,15 +123,3 @@ function _showAnyInviteErrors(addrs, room, inviter) { return addrs; } - -function _getDirectMessageRooms(addr) { - const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); - const dmRooms = dmRoomMap.getDMRoomsForUserId(addr); - const rooms = dmRooms.filter((dmRoom) => { - const room = MatrixClientPeg.get().getRoom(dmRoom); - if (room) { - return room.getMyMembership() === 'join'; - } - }); - return rooms; -} diff --git a/src/settings/Settings.js b/src/settings/Settings.js index eacf63e55d..e908861821 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -128,12 +128,6 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, - "feature_ftue_dms": { - isFeature: true, - displayName: _td("New invite dialog"), - supportedLevels: LEVELS_FEATURE, - default: false, - }, "feature_presence_in_room_list": { isFeature: true, displayName: _td("Show a presence dot next to DMs in the room list"), From a196ecc8911fc951ac18ab6ed28c2a7426f7bc99 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 21:17:40 -0700 Subject: [PATCH 24/65] Fix i18n --- src/i18n/strings/en_EN.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d19cbb9bfd..6a68c70d56 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -121,15 +121,8 @@ "Moderator": "Moderator", "Admin": "Admin", "Custom (%(level)s)": "Custom (%(level)s)", - "Start a chat": "Start a chat", - "Who would you like to communicate with?": "Who would you like to communicate with?", - "Email, name or Matrix ID": "Email, name or Matrix ID", - "Start Chat": "Start Chat", - "Invite new room members": "Invite new room members", - "Send Invites": "Send Invites", - "Failed to start chat": "Failed to start chat", - "Operation failed": "Operation failed", "Failed to invite": "Failed to invite", + "Operation failed": "Operation failed", "Failed to invite users to the room:": "Failed to invite users to the room:", "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", "You need to be logged in.": "You need to be logged in.", @@ -372,7 +365,6 @@ "Render simple counters in room header": "Render simple counters in room header", "Multiple integration managers": "Multiple integration managers", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", - "New invite dialog": "New invite dialog", "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", @@ -943,6 +935,7 @@ "Invite": "Invite", "Share Link to User": "Share Link to User", "User Options": "User Options", + "Start a chat": "Start a chat", "Direct chats": "Direct chats", "Remove recent messages": "Remove recent messages", "Unmute": "Unmute", From 65450e893c9862ef2ef7cdaeb22267bd19f3d731 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 22:40:02 -0700 Subject: [PATCH 25/65] Try and fix e2e tests for new invite dialog --- test/end-to-end-tests/src/usecases/invite.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/invite.js b/test/end-to-end-tests/src/usecases/invite.js index 6bee5dfd6f..75ebc61a88 100644 --- a/test/end-to-end-tests/src/usecases/invite.js +++ b/test/end-to-end-tests/src/usecases/invite.js @@ -31,10 +31,11 @@ module.exports = async function invite(session, userId) { } const inviteButton = await session.query(".mx_MemberList_invite"); await inviteButton.click(); - const inviteTextArea = await session.query(".mx_AddressPickerDialog textarea"); + const inviteTextArea = await session.query(".mx_InviteDialog_editor textarea"); await inviteTextArea.type(userId); - await inviteTextArea.press("Enter"); - const confirmButton = await session.query(".mx_Dialog_primary"); + const selectUserItem = await session.query(".mx_InviteDialog_roomTile"); + await selectUserItem.click(); + const confirmButton = await session.query(".mx_InviteDialog_goButton"); await confirmButton.click(); session.log.done(); }; From d68db74efe9bfb6952fe268ed53f97d9f305293c Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 17:17:40 +0000 Subject: [PATCH 26/65] Room list reflects encryption state --- res/css/views/rooms/_RoomHeader.scss | 4 +- res/css/views/rooms/_RoomTile.scss | 13 ++++ src/components/structures/RoomView.js | 1 + src/components/views/rooms/RoomTile.js | 101 +++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 0d92247735..a235a47fdd 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -22,7 +22,9 @@ limitations under the License. margin: 0; position: absolute; bottom: 0; - right: -5px; + right: -1px; + height: 10px; + width: 10px } } diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index db2c09f6f1..a36d781669 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -98,6 +98,19 @@ limitations under the License. z-index: 2; } +// Note we match .mx_E2EIcon to make sure this matches more tightly than just +// .mx_E2EIcon on its own +.mx_RoomTile_e2eIcon.mx_E2EIcon { + height: 10px; + width: 10px; + display: block; + position: absolute; + bottom: -0px; + right: -1px; + z-index: 1; + margin: 0; +} + .mx_RoomTile_name { font-size: 14px; padding: 0 6px; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9b02f6d503..f10c98dd98 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -796,6 +796,7 @@ export default createReactClass({ return; } + // Duplication betwen here and _updateE2eStatus in RoomTile /* At this point, the user has encryption on and cross-signing on */ const e2eMembers = await room.getEncryptionTargetMembers(); const verified = []; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index f4f5fa10fc..c277433a5c 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -33,6 +33,8 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; +import E2EIcon from './E2EIcon'; +import rate_limited_func from '../../../ratelimitedfunc'; export default createReactClass({ displayName: 'RoomTile', @@ -70,6 +72,7 @@ export default createReactClass({ notificationCount: this.props.room.getUnreadNotificationCount(), selected: this.props.room.roomId === RoomViewStore.getRoomId(), statusMessage: this._getStatusMessage(), + e2eStatus: null, }); }, @@ -102,6 +105,85 @@ export default createReactClass({ return statusUser._unstable_statusMessage; }, + onRoomStateMember: function(ev, state, member) { + // we only care about leaving users + // because trust state will change if someone joins a megolm session anyway + if (member.membership !== "leave") { + return; + } + // ignore members in other rooms + if (member.roomId !== this.props.room.roomId) { + return; + } + + this._updateE2eStatus(); + }, + + onUserVerificationChanged: function(userId, _trustStatus) { + if (!this.props.room.getMember(userId)) { + // Not in this room + return; + } + this._updateE2eStatus(); + }, + + onRoomTimeline: function(ev, room) { + if (!room) return; + if (room.roomId != this.props.room.roomId) return; + console.warn("e2e onRoomTimeline"); + if (ev.getType() !== "m.room.encryption") return; + console.warn("e2e onRoomTimeline ENCRYPTION"); + MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); + this.onFindingRoomToBeEncrypted(); + }, + + onFindingRoomToBeEncrypted: function () { + const cli = MatrixClientPeg.get(); + cli.on("RoomState.members", this.onRoomStateMember); + cli.on("userTrustStatusChanged", this.onUserVerificationChanged); + + this._updateE2eStatus(); + }, + + _updateE2eStatus: async function() { + const cli = MatrixClientPeg.get(); + if (!cli.isRoomEncrypted(this.props.room.roomId)) { + return; + } + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + return; + } + + // Duplication between here and _updateE2eStatus in RoomView + const e2eMembers = await this.props.room.getEncryptionTargetMembers(); + const verified = []; + const unverified = []; + e2eMembers.map(({userId}) => userId) + .filter((userId) => userId !== cli.getUserId()) + .forEach((userId) => { + (cli.checkUserTrust(userId).isCrossSigningVerified() ? + verified : unverified).push(userId) + }); + + /* Check all verified user devices. */ + for (const userId of verified) { + const devices = await cli.getStoredDevicesForUser(userId); + const allDevicesVerified = devices.every(({deviceId}) => { + return cli.checkDeviceTrust(userId, deviceId).isVerified(); + }); + if (!allDevicesVerified) { + this.setState({ + e2eStatus: "warning", + }); + return; + } + } + + this.setState({ + e2eStatus: unverified.length === 0 ? "verified" : "normal", + }); + }, + onRoomName: function(room) { if (room !== this.props.room) return; this.setState({ @@ -151,10 +233,19 @@ export default createReactClass({ }, componentDidMount: function() { + /* We bind here rather than in the definition because otherwise we wind up with the + method only being callable once every 500ms across all instances, which would be wrong */ + this._updateE2eStatus = rate_limited_func(this._updateE2eStatus, 500); + const cli = MatrixClientPeg.get(); cli.on("accountData", this.onAccountData); cli.on("Room.name", this.onRoomName); cli.on("RoomState.events", this.onJoinRule); + if (cli.isRoomEncrypted(this.props.room.roomId)) { + this.onFindingRoomToBeEncrypted(); + } else { + cli.on("Room.timeline", this.onRoomTimeline); + } ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange); this.dispatcherRef = dis.register(this.onAction); @@ -172,6 +263,9 @@ export default createReactClass({ MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); cli.removeListener("RoomState.events", this.onJoinRule); + cli.removeListener("RoomState.members", this.onRoomStateMember); + cli.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); + cli.removeListener("Room.timeline", this.onRoomTimeline); } ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange); dis.unregister(this.dispatcherRef); @@ -433,6 +527,12 @@ export default createReactClass({ privateIcon =
; } + let e2eIcon = null; + // For now, skip the icon for DMs. Possibly we want to move the DM icon elsewhere? + if (!dmUserId && this.state.e2eStatus) { + e2eIcon = + } + return {({onFocus, isActive, ref}) => @@ -453,6 +553,7 @@ export default createReactClass({
{ dmIndicator } + { e2eIcon }
{ privateIcon } From ee33c7cd627bdc378d1760f288d0b027f94bf6f1 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 23 Jan 2020 11:09:52 +0000 Subject: [PATCH 27/65] lint --- src/components/views/rooms/RoomTile.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index c277433a5c..6ad7bc3f2d 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -34,6 +34,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import E2EIcon from './E2EIcon'; +// eslint-disable-next-line camelcase import rate_limited_func from '../../../ratelimitedfunc'; export default createReactClass({ @@ -137,7 +138,7 @@ export default createReactClass({ this.onFindingRoomToBeEncrypted(); }, - onFindingRoomToBeEncrypted: function () { + onFindingRoomToBeEncrypted: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.members", this.onRoomStateMember); cli.on("userTrustStatusChanged", this.onUserVerificationChanged); @@ -162,7 +163,7 @@ export default createReactClass({ .filter((userId) => userId !== cli.getUserId()) .forEach((userId) => { (cli.checkUserTrust(userId).isCrossSigningVerified() ? - verified : unverified).push(userId) + verified : unverified).push(userId); }); /* Check all verified user devices. */ @@ -530,7 +531,7 @@ export default createReactClass({ let e2eIcon = null; // For now, skip the icon for DMs. Possibly we want to move the DM icon elsewhere? if (!dmUserId && this.state.e2eStatus) { - e2eIcon = + e2eIcon = ; } return From a409b9b96fe706e02d88c8956886be0d103da794 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 23 Jan 2020 11:14:01 +0000 Subject: [PATCH 28/65] whoops, left some printfs --- src/components/views/rooms/RoomTile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 6ad7bc3f2d..0b50d85ff6 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -131,9 +131,7 @@ export default createReactClass({ onRoomTimeline: function(ev, room) { if (!room) return; if (room.roomId != this.props.room.roomId) return; - console.warn("e2e onRoomTimeline"); if (ev.getType() !== "m.room.encryption") return; - console.warn("e2e onRoomTimeline ENCRYPTION"); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); this.onFindingRoomToBeEncrypted(); }, From 78e1d1674f15d99a182158fcb73974284512e8c3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 13:00:17 +0000 Subject: [PATCH 29/65] reactor E2EIcon for reusability --- src/components/views/rooms/E2EIcon.js | 100 +++++++++++++------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 6ee20023ff..36f230f472 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -15,8 +15,10 @@ limitations under the License. */ import React from "react"; +import PropTypes from "prop-types"; import classNames from 'classnames'; -import { _t } from '../../../languageHandler'; + +import {_t, _td} from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import SettingsStore from '../../../settings/SettingsStore'; @@ -27,71 +29,65 @@ export const E2E_STATE = { NORMAL: "normal", }; -export default function(props) { - const { isUser, status, className } = props; - const isNormal = status === E2E_STATE.NORMAL; - const isWarning = status === E2E_STATE.WARNING; - const isVerified = status === E2E_STATE.VERIFIED; +const crossSigningUserTitles = { + [E2E_STATE.WARNING]: _td("This user has not verified all of their devices."), + [E2E_STATE.NORMAL]: _td("You have not verified this user. This user has verified all of their devices."), + [E2E_STATE.VERIFIED]: _td("You have verified this user. This user has verified all of their devices."), +}; +const crossSigningRoomTitles = { + [E2E_STATE.WARNING]: _td("Some users in this encrypted room are not verified by you or they have not verified " + + "their own devices."), + [E2E_STATE.VERIFIED]: _td("All users in this encrypted room are verified by you and they have verified their " + + "own devices."), +}; +const legacyUserTitles = { + [E2E_STATE.WARNING]: _td("Some devices for this user are not trusted"), + [E2E_STATE.VERIFIED]: _td("All devices for this user are trusted"), +}; +const legacyRoomTitles = { + [E2E_STATE.WARNING]: _td("Some devices in this encrypted room are not trusted"), + [E2E_STATE.VERIFIED]: _td("All devices in this encrypted room are trusted"), +}; + +const E2EIcon = ({isUser, status, className, size, onClick}) => { const e2eIconClasses = classNames({ mx_E2EIcon: true, - mx_E2EIcon_warning: isWarning, - mx_E2EIcon_normal: isNormal, - mx_E2EIcon_verified: isVerified, + mx_E2EIcon_warning: status === E2E_STATE.WARNING, + mx_E2EIcon_normal: status === E2E_STATE.NORMAL, + mx_E2EIcon_verified: status === E2E_STATE.VERIFIED, }, className); - let e2eTitle; + let e2eTitle; const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing"); if (crossSigning && isUser) { - if (isWarning) { - e2eTitle = _t( - "This user has not verified all of their devices.", - ); - } else if (isNormal) { - e2eTitle = _t( - "You have not verified this user. " + - "This user has verified all of their devices.", - ); - } else if (isVerified) { - e2eTitle = _t( - "You have verified this user. " + - "This user has verified all of their devices.", - ); - } + e2eTitle = crossSigningUserTitles[status]; } else if (crossSigning && !isUser) { - if (isWarning) { - e2eTitle = _t( - "Some users in this encrypted room are not verified by you or " + - "they have not verified their own devices.", - ); - } else if (isVerified) { - e2eTitle = _t( - "All users in this encrypted room are verified by you and " + - "they have verified their own devices.", - ); - } + e2eTitle = crossSigningRoomTitles[status]; } else if (!crossSigning && isUser) { - if (isWarning) { - e2eTitle = _t("Some devices for this user are not trusted"); - } else if (isVerified) { - e2eTitle = _t("All devices for this user are trusted"); - } + e2eTitle = legacyUserTitles[status]; } else if (!crossSigning && !isUser) { - if (isWarning) { - e2eTitle = _t("Some devices in this encrypted room are not trusted"); - } else if (isVerified) { - e2eTitle = _t("All devices in this encrypted room are trusted"); - } + e2eTitle = legacyRoomTitles[status]; } let style = null; - if (props.size) { - style = {width: `${props.size}px`, height: `${props.size}px`}; + if (size) { + style = {width: `${size}px`, height: `${size}px`}; } - const icon = (
); - if (props.onClick) { - return ({ icon }); + const icon = (
); + if (onClick) { + return ({ icon }); } else { return icon; } -} +}; + +E2EIcon.propTypes = { + isUser: PropTypes.bool, + status: PropTypes.oneOf(Object.values(E2E_STATE)), + className: PropTypes.string, + size: PropTypes.number, + onClick: PropTypes.func, +}; + +export default E2EIcon; From 74b08ea4895a581ebb16a83a470e170213f7b24f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 14:38:17 +0000 Subject: [PATCH 30/65] Clean up E2EIcon for better maintainability --- src/components/views/rooms/E2EIcon.js | 13 +++---- src/hooks/useSettings.js | 52 +++++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 5 +-- 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/hooks/useSettings.js diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 36f230f472..7ac3b5af2d 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -1,5 +1,6 @@ /* Copyright 2019 New Vector Ltd +Copyright 2020 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. @@ -20,7 +21,7 @@ import classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; -import SettingsStore from '../../../settings/SettingsStore'; +import {useFeatureEnabled} from "../../../hooks/useSettings"; export const E2E_STATE = { VERIFIED: "verified", @@ -35,11 +36,11 @@ const crossSigningUserTitles = { [E2E_STATE.VERIFIED]: _td("You have verified this user. This user has verified all of their devices."), }; const crossSigningRoomTitles = { - [E2E_STATE.WARNING]: _td("Some users in this encrypted room are not verified by you or they have not verified " + - "their own devices."), - [E2E_STATE.VERIFIED]: _td("All users in this encrypted room are verified by you and they have verified their " + - "own devices."), + [E2E_STATE.WARNING]: _td("Someone is using an unknown device"), + [E2E_STATE.NORMAL]: _td("This room is end-to-end encrypted"), + [E2E_STATE.VERIFIED]: _td("Everyone in this room is verified"), }; + const legacyUserTitles = { [E2E_STATE.WARNING]: _td("Some devices for this user are not trusted"), [E2E_STATE.VERIFIED]: _td("All devices for this user are trusted"), @@ -58,7 +59,7 @@ const E2EIcon = ({isUser, status, className, size, onClick}) => { }, className); let e2eTitle; - const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing"); + const crossSigning = useFeatureEnabled("feature_cross_signing"); if (crossSigning && isUser) { e2eTitle = crossSigningUserTitles[status]; } else if (crossSigning && !isUser) { diff --git a/src/hooks/useSettings.js b/src/hooks/useSettings.js new file mode 100644 index 0000000000..151a6369de --- /dev/null +++ b/src/hooks/useSettings.js @@ -0,0 +1,52 @@ +/* +Copyright 2020 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 {useEffect, useState} from "react"; +import SettingsStore from '../settings/SettingsStore'; + +// Hook to fetch the value of a setting and dynamically update when it changes +export const useSettingValue = (settingName, roomId = null, excludeDefault = false) => { + const [value, setValue] = useState(SettingsStore.getValue(settingName, roomId, excludeDefault)); + + useEffect(() => { + const ref = SettingsStore.watchSetting(settingName, roomId, () => { + setValue(SettingsStore.getValue(settingName, roomId, excludeDefault)); + }); + // clean-up + return () => { + SettingsStore.unwatchSetting(ref); + }; + }, [settingName, roomId, excludeDefault]); + + return value; +}; + +// Hook to fetch whether a feature is enabled and dynamically update when that changes +export const useFeatureEnabled = (featureName, roomId = null) => { + const [enabled, setEnabled] = useState(SettingsStore.isFeatureEnabled(featureName, roomId)); + + useEffect(() => { + const ref = SettingsStore.watchSetting(featureName, roomId, () => { + setEnabled(SettingsStore.isFeatureEnabled(featureName, roomId)); + }); + // clean-up + return () => { + SettingsStore.unwatchSetting(ref); + }; + }, [featureName, roomId]); + + return enabled; +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d19cbb9bfd..fb2898ae3d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -884,8 +884,9 @@ "This user has not verified all of their devices.": "This user has not verified all of their devices.", "You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.", "You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.", - "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.", - "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.", + "Someone is using an unknown device": "Someone is using an unknown device", + "This room is end-to-end encrypted": "This room is end-to-end encrypted", + "Everyone in this room is verified": "Everyone in this room is verified", "Some devices for this user are not trusted": "Some devices for this user are not trusted", "All devices for this user are trusted": "All devices for this user are trusted", "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", From 662b34c8dbc2196b4061f08c8555c2a1e02cf5a2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 14:38:39 +0000 Subject: [PATCH 31/65] Update MessageComposer placeholder and e2e icon size --- res/css/views/rooms/_MessageComposer.scss | 2 + src/components/views/rooms/MessageComposer.js | 46 ++++++++++--------- src/i18n/strings/en_EN.json | 4 +- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 5efca51844..fae9d0dfe3 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -76,6 +76,8 @@ limitations under the License. left: 60px; margin-right: 0; // Counteract the E2EIcon class margin-left: 3px; // Counteract the E2EIcon class + width: 12px; + height: 12px; } .mx_MessageComposer_noperm_error { diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 8d36f02d02..53e10fa750 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -26,6 +26,7 @@ import Stickerpicker from './Stickerpicker'; import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks'; import ContentMessages from '../../../ContentMessages'; import E2EIcon from './E2EIcon'; +import SettingsStore from "../../../settings/SettingsStore"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -168,7 +169,6 @@ export default class MessageComposer extends React.Component { constructor(props) { super(props); this.onInputStateChanged = this.onInputStateChanged.bind(this); - this.onEvent = this.onEvent.bind(this); this._onRoomStateEvents = this._onRoomStateEvents.bind(this); this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this); this._onTombstoneClick = this._onTombstoneClick.bind(this); @@ -182,11 +182,6 @@ export default class MessageComposer extends React.Component { } componentDidMount() { - // N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler - // for 'event' fires *after* 'RoomEvent', and our room won't have yet been - // marked as encrypted. - // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something. - MatrixClientPeg.get().on("event", this.onEvent); MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._waitForOwnMember(); @@ -210,7 +205,6 @@ export default class MessageComposer extends React.Component { componentWillUnmount() { if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener("event", this.onEvent); MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents); } if (this._roomStoreToken) { @@ -218,13 +212,6 @@ export default class MessageComposer extends React.Component { } } - onEvent(event) { - if (event.getType() !== 'm.room.encryption') return; - if (event.getRoomId() !== this.props.room.roomId) return; - // TODO: put (encryption state??) in state - this.forceUpdate(); - } - _onRoomStateEvents(ev, state) { if (ev.getRoomId() !== this.props.room.roomId) return; @@ -282,18 +269,33 @@ export default class MessageComposer extends React.Component { } renderPlaceholderText() { - const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); - if (this.state.isQuoting) { - if (roomIsEncrypted) { - return _t('Send an encrypted reply…'); + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (this.state.isQuoting) { + if (this.props.e2eStatus) { + return _t('Send an encrypted reply…'); + } else { + return _t('Send a reply…'); + } } else { - return _t('Send a reply (unencrypted)…'); + if (this.props.e2eStatus) { + return _t('Send an encrypted message…'); + } else { + return _t('Send a message…'); + } } } else { - if (roomIsEncrypted) { - return _t('Send an encrypted message…'); + if (this.state.isQuoting) { + if (this.props.e2eStatus) { + return _t('Send an encrypted reply…'); + } else { + return _t('Send a reply (unencrypted)…'); + } } else { - return _t('Send a message (unencrypted)…'); + if (this.props.e2eStatus) { + return _t('Send an encrypted message…'); + } else { + return _t('Send a message (unencrypted)…'); + } } } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fb2898ae3d..20707a4f29 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -962,8 +962,10 @@ "Hangup": "Hangup", "Upload file": "Upload file", "Send an encrypted reply…": "Send an encrypted reply…", - "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", + "Send a reply…": "Send a reply…", "Send an encrypted message…": "Send an encrypted message…", + "Send a message…": "Send a message…", + "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", "Send a message (unencrypted)…": "Send a message (unencrypted)…", "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.", From e9dccd1460935cd8e68fe8f9595223cc7ebac4ba Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 23 Jan 2020 14:44:57 +0000 Subject: [PATCH 32/65] moving icons exactly one pixel to the right and one pixel down --- res/css/views/rooms/_RoomHeader.scss | 4 ++-- res/css/views/rooms/_RoomTile.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a235a47fdd..6bfcd437c1 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -21,8 +21,8 @@ limitations under the License. .mx_E2EIcon { margin: 0; position: absolute; - bottom: 0; - right: -1px; + bottom: -1px; + right: -2px; height: 10px; width: 10px } diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index a36d781669..376f4370e3 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -105,8 +105,8 @@ limitations under the License. width: 10px; display: block; position: absolute; - bottom: -0px; - right: -1px; + bottom: -1px; + right: -2px; z-index: 1; margin: 0; } From b5f22001b13292768ede9a20a275892e904f1433 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 17:22:39 -0700 Subject: [PATCH 33/65] Fix copy --- src/components/views/dialogs/InviteDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index a96028e71b..54a067d716 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -985,7 +985,7 @@ export default class InviteDialog extends React.PureComponent { title = _t("Direct Messages"); helpText = _t( - "If you can't find someone, ask them for their username, or share your " + + "If you can't find someone, ask them for their username, share your " + "username (%(userId)s) or profile link.", {userId}, {a: (sub) => {sub}}, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..e59805ccd7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1472,7 +1472,7 @@ "Recently Direct Messaged": "Recently Direct Messaged", "Show more": "Show more", "Direct Messages": "Direct Messages", - "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", + "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.", "Go": "Go", "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", From 442ab9f30170743c26523ca867ac9d2821a979e4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 17:27:37 -0700 Subject: [PATCH 34/65] Clear the filter when a suggestion is accepted/added --- src/components/views/dialogs/InviteDialog.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 54a067d716..42319b1842 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -713,11 +713,16 @@ export default class InviteDialog extends React.PureComponent { }; _toggleMember = (member: Member) => { + let filterText = this.state.filterText; const targets = this.state.targets.map(t => t); // cheap clone for mutation const idx = targets.indexOf(member); - if (idx >= 0) targets.splice(idx, 1); - else targets.push(member); - this.setState({targets}); + if (idx >= 0) { + targets.splice(idx, 1); + } else { + targets.push(member); + filterText = ""; // clear the filter when the user accepts a suggestion + } + this.setState({targets, filterText}); }; _removeMember = (member: Member) => { @@ -917,7 +922,7 @@ export default class InviteDialog extends React.PureComponent { key={"input"} rows={1} onChange={this._updateFilter} - defaultValue={this.state.filterText} + value={this.state.filterText} ref={this._editorRef} onPaste={this._onPaste} /> From 32f9a4e6231cefbdb9790afcfcd10d2ab5f56ea9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 17:35:36 -0700 Subject: [PATCH 35/65] Add some debugging around the recently DM'd users The suggestions are relatively stable, but the recents have some issues. Adding logging to the suggestions would also destroy the console log with thousands of messages whereas recents aren't too bad. --- src/components/views/dialogs/InviteDialog.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 42319b1842..de11dbf9fa 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -338,19 +338,31 @@ export default class InviteDialog extends React.PureComponent { const recents = []; for (const userId in rooms) { // Filter out user IDs that are already in the room / should be excluded - if (excludedTargetIds.includes(userId)) continue; + if (excludedTargetIds.includes(userId)) { + console.warn(`[Invite:Recents] Excluding ${userId} from recents`); + continue; + } const room = rooms[userId]; const member = room.getMember(userId); - if (!member) continue; // just skip people who don't have memberships for some reason + if (!member) { + // just skip people who don't have memberships for some reason + console.warn(`[Invite:Recents] ${userId} is missing a member object in their own DM (${room.roomId})`); + continue; + } const lastEventTs = room.timeline && room.timeline.length ? room.timeline[room.timeline.length - 1].getTs() : 0; - if (!lastEventTs) continue; // something weird is going on with this room + if (!lastEventTs) { + // something weird is going on with this room + console.warn(`[Invite:Recents] ${userId} (${room.roomId}) has a weird last timestamp: ${lastEventTs}`); + continue; + } recents.push({userId, user: member, lastActive: lastEventTs}); } + if (!recents) console.warn("[Invite:Recents] No recents to suggest!"); // Sort the recents by last active to save us time later recents.sort((a, b) => b.lastActive - a.lastActive); From 1ec746c01411dafa8b2554d8463e2ec5bcd204eb Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jan 2020 19:53:33 -0700 Subject: [PATCH 36/65] Add verification with QR codes --- src/MatrixClientPeg.js | 2 +- .../elements/crypto/VerificationQRCode.js | 59 +++++++++++++++++++ .../views/right_panel/VerificationPanel.js | 22 ++++++- 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 src/components/views/elements/crypto/VerificationQRCode.js diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index dbc570c872..450bec8e77 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -217,7 +217,7 @@ class _MatrixClientPeg { timelineSupport: true, forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false), fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'), - verificationMethods: [verificationMethods.SAS], + verificationMethods: [verificationMethods.SAS, verificationMethods.QR_CODE_SHOW], unstableClientRelationAggregation: true, identityServer: new IdentityAuthClient(), }; diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js new file mode 100644 index 0000000000..c49dd3faf5 --- /dev/null +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -0,0 +1,59 @@ +/* +Copyright 2020 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 PropTypes from "prop-types"; +import {replaceableComponent} from "../../../../utils/replaceableComponent"; +import * as qs from "qs"; +import QRCode from "qrcode-react"; + +@replaceableComponent("views.elements.crypto.VerificationQRCode") +export default class VerificationQRCode extends React.PureComponent { + static propTypes = { + // Common for all kinds of QR codes + keys: PropTypes.array.isRequired, // array of [Key ID, Key] pairs + action: PropTypes.string.isRequired, + keyholderUserId: PropTypes.string.isRequired, + + // User verification use case only + secret: PropTypes.string, + otherUserKey: PropTypes.string, + verificationKey: PropTypes.string, + verificationAlgorithms: PropTypes.array, + requestEventId: PropTypes.string, + }; + + static defaultProps = { + action: "verify", + }; + + render() { + const query = { + request: this.props.requestEventId, + action: this.props.action, + verification_algorithms: this.props.verificationAlgorithms, + verification_key: this.props.verificationKey, + other_user_key: this.props.otherUserKey, + }; + for (const key of this.props.keys) { + query[`key_${key[0]}`] = key[1]; + } + + const uri = `https://matrix.to/#/${this.props.keyholderUserId}?${qs.stringify(query)}`; + + return + } +} diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 0d28e1568f..a2038087fc 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -17,6 +17,9 @@ limitations under the License. import React from 'react'; import * as sdk from '../../../index'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; +import VerificationQRCode from "../elements/crypto/VerificationQRCode"; +import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import {MatrixClientPeg} from "../../../MatrixClientPeg"; export default class VerificationPanel extends React.PureComponent { constructor(props) { @@ -36,15 +39,30 @@ export default class VerificationPanel extends React.PureComponent { renderStatus() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const Spinner = sdk.getComponent('elements.Spinner'); - const {request} = this.props; + const {request: req} = this.props; + const request: VerificationRequest = req; if (request.requested) { return (

Waiting for {request.otherUserId} to accept ...

); } else if (request.ready) { + const qrCodeKeys = [ + [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], + [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningKey("master")], + ]; + // TODO: Await a bunch of this + const otherCrossSigning = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); + const otherUserKey = otherCrossSigning ? otherCrossSigning.getCrossSigningKey("master") : null; + const qrCode = ; const verifyButton = Verify by emoji ; - return (

{request.otherUserId} is ready, start {verifyButton}

); + return (

{request.otherUserId} is ready, start {verifyButton} or have them scan: {qrCode}

); } else if (request.started) { if (this.state.sasWaitingForOtherParty) { return

Waiting for {request.otherUserId} to confirm ...

; From 586a240e41721651ae661abb216de865504672bd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 23:10:35 -0700 Subject: [PATCH 37/65] Don't use private keys for QR code --- src/components/views/right_panel/VerificationPanel.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index a2038087fc..34f7538b1c 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -45,17 +45,15 @@ export default class VerificationPanel extends React.PureComponent { if (request.requested) { return (

Waiting for {request.otherUserId} to accept ...

); } else if (request.ready) { + const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; const qrCodeKeys = [ [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningKey("master")], + [keyId, MatrixClientPeg.get().getCrossSigningId()], ]; - // TODO: Await a bunch of this - const otherCrossSigning = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); - const otherUserKey = otherCrossSigning ? otherCrossSigning.getCrossSigningKey("master") : null; const qrCode = ; From b682e7d908a8e6b3c84a0e77977604bd85709bf6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:05:32 -0700 Subject: [PATCH 38/65] Generate a QR code for apps to scan --- .../elements/crypto/VerificationQRCode.js | 8 ++--- .../views/right_panel/VerificationPanel.js | 32 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index c49dd3faf5..fa4031da9f 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -24,15 +24,13 @@ import QRCode from "qrcode-react"; export default class VerificationQRCode extends React.PureComponent { static propTypes = { // Common for all kinds of QR codes - keys: PropTypes.array.isRequired, // array of [Key ID, Key] pairs + keys: PropTypes.array.isRequired, // array of [Key ID, Base64 Key] pairs action: PropTypes.string.isRequired, keyholderUserId: PropTypes.string.isRequired, // User verification use case only secret: PropTypes.string, - otherUserKey: PropTypes.string, - verificationKey: PropTypes.string, - verificationAlgorithms: PropTypes.array, + otherUserKey: PropTypes.string, // Base64 key being verified requestEventId: PropTypes.string, }; @@ -44,8 +42,6 @@ export default class VerificationQRCode extends React.PureComponent { const query = { request: this.props.requestEventId, action: this.props.action, - verification_algorithms: this.props.verificationAlgorithms, - verification_key: this.props.verificationKey, other_user_key: this.props.otherUserKey, }; for (const key of this.props.keys) { diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 34f7538b1c..d0c3d2df10 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -45,22 +45,28 @@ export default class VerificationPanel extends React.PureComponent { if (request.requested) { return (

Waiting for {request.otherUserId} to accept ...

); } else if (request.ready) { - const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; - const qrCodeKeys = [ - [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [keyId, MatrixClientPeg.get().getCrossSigningId()], - ]; - const qrCode = ; const verifyButton = Verify by emoji ; - return (

{request.otherUserId} is ready, start {verifyButton} or have them scan: {qrCode}

); + + if (request.requestEvent && request.requestEvent.getId()) { + const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; + const qrCodeKeys = [ + [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], + [keyId, MatrixClientPeg.get().getCrossSigningId()], + ]; + const crossSigningInfo = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); + const qrCode = ; + return (

{request.otherUserId} is ready, start {verifyButton} or have them scan: {qrCode}

); + } + + return (

{request.otherUserId} is ready, start {verifyButton}

); } else if (request.started) { if (this.state.sasWaitingForOtherParty) { return

Waiting for {request.otherUserId} to confirm ...

; From ebcc4d573bacac10c991db7df788cc28b1b73a8a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:10:31 -0700 Subject: [PATCH 39/65] Add trace logging to figure out which component is causing weird events For https://github.com/vector-im/riot-web/issues/11120 --- src/components/views/rooms/EventTile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 634b77c9e1..155ed19821 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -578,6 +578,7 @@ export default createReactClass({ console.error("EventTile attempted to get relations for an event without an ID"); // Use event's special `toJSON` method to log key data. console.log(JSON.stringify(this.props.mxEvent, null, 4)); + console.trace("Stacktrace for https://github.com/vector-im/riot-web/issues/11120"); } return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); }, From 0ce9da5d365d4b2e1490206233c8ecfa6dc01eed Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:12:24 -0700 Subject: [PATCH 40/65] Appease the linter --- src/components/views/elements/crypto/VerificationQRCode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index fa4031da9f..9026e51096 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -50,6 +50,6 @@ export default class VerificationQRCode extends React.PureComponent { const uri = `https://matrix.to/#/${this.props.keyholderUserId}?${qs.stringify(query)}`; - return + return ; } } From b7e680ba5154935f3f8a394ceaeeb0bda307c29b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:14:27 -0700 Subject: [PATCH 41/65] Fix key ID --- src/components/views/right_panel/VerificationPanel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index d0c3d2df10..a29546c8f7 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -50,10 +50,9 @@ export default class VerificationPanel extends React.PureComponent { ; if (request.requestEvent && request.requestEvent.getId()) { - const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; const qrCodeKeys = [ [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [keyId, MatrixClientPeg.get().getCrossSigningId()], + [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningId()], ]; const crossSigningInfo = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); const qrCode = Date: Thu, 23 Jan 2020 20:19:17 -0700 Subject: [PATCH 42/65] Actually add the secret to the QR code url --- src/components/views/elements/crypto/VerificationQRCode.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index 9026e51096..1cb5647317 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -43,6 +43,7 @@ export default class VerificationQRCode extends React.PureComponent { request: this.props.requestEventId, action: this.props.action, other_user_key: this.props.otherUserKey, + secret: this.props.secret, }; for (const key of this.props.keys) { query[`key_${key[0]}`] = key[1]; From b72ab57e1b33d443b40a56e73645044233a736f5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 24 Jan 2020 10:13:03 +0000 Subject: [PATCH 43/65] add to --- src/components/views/rooms/E2EIcon.js | 40 +++++++++++++++++++++------ 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 7ac3b5af2d..df5fe204d4 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -15,13 +15,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, {useState} from "react"; import PropTypes from "prop-types"; import classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; -import AccessibleButton from '../elements/AccessibleButton'; import {useFeatureEnabled} from "../../../hooks/useSettings"; +import AccessibleButton from "../elements/AccessibleButton"; +import Tooltip from "../elements/Tooltip"; export const E2E_STATE = { VERIFIED: "verified", @@ -51,7 +52,9 @@ const legacyRoomTitles = { }; const E2EIcon = ({isUser, status, className, size, onClick}) => { - const e2eIconClasses = classNames({ + const [hover, setHover] = useState(false); + + const classes = classNames({ mx_E2EIcon: true, mx_E2EIcon_warning: status === E2E_STATE.WARNING, mx_E2EIcon_normal: status === E2E_STATE.NORMAL, @@ -70,17 +73,36 @@ const E2EIcon = ({isUser, status, className, size, onClick}) => { e2eTitle = legacyRoomTitles[status]; } - let style = null; + let style; if (size) { style = {width: `${size}px`, height: `${size}px`}; } - const icon = (
); - if (onClick) { - return ({ icon }); - } else { - return icon; + const onMouseOver = () => setHover(true); + const onMouseOut = () => setHover(false); + + let tip; + if (hover) { + tip = ; } + + if (onClick) { + return ( + + { tip } + + ); + } + + return
+ { tip } +
; }; E2EIcon.propTypes = { From 2483337e8993463f87be946b078ffa08a6f514ca Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 11:57:57 +0100 Subject: [PATCH 44/65] don't use removed .event property anymore on verification request --- .../messages/MKeyVerificationConclusion.js | 2 +- .../views/messages/MKeyVerificationRequest.js | 10 +++--- .../views/toasts/VerificationRequestToast.js | 33 +++++++++---------- src/utils/KeyVerificationStateObserver.js | 7 ++-- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationConclusion.js b/src/components/views/messages/MKeyVerificationConclusion.js index f51b97786b..a17dcd8ab0 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.js +++ b/src/components/views/messages/MKeyVerificationConclusion.js @@ -93,7 +93,7 @@ export default class MKeyVerificationConclusion extends React.Component { } if (title) { - const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent); + const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId()); const classes = classNames("mx_EventTile_bubble", "mx_KeyVerification", "mx_KeyVerification_icon", { mx_KeyVerification_icon_verified: request.done, }); diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index ae793556d8..8caff322aa 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -85,7 +85,7 @@ export default class MKeyVerificationRequest extends React.Component { if (userId === myUserId) { return _t("You accepted"); } else { - return _t("%(name)s accepted", {name: getNameForEventRoom(userId, this.props.mxEvent)}); + return _t("%(name)s accepted", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())}); } } @@ -95,7 +95,7 @@ export default class MKeyVerificationRequest extends React.Component { if (userId === myUserId) { return _t("You cancelled"); } else { - return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent)}); + return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())}); } } @@ -129,9 +129,9 @@ export default class MKeyVerificationRequest extends React.Component { if (!request.initiatedByMe) { title = (
{ - _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent)})}
); + _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId())})}
); subtitle = (
{ - userLabelForEventRoom(request.requestingUserId, mxEvent)}
); + userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}
); if (request.requested && !request.observeOnly) { stateNode = (
@@ -142,7 +142,7 @@ export default class MKeyVerificationRequest extends React.Component { title = (
{ _t("You sent a verification request")}
); subtitle = (
{ - userLabelForEventRoom(request.receivingUserId, mxEvent)}
); + userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId())}
); } if (title) { diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 479a3e3f93..274085c0fd 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -65,22 +65,21 @@ export default class VerificationRequestToast extends React.PureComponent { accept = async () => { ToastStore.sharedInstance().dismissToast(this.props.toastKey); const {request} = this.props; - const {event} = request; // no room id for to_device requests - if (event.getRoomId()) { - dis.dispatch({ - action: 'view_room', - room_id: event.getRoomId(), - should_peek: false, - }); - } try { - await request.accept(); - dis.dispatch({ - action: "set_right_panel_phase", - phase: RIGHT_PANEL_PHASES.EncryptionPanel, - refireParams: {verificationRequest: request}, - }); + if (request.channel.roomId) { + dis.dispatch({ + action: 'view_room', + room_id: request.channel.roomId, + should_peek: false, + }); + await request.accept(); + dis.dispatch({ + action: "set_right_panel_phase", + phase: RIGHT_PANEL_PHASES.EncryptionPanel, + refireParams: {verificationRequest: request}, + }); + } } catch (err) { console.error(err.message); } @@ -89,13 +88,13 @@ export default class VerificationRequestToast extends React.PureComponent { render() { const FormButton = sdk.getComponent("elements.FormButton"); const {request} = this.props; - const {event} = request; const userId = request.otherUserId; - let nameLabel = event.getRoomId() ? userLabelForEventRoom(userId, event) : userId; + const roomId = request.channel.roomId; + let nameLabel = roomId ? userLabelForEventRoom(userId, roomId) : userId; // for legacy to_device verification requests if (nameLabel === userId) { const client = MatrixClientPeg.get(); - const user = client.getUser(event.getSender()); + const user = client.getUser(userId); if (user && user.displayName) { nameLabel = _t("%(name)s (%(userId)s)", {name: user.displayName, userId}); } diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.js index 1a35319186..7da532109c 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.js @@ -17,16 +17,15 @@ limitations under the License. import {MatrixClientPeg} from '../MatrixClientPeg'; import { _t } from '../languageHandler'; -export function getNameForEventRoom(userId, mxEvent) { - const roomId = mxEvent.getRoomId(); +export function getNameForEventRoom(userId, roomId) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); const member = room.getMember(userId); return member ? member.name : userId; } -export function userLabelForEventRoom(userId, mxEvent) { - const name = getNameForEventRoom(userId, mxEvent); +export function userLabelForEventRoom(userId, roomId) { + const name = getNameForEventRoom(userId, roomId); if (name !== userId) { return _t("%(name)s (%(userId)s)", {name, userId}); } else { From 512bd4b0b2afb62d50cf6bb179b6bc8b619887c2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 11:59:17 +0100 Subject: [PATCH 45/65] show to_device verification in dialog, not right panel this also makes it work again, as the VerificationPanel doens't know how to deal with requests that are already in PHASE_STARTED, which was breaking verifying your own devices. --- src/components/views/toasts/VerificationRequestToast.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 274085c0fd..f912984486 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -23,6 +23,7 @@ import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; import dis from "../../../dispatcher"; import ToastStore from "../../../stores/ToastStore"; +import Modal from "../../../Modal"; export default class VerificationRequestToast extends React.PureComponent { constructor(props) { @@ -79,6 +80,12 @@ export default class VerificationRequestToast extends React.PureComponent { phase: RIGHT_PANEL_PHASES.EncryptionPanel, refireParams: {verificationRequest: request}, }); + } else if (request.channel.deviceId && request.verifier) { + // show to_device verifications in dialog still + const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); + Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { + verifier: request.verifier, + }, null, /* priority = */ false, /* static = */ true); } } catch (err) { console.error(err.message); From 395c82b1e5042e915cf3d1184ace4d23ea2ad240 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 24 Jan 2020 11:04:40 +0000 Subject: [PATCH 46/65] Update src/components/structures/RoomView.js Co-Authored-By: J. Ryan Stinnett --- src/components/structures/RoomView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index f10c98dd98..4c88f533f0 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -796,7 +796,7 @@ export default createReactClass({ return; } - // Duplication betwen here and _updateE2eStatus in RoomTile + // Duplication between here and _updateE2eStatus in RoomTile /* At this point, the user has encryption on and cross-signing on */ const e2eMembers = await room.getEncryptionTargetMembers(); const verified = []; From ecfecfe559e2eda9c8cc326913a5103d19fb9ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 12:07:03 +0100 Subject: [PATCH 47/65] EventIndex: Fix a small style issue. --- src/indexing/EventIndex.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 93c640cf8e..2b432ab1a1 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -172,7 +172,7 @@ export default class EventIndex { } const jsonEvent = ev.toJSON(); - const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; const profile = { displayname: ev.sender.rawDisplayName, @@ -308,7 +308,7 @@ export default class EventIndex { // consume. const events = filteredEvents.map((ev) => { const jsonEvent = ev.toJSON(); - const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; let profile = {}; if (e.sender in profiles) profile = profiles[e.sender]; From 6cce65a2a59544ef21b77744856ad2875abfb1ad Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 12:08:47 +0100 Subject: [PATCH 48/65] fix lint --- src/components/views/messages/MKeyVerificationRequest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index 8caff322aa..49f871d16e 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -128,8 +128,9 @@ export default class MKeyVerificationRequest extends React.Component { } if (!request.initiatedByMe) { + const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId()); title = (
{ - _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId())})}
); + _t("%(name)s wants to verify", {name})}
); subtitle = (
{ userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}
); if (request.requested && !request.observeOnly) { From a6fcbcacf68d0d01ead7edaad9ad33dbbd1dfbcb Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 24 Jan 2020 12:04:08 +0000 Subject: [PATCH 49/65] Seperates out the padlock icon, and adds a tooltip Fixes https://github.com/vector-im/riot-web/issues/11840 --- res/css/_components.scss | 1 + res/css/views/rooms/_InviteOnlyIcon.scss | 38 +++++++++++++++ res/css/views/rooms/_RoomHeader.scss | 21 -------- res/css/views/rooms/_RoomTile.scss | 25 +--------- src/components/views/rooms/InviteOnlyIcon.js | 51 ++++++++++++++++++++ src/components/views/rooms/RoomHeader.js | 12 +++-- src/components/views/rooms/RoomTile.js | 6 ++- src/i18n/strings/en_EN.json | 1 + 8 files changed, 103 insertions(+), 52 deletions(-) create mode 100644 res/css/views/rooms/_InviteOnlyIcon.scss create mode 100644 src/components/views/rooms/InviteOnlyIcon.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 60f749de9c..07e92bdc7b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -148,6 +148,7 @@ @import "./views/rooms/_AuxPanel.scss"; @import "./views/rooms/_BasicMessageComposer.scss"; @import "./views/rooms/_E2EIcon.scss"; +@import "./views/rooms/_InviteOnlyIcon.scss"; @import "./views/rooms/_EditMessageComposer.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; diff --git a/res/css/views/rooms/_InviteOnlyIcon.scss b/res/css/views/rooms/_InviteOnlyIcon.scss new file mode 100644 index 0000000000..e70586bb73 --- /dev/null +++ b/res/css/views/rooms/_InviteOnlyIcon.scss @@ -0,0 +1,38 @@ +/* +Copyright 2020 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. +*/ + +.mx_InviteOnlyIcon { + width: 12px; + height: 12px; + position: relative; + display: block !important; + // Align the padlock with unencrypted room names + margin-left: 6px; + + &::before { + background-color: $roomtile-name-color; + mask-image: url('$(res)/img/feather-customised/lock-solid.svg'); + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } +} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 6bfcd437c1..0ac2e99b97 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -269,24 +269,3 @@ limitations under the License. .mx_RoomHeader_pinsIndicatorUnread { background-color: $pinned-unread-color; } - -.mx_RoomHeader_PrivateIcon.mx_RoomHeader_isPrivate { - width: 12px; - height: 12px; - position: relative; - display: block !important; - - &::before { - background-color: $roomtile-name-color; - mask-image: url('$(res)/img/feather-customised/lock-solid.svg'); - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } -} diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 376f4370e3..a24fdf2629 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -215,30 +215,7 @@ limitations under the License. flex: 1; } -.mx_RoomTile.mx_RoomTile.mx_RoomTile_isPrivate .mx_RoomTile_name { +.mx_InviteOnlyIcon + .mx_RoomTile_nameContainer .mx_RoomTile_name { // Scoot the padding in a bit from 6px to make it look better padding-left: 3px; } - -.mx_RoomTile.mx_RoomTile_isPrivate .mx_RoomTile_PrivateIcon { - width: 12px; - height: 12px; - position: relative; - display: block !important; - // Align the padlock with unencrypted room names - margin-left: 6px; - - &::before { - background-color: $roomtile-name-color; - mask-image: url('$(res)/img/feather-customised/lock-solid.svg'); - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } -} diff --git a/src/components/views/rooms/InviteOnlyIcon.js b/src/components/views/rooms/InviteOnlyIcon.js new file mode 100644 index 0000000000..5afaa7f0f2 --- /dev/null +++ b/src/components/views/rooms/InviteOnlyIcon.js @@ -0,0 +1,51 @@ +/* +Copyright 2020 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 { _t } from '../../../languageHandler'; +import * as sdk from '../../../index'; + +export default class InviteOnlyIcon extends React.Component { + constructor() { + super(); + + this.state = { + hover: false, + }; + } + + onHoverStart = () => { + this.setState({hover: true}); + }; + + onHoverEnd = () => { + this.setState({hover: false}); + }; + + render() { + const Tooltip = sdk.getComponent("elements.Tooltip"); + let tooltip; + if (this.state.hover) { + tooltip = ; + } + return (
+ { tooltip } +
); + } +} diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 15f0daa200..8a427e1c06 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -32,6 +32,7 @@ import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; import E2EIcon from './E2EIcon'; +import InviteOnlyIcon from './InviteOnlyIcon'; export default createReactClass({ displayName: 'RoomHeader', @@ -162,11 +163,12 @@ export default createReactClass({ const joinRules = this.props.room && this.props.room.currentState.getStateEvents("m.room.join_rules", ""); const joinRule = joinRules && joinRules.getContent().join_rule; - const joinRuleClass = classNames("mx_RoomHeader_PrivateIcon", - {"mx_RoomHeader_isPrivate": joinRule === "invite"}); - const privateIcon = SettingsStore.isFeatureEnabled("feature_cross_signing") ? -
: - undefined; + let privateIcon; + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (joinRule == "invite") { + privateIcon = ; + } + } if (this.props.onCancelClick) { cancelButton = ; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 0b50d85ff6..4e1a4c7b23 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -34,6 +34,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import E2EIcon from './E2EIcon'; +import InviteOnlyIcon from './InviteOnlyIcon'; // eslint-disable-next-line camelcase import rate_limited_func from '../../../ratelimitedfunc'; @@ -411,7 +412,6 @@ export default createReactClass({ 'mx_RoomTile_noBadges': !badges, 'mx_RoomTile_transparent': this.props.transparent, 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed, - 'mx_RoomTile_isPrivate': this.state.joinRule == "invite" && !dmUserId, }); const avatarClasses = classNames({ @@ -523,7 +523,9 @@ export default createReactClass({ let privateIcon = null; if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { - privateIcon =
; + if (this.state.joinRule == "invite" && !dmUserId) { + privateIcon = ; + } } let e2eIcon = null; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..456d48a94c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -908,6 +908,7 @@ "Unencrypted": "Unencrypted", "Encrypted by a deleted device": "Encrypted by a deleted device", "Please select the destination room for this message": "Please select the destination room for this message", + "Invite only": "Invite only", "Scroll to bottom of page": "Scroll to bottom of page", "Close preview": "Close preview", "device id: ": "device id: ", From 688f7029d292dcbcaace4eefeec28837e2978041 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 12:28:03 +0000 Subject: [PATCH 50/65] Split AsyncWrapper out from Modal So we can use it outside of modals & dialogs --- src/AsyncWrapper.js | 93 +++++++++++++++++++++++++++++++++++++++++++++ src/Modal.js | 77 +------------------------------------ 2 files changed, 95 insertions(+), 75 deletions(-) create mode 100644 src/AsyncWrapper.js diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js new file mode 100644 index 0000000000..63b856a882 --- /dev/null +++ b/src/AsyncWrapper.js @@ -0,0 +1,93 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 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 createReactClass from 'create-react-class'; +import Analytics from './Analytics'; +import * as sdk from './index'; +import PropTypes from 'prop-types'; +import { _t } from './languageHandler'; + +/** + * Wrap an asynchronous loader function with a react component which shows a + * spinner until the real component loads. + */ +export default createReactClass({ + propTypes: { + /** A promise which resolves with the real component + */ + prom: PropTypes.object.isRequired, + }, + + getInitialState: function() { + return { + component: null, + error: null, + }; + }, + + componentWillMount: function() { + this._unmounted = false; + // XXX: temporary logging to try to diagnose + // https://github.com/vector-im/riot-web/issues/3148 + console.log('Starting load of AsyncWrapper for modal'); + this.props.prom.then((result) => { + if (this._unmounted) { + return; + } + // Take the 'default' member if it's there, then we support + // passing in just an import()ed module, since ES6 async import + // always returns a module *namespace*. + const component = result.default ? result.default : result; + this.setState({component}); + }).catch((e) => { + console.warn('AsyncWrapper promise failed', e); + this.setState({error: e}); + }); + }, + + componentWillUnmount: function() { + this._unmounted = true; + }, + + _onWrapperCancelClick: function() { + this.props.onFinished(false); + }, + + render: function() { + if (this.state.component) { + const Component = this.state.component; + return ; + } else if (this.state.error) { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return + {_t("Unable to load! Check your network connectivity and try again.")} + + ; + } else { + // show a spinner until the component is loaded. + const Spinner = sdk.getComponent("elements.Spinner"); + return ; + } + }, +}); + diff --git a/src/Modal.js b/src/Modal.js index 29d3af2e74..b6215b2b5a 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -17,87 +17,14 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import Analytics from './Analytics'; -import * as sdk from './index'; import dis from './dispatcher'; -import { _t } from './languageHandler'; -import {defer} from "./utils/promise"; +import {defer} from './utils/promise'; +import AsyncWrapper from './AsyncWrapper'; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; -/** - * Wrap an asynchronous loader function with a react component which shows a - * spinner until the real component loads. - */ -const AsyncWrapper = createReactClass({ - propTypes: { - /** A promise which resolves with the real component - */ - prom: PropTypes.object.isRequired, - }, - - getInitialState: function() { - return { - component: null, - error: null, - }; - }, - - componentWillMount: function() { - this._unmounted = false; - // XXX: temporary logging to try to diagnose - // https://github.com/vector-im/riot-web/issues/3148 - console.log('Starting load of AsyncWrapper for modal'); - this.props.prom.then((result) => { - if (this._unmounted) { - return; - } - // Take the 'default' member if it's there, then we support - // passing in just an import()ed module, since ES6 async import - // always returns a module *namespace*. - const component = result.default ? result.default : result; - this.setState({component}); - }).catch((e) => { - console.warn('AsyncWrapper promise failed', e); - this.setState({error: e}); - }); - }, - - componentWillUnmount: function() { - this._unmounted = true; - }, - - _onWrapperCancelClick: function() { - this.props.onFinished(false); - }, - - render: function() { - if (this.state.component) { - const Component = this.state.component; - return ; - } else if (this.state.error) { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return - {_t("Unable to load! Check your network connectivity and try again.")} - - ; - } else { - // show a spinner until the component is loaded. - const Spinner = sdk.getComponent("elements.Spinner"); - return ; - } - }, -}); - class ModalManager { constructor() { this._counter = 0; From dbf1c9a02a4550fba98930f51d6058e4566ada47 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 12:34:03 +0000 Subject: [PATCH 51/65] Apparently i18n... --- src/i18n/strings/en_EN.json | 6 +- src/i18n/strings/en_EN.json.orig | 2040 ++++++++++++++++++++++++++++++ 2 files changed, 2043 insertions(+), 3 deletions(-) create mode 100644 src/i18n/strings/en_EN.json.orig diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..610f1546e7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -21,6 +21,9 @@ "Analytics": "Analytics", "The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.", + "Error": "Error", + "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", + "Dismiss": "Dismiss", "Call Failed": "Call Failed", "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", "Review Devices": "Review Devices", @@ -105,9 +108,6 @@ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", "Trust": "Trust", - "Error": "Error", - "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", - "Dismiss": "Dismiss", "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", "Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again", "Unable to enable Notifications": "Unable to enable Notifications", diff --git a/src/i18n/strings/en_EN.json.orig b/src/i18n/strings/en_EN.json.orig new file mode 100644 index 0000000000..f0eab6b12d --- /dev/null +++ b/src/i18n/strings/en_EN.json.orig @@ -0,0 +1,2040 @@ +{ + "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", + "Add Email Address": "Add Email Address", + "Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email", + "Add Phone Number": "Add Phone Number", + "The platform you're on": "The platform you're on", + "The version of Riot.im": "The version of Riot.im", + "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)", + "Your language of choice": "Your language of choice", + "Which officially provided instance you are using, if any": "Which officially provided instance you are using, if any", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Whether or not you're using the Richtext mode of the Rich Text Editor", + "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)", + "Your homeserver's URL": "Your homeserver's URL", + "Your identity server's URL": "Your identity server's URL", + "e.g. %(exampleValue)s": "e.g. %(exampleValue)s", + "Every page you use in the app": "Every page you use in the app", + "e.g. ": "e.g. ", + "Your User Agent": "Your User Agent", + "Your device resolution": "Your device resolution", + "Analytics": "Analytics", + "The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.", + "Call Failed": "Call Failed", + "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", + "Review Devices": "Review Devices", + "Call Anyway": "Call Anyway", + "Answer Anyway": "Answer Anyway", + "Call": "Call", + "Answer": "Answer", + "Call Timeout": "Call Timeout", + "The remote side failed to pick up": "The remote side failed to pick up", + "Call failed due to misconfigured server": "Call failed due to misconfigured server", + "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.", + "Try using turn.matrix.org": "Try using turn.matrix.org", + "OK": "OK", + "Unable to capture screen": "Unable to capture screen", + "Existing Call": "Existing Call", + "You are already in a call.": "You are already in a call.", + "VoIP is unsupported": "VoIP is unsupported", + "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", + "You cannot place a call with yourself.": "You cannot place a call with yourself.", + "Could not connect to the integration server": "Could not connect to the integration server", + "A conference call could not be started because the integrations server is not available": "A conference call could not be started because the integrations server is not available", + "Call in Progress": "Call in Progress", + "A call is currently being placed!": "A call is currently being placed!", + "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", + "Replying With Files": "Replying With Files", + "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "At this time it is not possible to reply with a file. Would you like to upload this file without replying?", + "Continue": "Continue", + "The file '%(fileName)s' failed to upload.": "The file '%(fileName)s' failed to upload.", + "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads", + "Upload Failed": "Upload Failed", + "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", + "The server does not support the room version specified.": "The server does not support the room version specified.", + "Failure to create room": "Failure to create room", + "Send cross-signing keys to homeserver": "Send cross-signing keys to homeserver", + "Send anyway": "Send anyway", + "Send": "Send", + "Sun": "Sun", + "Mon": "Mon", + "Tue": "Tue", + "Wed": "Wed", + "Thu": "Thu", + "Fri": "Fri", + "Sat": "Sat", + "Jan": "Jan", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "May", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Aug", + "Sep": "Sep", + "Oct": "Oct", + "Nov": "Nov", + "Dec": "Dec", + "PM": "PM", + "AM": "AM", + "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)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 %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", + "New Session": "New Session", + "Who would you like to add to this community?": "Who would you like to add to this community?", + "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", + "Invite new community members": "Invite new community members", + "Name or Matrix ID": "Name or Matrix ID", + "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?", + "Show these rooms to non-members on the community page and room list?": "Show these rooms to non-members on the community page and room list?", + "Add rooms to the community": "Add rooms to the community", + "Room name or alias": "Room name or alias", + "Add to community": "Add to community", + "Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:", + "Failed to invite users to community": "Failed to invite users to community", + "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", + "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", + "Unnamed Room": "Unnamed Room", + "Identity server has no terms of service": "Identity server has no terms of service", + "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", + "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", + "Trust": "Trust", + "Error": "Error", + "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", + "Dismiss": "Dismiss", + "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", + "Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again", + "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.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.", + "Registration Required": "Registration Required", + "You need to register to do this. Would you like to register now?": "You need to register to do this. Would you like to register now?", + "Register": "Register", + "Default": "Default", + "Restricted": "Restricted", + "Moderator": "Moderator", + "Admin": "Admin", + "Custom (%(level)s)": "Custom (%(level)s)", + "Start a chat": "Start a chat", + "Who would you like to communicate with?": "Who would you like to communicate with?", + "Email, name or Matrix ID": "Email, name or Matrix ID", + "Start Chat": "Start Chat", + "Invite new room members": "Invite new room members", + "Send Invites": "Send Invites", + "Failed to start chat": "Failed to start chat", + "Operation failed": "Operation failed", + "Failed to invite": "Failed to invite", + "Failed to invite users to the room:": "Failed to invite users to the room:", + "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", + "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.", + "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.", + "You are not in this room.": "You are not in this room.", + "You do not have permission to do that in this room.": "You do not have permission to do that in this room.", + "Missing room_id in request": "Missing room_id in request", + "Room %(roomId)s not visible": "Room %(roomId)s not visible", + "Missing user_id in request": "Missing user_id in request", + "Messages": "Messages", + "Actions": "Actions", + "Advanced": "Advanced", + "Other": "Other", + "Usage": "Usage", + "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message", + "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", + "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", + "/ddg is not a command": "/ddg is not a command", + "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", + "Upgrades a room to a new version": "Upgrades a room to a new version", + "You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.", + "Error upgrading room": "Error upgrading room", + "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", + "Changes your display nickname": "Changes your display nickname", + "Changes your display nickname in the current room only": "Changes your display nickname in the current room only", + "Changes the avatar of the current room": "Changes the avatar of the current room", + "Changes your avatar in this current room only": "Changes your avatar in this current room only", + "Changes your avatar in all rooms": "Changes your avatar in all rooms", + "Gets or sets the room topic": "Gets or sets the room topic", + "This room has no topic.": "This room has no topic.", + "Sets the room name": "Sets the room name", + "Invites user with given id to current room": "Invites user with given id to current room", + "Use an identity server": "Use an identity server", + "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.", + "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", + "Joins room with given alias": "Joins room with given alias", + "Leave room": "Leave room", + "Unrecognised room alias:": "Unrecognised room alias:", + "Kicks user with given id": "Kicks user with given id", + "Bans user with given id": "Bans user with given id", + "Unbans user with given ID": "Unbans user with given 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": "You are now ignoring %(userId)s", + "Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward", + "Unignored user": "Unignored user", + "You are no longer ignoring %(userId)s": "You are no longer ignoring %(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", + "Opens the Developer Tools dialog": "Opens the Developer Tools dialog", + "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": "Please supply a https:// or http:// widget URL", + "You cannot modify widgets in this room.": "You cannot modify widgets in this room.", + "Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple", + "Unknown (user, device) pair:": "Unknown (user, device) pair:", + "Device already verified!": "Device already verified!", + "WARNING: Device already verified, but keys do NOT MATCH!": "WARNING: Device already verified, but keys do NOT MATCH!", + "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!", + "Verified key": "Verified key", + "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", + "Displays action": "Displays action", + "Forces the current outbound group session in an encrypted room to be discarded": "Forces the current outbound group session in an encrypted room to be discarded", + "Sends the given message coloured as a rainbow": "Sends the given message coloured as a rainbow", + "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", + "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", + "Unrecognised command:": "Unrecognised command:", + "Reason": "Reason", + "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", + "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", + "%(senderName)s requested a VoIP conference.": "%(senderName)s requested a VoIP conference.", + "%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.", + "%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.", + "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.", + "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).", + "%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.", + "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", + "%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.", + "%(senderName)s made no change.": "%(senderName)s made no change.", + "VoIP conference started.": "VoIP conference started.", + "%(targetName)s joined the room.": "%(targetName)s joined the room.", + "VoIP conference finished.": "VoIP conference finished.", + "%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.", + "%(targetName)s left the room.": "%(targetName)s left the room.", + "%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.", + "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.", + "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.", + "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".", + "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.", + "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.", + "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgraded this room.", + "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s made the room public to whoever knows the link.", + "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s made the room invite only.", + "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s changed the join rule to %(rule)s", + "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s has allowed guests to join the room.", + "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s has prevented guests from joining the room.", + "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s changed guest access to %(rule)s", + "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.", + "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", + "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", + "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", + "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", + "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", + "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", + "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", + "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", + "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", + "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", + "Someone": "Someone", + "(not supported by this browser)": "(not supported by this browser)", + "%(senderName)s answered the call.": "%(senderName)s answered the call.", + "(could not connect media)": "(could not connect media)", + "(no answer)": "(no answer)", + "(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)", + "%(senderName)s ended the call.": "%(senderName)s ended the call.", + "%(senderName)s placed a voice call.": "%(senderName)s placed a voice call.", + "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s placed a voice call. (not supported by this browser)", + "%(senderName)s placed a video call.": "%(senderName)s placed a video call.", + "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s placed a video call. (not supported by this browser)", + "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.", + "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.", + "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", + "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).", + "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", + "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", + "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", + "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", + "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", + "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s removed the rule banning users matching %(glob)s", + "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s removed the rule banning rooms matching %(glob)s", + "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s removed the rule banning servers matching %(glob)s", + "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s removed a ban rule matching %(glob)s", + "%(senderName)s updated an invalid ban rule": "%(senderName)s updated an invalid ban rule", + "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s", + "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s", + "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s", + "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s", + "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s", + "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s", + "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s", + "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s created a ban rule matching %(glob)s for %(reason)s", + "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "Light theme": "Light theme", + "Dark theme": "Dark theme", + "%(displayName)s is typing …": "%(displayName)s is typing …", + "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", + "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", + "%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …", + "Cannot reach homeserver": "Cannot reach homeserver", + "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", + "Your Riot is misconfigured": "Your Riot is misconfigured", + "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Ask your Riot admin to check your config for incorrect or duplicate entries.", + "Cannot reach identity server": "Cannot reach identity server", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", + "No homeserver URL provided": "No homeserver URL provided", + "Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration", + "Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration", + "The message you are trying to send is too large.": "The message you are trying to send is too large.", + "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", + "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", + "Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.", + "Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...", + "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", + "%(items)s and %(count)s others|one": "%(items)s and one other", + "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", + "a few seconds ago": "a few seconds ago", + "about a minute ago": "about a minute ago", + "%(num)s minutes ago": "%(num)s minutes ago", + "about an hour ago": "about an hour ago", + "%(num)s hours ago": "%(num)s hours ago", + "about a day ago": "about a day ago", + "%(num)s days ago": "%(num)s days ago", + "a few seconds from now": "a few seconds from now", + "about a minute from now": "about a minute from now", + "%(num)s minutes from now": "%(num)s minutes from now", + "about an hour from now": "about an hour from now", + "%(num)s hours from now": "%(num)s hours from now", + "about a day from now": "about a day from now", + "%(num)s days from now": "%(num)s days from now", + "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", + "Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions", + "Not a valid Riot keyfile": "Not a valid Riot keyfile", + "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", + "Unrecognised address": "Unrecognised address", + "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": "User %(userId)s is already in the room", + "User %(user_id)s does not exist": "User %(user_id)s does not exist", + "User %(user_id)s may or may not exist": "User %(user_id)s may or may not exist", + "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", + "No need for symbols, digits, or uppercase letters": "No need for symbols, digits, or uppercase letters", + "Use a longer keyboard pattern with more turns": "Use a longer keyboard pattern with more turns", + "Avoid repeated words and characters": "Avoid repeated words and characters", + "Avoid sequences": "Avoid sequences", + "Avoid recent years": "Avoid recent years", + "Avoid years that are associated with you": "Avoid years that are associated with you", + "Avoid dates and years that are associated with you": "Avoid dates and years that are associated with you", + "Capitalization doesn't help very much": "Capitalization doesn't help very much", + "All-uppercase is almost as easy to guess as all-lowercase": "All-uppercase is almost as easy to guess as all-lowercase", + "Reversed words aren't much harder to guess": "Reversed words aren't much harder to guess", + "Predictable substitutions like '@' instead of 'a' don't help very much": "Predictable substitutions like '@' instead of 'a' don't help very much", + "Add another word or two. Uncommon words are better.": "Add another word or two. Uncommon words are better.", + "Repeats like \"aaa\" are easy to guess": "Repeats like \"aaa\" are easy to guess", + "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"", + "Sequences like abc or 6543 are easy to guess": "Sequences like abc or 6543 are easy to guess", + "Recent years are easy to guess": "Recent years are easy to guess", + "Dates are often easy to guess": "Dates are often easy to guess", + "This is a top-10 common password": "This is a top-10 common password", + "This is a top-100 common password": "This is a top-100 common password", + "This is a very common password": "This is a very common password", + "This is similar to a commonly used password": "This is similar to a commonly used password", + "A word by itself is easy to guess": "A word by itself is easy to guess", + "Names and surnames by themselves are easy to guess": "Names and surnames by themselves are easy to guess", + "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", + "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", + "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", + "There was an error joining the room": "There was an error joining the room", + "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", + "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", + "Failed to join room": "Failed to join room", + "Message Pinning": "Message Pinning", + "Custom user status messages": "Custom user status messages", + "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", + "Render simple counters in room header": "Render simple counters in room header", + "Multiple integration managers": "Multiple integration managers", + "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", + "New invite dialog": "New invite dialog", + "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", + "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", + "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", + "Show info about bridges in room settings": "Show info about bridges in room settings", + "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", + "Use compact timeline layout": "Use compact timeline layout", + "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", + "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", + "Always show message timestamps": "Always show message timestamps", + "Autoplay GIFs and videos": "Autoplay GIFs and videos", + "Always show encryption icons": "Always show encryption icons", + "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms", + "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", + "Show avatars in user and room mentions": "Show avatars in user and room mentions", + "Enable big emoji in chat": "Enable big emoji in chat", + "Send typing notifications": "Send typing notifications", + "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", + "Mirror local video feed": "Mirror local video feed", + "Enable Community Filter Panel": "Enable Community Filter Panel", + "Match system theme": "Match system theme", + "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", + "Send analytics data": "Send analytics data", + "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", + "Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device", + "Enable inline URL previews by default": "Enable inline URL previews by default", + "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", + "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", + "Room Colour": "Room Colour", + "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", + "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", + "Show developer tools": "Show developer tools", + "Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent", + "Show recently visited rooms above the room list": "Show recently visited rooms above the room list", + "Show hidden events in timeline": "Show hidden events in timeline", + "Low bandwidth mode": "Low bandwidth mode", + "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)", + "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", + "Show previews/thumbnails for images": "Show previews/thumbnails for images", + "Collecting app version information": "Collecting app version information", + "Collecting logs": "Collecting logs", + "Uploading report": "Uploading report", + "Waiting for response from server": "Waiting for response from server", + "Messages containing my display name": "Messages containing my display name", + "Messages containing my username": "Messages containing my username", + "Messages containing @room": "Messages containing @room", + "Messages in one-to-one chats": "Messages in one-to-one chats", + "Encrypted messages in one-to-one chats": "Encrypted messages in one-to-one chats", + "Messages in group chats": "Messages in group chats", + "Encrypted messages in group chats": "Encrypted messages in group chats", + "When I'm invited to a room": "When I'm invited to a room", + "Call invitation": "Call invitation", + "Messages sent by bot": "Messages sent by bot", + "When rooms are upgraded": "When rooms are upgraded", + "My Ban List": "My Ban List", + "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!", + "Active call (%(roomName)s)": "Active call (%(roomName)s)", + "unknown caller": "unknown caller", + "Incoming voice call from %(name)s": "Incoming voice call from %(name)s", + "Incoming video call from %(name)s": "Incoming video call from %(name)s", + "Incoming call from %(name)s": "Incoming call from %(name)s", + "Decline": "Decline", + "Accept": "Accept", + "The other party cancelled the verification.": "The other party cancelled the verification.", + "Verified!": "Verified!", + "You've successfully verified this user.": "You've successfully verified this user.", + "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.", + "Got It": "Got It", + "Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.", + "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.", + "Unable to find a supported verification method.": "Unable to find a supported verification method.", + "Cancel": "Cancel", + "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.", + "Dog": "Dog", + "Cat": "Cat", + "Lion": "Lion", + "Horse": "Horse", + "Unicorn": "Unicorn", + "Pig": "Pig", + "Elephant": "Elephant", + "Rabbit": "Rabbit", + "Panda": "Panda", + "Rooster": "Rooster", + "Penguin": "Penguin", + "Turtle": "Turtle", + "Fish": "Fish", + "Octopus": "Octopus", + "Butterfly": "Butterfly", + "Flower": "Flower", + "Tree": "Tree", + "Cactus": "Cactus", + "Mushroom": "Mushroom", + "Globe": "Globe", + "Moon": "Moon", + "Cloud": "Cloud", + "Fire": "Fire", + "Banana": "Banana", + "Apple": "Apple", + "Strawberry": "Strawberry", + "Corn": "Corn", + "Pizza": "Pizza", + "Cake": "Cake", + "Heart": "Heart", + "Smiley": "Smiley", + "Robot": "Robot", + "Hat": "Hat", + "Glasses": "Glasses", + "Spanner": "Spanner", + "Santa": "Santa", + "Thumbs up": "Thumbs up", + "Umbrella": "Umbrella", + "Hourglass": "Hourglass", + "Clock": "Clock", + "Gift": "Gift", + "Light bulb": "Light bulb", + "Book": "Book", + "Pencil": "Pencil", + "Paperclip": "Paperclip", + "Scissors": "Scissors", + "Lock": "Lock", + "Key": "Key", + "Hammer": "Hammer", + "Telephone": "Telephone", + "Flag": "Flag", + "Train": "Train", + "Bicycle": "Bicycle", + "Aeroplane": "Aeroplane", + "Rocket": "Rocket", + "Trophy": "Trophy", + "Ball": "Ball", + "Guitar": "Guitar", + "Trumpet": "Trumpet", + "Bell": "Bell", + "Anchor": "Anchor", + "Headphones": "Headphones", + "Folder": "Folder", + "Pin": "Pin", + "Other users may not trust it": "Other users may not trust it", + "Later": "Later", + "Verify": "Verify", + "Decline (%(counter)s)": "Decline (%(counter)s)", + "Accept to continue:": "Accept to continue:", + "Upload": "Upload", + "Remove": "Remove", + "Failed to upload profile picture!": "Failed to upload profile picture!", + "Upload new:": "Upload new:", + "No display name": "No display name", + "New passwords don't match": "New passwords don't match", + "Passwords can't be empty": "Passwords can't be empty", + "Warning!": "Warning!", + "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", + "Export E2E room keys": "Export E2E room keys", + "Do you want to set an email address?": "Do you want to set an email address?", + "Current password": "Current password", + "Password": "Password", + "New Password": "New Password", + "Confirm password": "Confirm password", + "Change Password": "Change Password", + "Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.", + "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.", + "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.", + "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", + "Cross-signing public keys:": "Cross-signing public keys:", + "on device": "on device", + "not found": "not found", + "Cross-signing private keys:": "Cross-signing private keys:", + "in secret storage": "in secret storage", + "Secret storage public key:": "Secret storage public key:", + "in account data": "in account data", + "Your homeserver does not support device management.": "Your homeserver does not support device management.", + "Unable to load device list": "Unable to load device list", + "Authentication": "Authentication", + "Delete %(count)s devices|other": "Delete %(count)s devices", + "Delete %(count)s devices|one": "Delete device", + "ID": "ID", + "Public Name": "Public Name", + "Last seen": "Last seen", + "Failed to set display name": "Failed to set display name", + "Disable Notifications": "Disable Notifications", + "Enable Notifications": "Enable Notifications", + "Connecting to integration manager...": "Connecting 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.", + "Delete Backup": "Delete Backup", + "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Unable to load key backup status": "Unable to load key backup status", + "Restore from Backup": "Restore from Backup", + "This device is backing up your keys. ": "This device is backing up your keys. ", + "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", + "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.", + "Connect this device to Key Backup": "Connect this device to Key Backup", + "not stored": "not stored", + "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", + "All keys backed up": "All keys backed up", + "Backup has a valid signature from this user": "Backup has a valid signature from this user", + "Backup has a invalid signature from this user": "Backup has a invalid signature from this user", + "Backup has a signature from unknown user with ID %(deviceId)s": "Backup has a signature from unknown user with ID %(deviceId)s", + "Backup has a signature from unknown device with ID %(deviceId)s": "Backup has a signature from unknown device with ID %(deviceId)s", + "Backup has a valid signature from this device": "Backup has a valid signature from this device", + "Backup has an invalid signature from this device": "Backup has an invalid signature from this device", + "Backup has a valid signature from verified device ": "Backup has a valid signature from verified device ", + "Backup has a valid signature from unverified device ": "Backup has a valid signature from unverified device ", + "Backup has an invalid signature from verified device ": "Backup has an invalid signature from verified device ", + "Backup has an invalid signature from unverified device ": "Backup has an invalid signature from unverified device ", + "Backup is not signed by any of your devices": "Backup is not signed by any of your devices", + "This backup is trusted because it has been restored on this device": "This backup is trusted because it has been restored on this device", + "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.": "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.", + "Backup version: ": "Backup version: ", + "Algorithm: ": "Algorithm: ", + "Backup key stored: ": "Backup key stored: ", + "Your keys are not being backed up from this device.": "Your keys are not being backed up from this device.", + "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", + "Start using Key Backup": "Start using Key Backup", + "Error saving email notification preferences": "Error saving email notification preferences", + "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.", + "Keywords": "Keywords", + "Enter keywords separated by a comma:": "Enter keywords separated by a comma:", + "Failed to change settings": "Failed to change settings", + "Can't update user notification settings": "Can't update user notification settings", + "Failed to update keywords": "Failed to update keywords", + "Messages containing keywords": "Messages containing keywords", + "Notify for all other messages/rooms": "Notify for all other messages/rooms", + "Notify me for anything else": "Notify me for anything else", + "Enable notifications for this account": "Enable notifications for this account", + "Clear notifications": "Clear notifications", + "All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.", + "Add an email address to configure email notifications": "Add an email address to configure email notifications", + "Enable email notifications": "Enable email notifications", + "Notifications on the following keywords follow rules which can’t be displayed here:": "Notifications on the following keywords follow rules which can’t be displayed here:", + "Unable to fetch notification target list": "Unable to fetch notification target list", + "Notification targets": "Notification targets", + "Advanced notification settings": "Advanced notification settings", + "There are advanced notifications which are not shown here": "There are advanced notifications which are not shown here", + "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply", + "Enable desktop notifications for this device": "Enable desktop notifications for this device", + "Show message in desktop notification": "Show message in desktop notification", + "Enable audible notifications for this device": "Enable audible notifications for this device", + "Off": "Off", + "On": "On", + "Noisy": "Noisy", + "Upgrade to your own domain": "Upgrade to your own domain", + "Display Name": "Display Name", + "Profile picture": "Profile picture", + "Save": "Save", + "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", + "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", + "Could not connect to Identity Server": "Could not connect to Identity Server", + "Checking server": "Checking server", + "Change identity server": "Change identity server", + "Disconnect from the identity server and connect to instead?": "Disconnect from the identity server and connect to instead?", + "Terms of service not accepted or the identity server is invalid.": "Terms of service not accepted or the identity server is invalid.", + "The identity server you have chosen does not have any terms of service.": "The identity server you have chosen does not have any terms of service.", + "Disconnect identity server": "Disconnect identity server", + "Disconnect from the identity server ?": "Disconnect from the identity server ?", + "Disconnect": "Disconnect", + "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.", + "You should:": "You should:", + "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "check your browser plugins for anything that might block the identity server (such as Privacy Badger)", + "contact the administrators of identity server ": "contact the administrators of identity server ", + "wait and try again later": "wait and try again later", + "Disconnect anyway": "Disconnect anyway", + "You are still sharing your personal data on the identity server .": "You are still sharing your personal data on the identity server .", + "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.", + "Go back": "Go back", + "Identity Server (%(server)s)": "Identity Server (%(server)s)", + "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.", + "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.", + "Identity Server": "Identity Server", + "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.", + "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.", + "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.", + "Do not use an identity server": "Do not use an identity server", + "Enter a new identity server": "Enter a new identity server", + "Change": "Change", + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.", + "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.", + "Manage integrations": "Manage integrations", + "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.", + "Flair": "Flair", + "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", + "Success": "Success", + "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them", + "Profile": "Profile", + "Email addresses": "Email addresses", + "Phone numbers": "Phone numbers", + "Account": "Account", + "Set a new account password...": "Set a new account password...", + "Language and region": "Language and region", + "Theme": "Theme", + "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", + "Account management": "Account management", + "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", + "Deactivate Account": "Deactivate Account", + "Warning": "Warning", + "General": "General", + "Discovery": "Discovery", + "Deactivate account": "Deactivate account", + "Legal": "Legal", + "Credits": "Credits", + "For help with using Riot, click here.": "For help with using Riot, click here.", + "For help with using Riot, click here or start a chat with our bot using the button below.": "For help with using Riot, click here or start a chat with our bot using the button below.", + "Chat with Riot Bot": "Chat with Riot Bot", + "Check for update": "Check for update", + "Help & About": "Help & About", + "Bug reporting": "Bug reporting", + "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 and the usernames of other users. They do not contain messages.": "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 and the usernames of other users. They do not contain messages.", + "Submit debug logs": "Submit debug logs", + "Clear cache and reload": "Clear cache and reload", + "FAQ": "FAQ", + "Versions": "Versions", + "matrix-react-sdk version:": "matrix-react-sdk version:", + "riot-web version:": "riot-web version:", + "olm version:": "olm version:", + "Homeserver is": "Homeserver is", + "Identity Server is": "Identity Server is", + "Access Token:": "Access Token:", + "click to reveal": "click to reveal", + "Labs": "Labs", + "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", + "Ignored/Blocked": "Ignored/Blocked", + "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 alias and try again.": "Please verify the room ID or alias and try again.", + "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.", + "None": "None", + "Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s", + "Server rules": "Server rules", + "User rules": "User rules", + "Close": "Close", + "You have not ignored anyone.": "You have not ignored anyone.", + "You are currently ignoring:": "You are currently ignoring:", + "You are not subscribed to any lists": "You are not subscribed to any lists", + "Unsubscribe": "Unsubscribe", + "View rules": "View rules", + "You are currently subscribed to:": "You are currently subscribed to:", + "Ignored users": "Ignored users", + "⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.", + "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.", + "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": "Server or user ID to ignore", + "eg: @bot:* or example.org": "eg: @bot:* or 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 alias of ban list": "Room ID or alias of ban list", + "Subscribe": "Subscribe", + "Notifications": "Notifications", + "Start automatically after system login": "Start automatically after system login", + "Always show the window menu bar": "Always show the window menu bar", + "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", + "Preferences": "Preferences", + "Composer": "Composer", + "Timeline": "Timeline", + "Room list": "Room list", + "Autocomplete delay (ms)": "Autocomplete delay (ms)", + "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", + "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", + "Unignore": "Unignore", + "": "", + "Import E2E room keys": "Import E2E room keys", + "Cryptography": "Cryptography", + "Device ID:": "Device ID:", + "Device key:": "Device key:", + "Bulk options": "Bulk options", + "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", + "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", + "Key backup": "Key backup", + "Cross-signing": "Cross-signing", + "Security & Privacy": "Security & Privacy", + "Devices": "Devices", + "A device's public name is visible to people you communicate with": "A device's public name is visible to people you communicate with", + "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.", + "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", + "Learn more about how we use analytics.": "Learn more about how we use analytics.", + "No media permissions": "No media permissions", + "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", + "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", + "Request media permissions": "Request media permissions", + "No Audio Outputs detected": "No Audio Outputs detected", + "No Microphones detected": "No Microphones detected", + "No Webcams detected": "No Webcams detected", + "Default Device": "Default Device", + "Audio Output": "Audio Output", + "Microphone": "Microphone", + "Camera": "Camera", + "Voice & Video": "Voice & Video", + "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", + "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", + "Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version", + "this room": "this room", + "View older messages in %(roomName)s.": "View older messages in %(roomName)s.", + "Room information": "Room information", + "Internal room ID:": "Internal room ID:", + "Room version": "Room version", + "Room version:": "Room version:", + "Developer options": "Developer options", + "Open Devtools": "Open Devtools", + "This bridge was provisioned by ": "This bridge was provisioned by ", + "This bridge is managed by .": "This bridge is managed by .", + "Bridged into , on ": "Bridged into , on ", + "Connected to on ": "Connected to on ", + "Connected via %(protocolName)s": "Connected via %(protocolName)s", + "Bridge Info": "Bridge Info", + "Below is a list of bridges connected to this room.": "Below is a list of bridges connected to this room.", + "Room Addresses": "Room Addresses", + "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", + "URL Previews": "URL Previews", + "Uploaded sound": "Uploaded sound", + "Sounds": "Sounds", + "Notification sound": "Notification sound", + "Reset": "Reset", + "Set a new custom sound": "Set a new custom sound", + "Browse": "Browse", + "Change room avatar": "Change room avatar", + "Change room name": "Change room name", + "Change main address for the room": "Change main address for the room", + "Change history visibility": "Change history visibility", + "Change permissions": "Change permissions", + "Change topic": "Change topic", + "Upgrade the room": "Upgrade the room", + "Enable room encryption": "Enable room encryption", + "Modify widgets": "Modify widgets", + "Failed to unban": "Failed to unban", + "Unban": "Unban", + "Banned by %(displayName)s": "Banned by %(displayName)s", + "Error changing power level requirement": "Error changing power level requirement", + "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.", + "Error changing power level": "Error changing power level", + "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.", + "Default role": "Default role", + "Send messages": "Send messages", + "Invite users": "Invite users", + "Change settings": "Change settings", + "Kick users": "Kick users", + "Ban users": "Ban users", + "Remove messages": "Remove messages", + "Notify everyone": "Notify everyone", + "No users have specific privileges in this room": "No users have specific privileges in this room", + "Privileged Users": "Privileged Users", + "Muted Users": "Muted Users", + "Banned users": "Banned users", + "Send %(eventType)s events": "Send %(eventType)s events", + "Roles & Permissions": "Roles & Permissions", + "Permissions": "Permissions", + "Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room", + "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. Learn more about 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. Learn more about encryption.", + "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", + "Click here to fix": "Click here to fix", + "To link to this room, please add an alias.": "To link to this room, please add an alias.", + "Only people who have been invited": "Only people who have been invited", + "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", + "Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests", + "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.", + "Anyone": "Anyone", + "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)", + "Encryption": "Encryption", + "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", + "Encrypted": "Encrypted", + "Who can access this room?": "Who can access this room?", + "Who can read history?": "Who can read history?", + "Unable to revoke sharing for email address": "Unable to revoke sharing for email 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.", + "Unable to verify email address.": "Unable to verify email address.", + "Verify the link in your inbox": "Verify the link in your inbox", + "Complete": "Complete", + "Revoke": "Revoke", + "Share": "Share", + "Discovery options will appear once you have added an email above.": "Discovery options will appear once you have added an email above.", + "Unable to revoke sharing for phone number": "Unable to revoke sharing for phone number", + "Unable to share phone number": "Unable to share phone number", + "Unable to verify phone number.": "Unable to verify phone number.", + "Incorrect verification code": "Incorrect verification code", + "Please enter verification code sent via text.": "Please enter verification code sent via text.", + "Verification code": "Verification code", + "Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.", + "Unable to remove contact information": "Unable to remove contact information", + "Remove %(email)s?": "Remove %(email)s?", + "Invalid Email Address": "Invalid Email Address", + "This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address", + "Unable to add email address": "Unable to add email address", + "Add": "Add", + "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.", + "Email Address": "Email Address", + "Remove %(phone)s?": "Remove %(phone)s?", + "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", + "Phone Number": "Phone Number", + "Cannot add any more widgets": "Cannot add any more widgets", + "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", + "Add a widget": "Add a widget", + "Drop File Here": "Drop File Here", + "Drop file here to upload": "Drop file here to upload", + " (unsupported)": " (unsupported)", + "Join as voice or video.": "Join as voice or video.", + "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", + "This user has not verified all of their devices.": "This user has not verified all of their devices.", + "You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.", + "You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.", + "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.", + "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.", + "Some devices for this user are not trusted": "Some devices for this user are not trusted", + "All devices for this user are trusted": "All devices for this user are trusted", + "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", + "All devices in this encrypted room are trusted": "All devices in this encrypted room are trusted", + "Edit message": "Edit message", + "This event could not be displayed": "This event could not be displayed", + "%(senderName)s sent an image": "%(senderName)s sent an image", + "%(senderName)s sent a video": "%(senderName)s sent a video", + "%(senderName)s uploaded a file": "%(senderName)s uploaded a file", + "Your key share request has been sent - please check your other devices for key share requests.": "Your key share request has been sent - please check your other devices for key share requests.", + "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.": "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.", + "If your other devices do not have the key for this message you will not be able to decrypt them.": "If your other devices do not have the key for this message you will not be able to decrypt them.", + "Key request sent.": "Key request sent.", + "Re-request encryption keys from your other devices.": "Re-request encryption keys from your other devices.", + "This message cannot be decrypted": "This message cannot be decrypted", + "Encrypted by an unverified device": "Encrypted by an unverified device", + "Unencrypted": "Unencrypted", + "Please select the destination room for this message": "Please select the destination room for this message", + "Scroll to bottom of page": "Scroll to bottom of page", + "Close preview": "Close preview", + "device id: ": "device id: ", + "Disinvite": "Disinvite", + "Kick": "Kick", + "Disinvite this user?": "Disinvite this user?", + "Kick this user?": "Kick this user?", + "Failed to kick": "Failed to kick", + "Ban": "Ban", + "Unban this user?": "Unban this user?", + "Ban this user?": "Ban this user?", + "Failed to ban user": "Failed to ban user", + "No recent messages by %(user)s found": "No recent messages by %(user)s found", + "Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.", + "Remove recent messages by %(user)s": "Remove recent messages by %(user)s", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?", + "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.", + "Remove %(count)s messages|other": "Remove %(count)s messages", + "Remove %(count)s messages|one": "Remove 1 message", + "Demote yourself?": "Demote yourself?", + "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.", + "Demote": "Demote", + "Failed to mute user": "Failed to mute user", + "Failed to toggle moderator status": "Failed to toggle moderator status", + "Deactivate user?": "Deactivate 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", + "Failed to deactivate user": "Failed to deactivate user", + "Failed to change power level": "Failed to change power level", + "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.", + "Are you sure?": "Are you sure?", + "No devices with registered encryption keys": "No devices with registered encryption keys", + "Jump to read receipt": "Jump to read receipt", + "Mention": "Mention", + "Invite": "Invite", + "Share Link to User": "Share Link to User", + "User Options": "User Options", + "Direct chats": "Direct chats", + "Remove recent messages": "Remove recent messages", + "Unmute": "Unmute", + "Mute": "Mute", + "Revoke Moderator": "Revoke Moderator", + "Make Moderator": "Make Moderator", + "Admin Tools": "Admin Tools", + "and %(count)s others...|other": "and %(count)s others...", + "and %(count)s others...|one": "and one other...", + "Invite to this room": "Invite to this room", + "Invited": "Invited", + "Filter room members": "Filter room members", + "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", + "Voice call": "Voice call", + "Video call": "Video call", + "Hangup": "Hangup", + "Upload file": "Upload file", + "Send an encrypted reply…": "Send an encrypted reply…", + "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", + "Send an encrypted message…": "Send an encrypted message…", + "Send a message (unencrypted)…": "Send a message (unencrypted)…", + "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.", + "You do not have permission to post to this room": "You do not have permission to post to this room", + "Bold": "Bold", + "Italics": "Italics", + "Strikethrough": "Strikethrough", + "Code block": "Code block", + "Quote": "Quote", + "No pinned messages.": "No pinned messages.", + "Loading...": "Loading...", + "Pinned Messages": "Pinned Messages", + "Unpin Message": "Unpin Message", + "Jump to message": "Jump to message", + "%(duration)ss": "%(duration)ss", + "%(duration)sm": "%(duration)sm", + "%(duration)sh": "%(duration)sh", + "%(duration)sd": "%(duration)sd", + "Online for %(duration)s": "Online for %(duration)s", + "Idle for %(duration)s": "Idle for %(duration)s", + "Offline for %(duration)s": "Offline for %(duration)s", + "Unknown for %(duration)s": "Unknown for %(duration)s", + "Online": "Online", + "Idle": "Idle", + "Offline": "Offline", + "Unknown": "Unknown", + "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", + "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", + "Replying": "Replying", + "Direct Chat": "Direct Chat", + "Room %(name)s": "Room %(name)s", + "Recent rooms": "Recent rooms", + "No rooms to show": "No rooms to show", + "Unnamed room": "Unnamed room", + "World readable": "World readable", + "Guests can join": "Guests can join", + "(~%(count)s results)|other": "(~%(count)s results)", + "(~%(count)s results)|one": "(~%(count)s result)", + "Join Room": "Join Room", + "Settings": "Settings", + "Forget room": "Forget room", + "Search": "Search", + "Share room": "Share room", + "Community Invites": "Community Invites", + "Invites": "Invites", + "Favourites": "Favourites", + "People": "People", + "Start chat": "Start chat", + "Rooms": "Rooms", + "Low priority": "Low priority", + "Historical": "Historical", + "System Alerts": "System Alerts", + "This room": "This room", + "Joining room …": "Joining room …", + "Loading …": "Loading …", + "Rejecting invite …": "Rejecting invite …", + "Join the conversation with an account": "Join the conversation with an account", + "Sign Up": "Sign Up", + "Sign In": "Sign In", + "Loading room preview": "Loading room preview", + "You were kicked from %(roomName)s by %(memberName)s": "You were kicked from %(roomName)s by %(memberName)s", + "Reason: %(reason)s": "Reason: %(reason)s", + "Forget this room": "Forget this room", + "Re-join": "Re-join", + "You were banned from %(roomName)s by %(memberName)s": "You were banned from %(roomName)s by %(memberName)s", + "Something went wrong with your invite to %(roomName)s": "Something went wrong with your invite to %(roomName)s", + "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.", + "unknown error code": "unknown error code", + "You can only join it with a working invite.": "You can only join it with a working invite.", + "Try to join anyway": "Try to join anyway", + "You can still join it because this is a public room.": "You can still join it because this is a public room.", + "Join the discussion": "Join the discussion", + "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "This invite to %(roomName)s was sent to %(email)s which is not associated with your account", + "Link this email with your account in Settings to receive invites directly in Riot.": "Link this email with your account in Settings to receive invites directly in Riot.", + "This invite to %(roomName)s was sent to %(email)s": "This invite to %(roomName)s was sent to %(email)s", + "Use an identity server in Settings to receive invites directly in Riot.": "Use an identity server in Settings to receive invites directly in Riot.", + "Share this email in Settings to receive invites directly in Riot.": "Share this email in Settings to receive invites directly in Riot.", + "Do you want to chat with %(user)s?": "Do you want to chat with %(user)s?", + " wants to chat": " wants to chat", + "Start chatting": "Start chatting", + "Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?", + " invited you": " invited you", + "Reject": "Reject", + "You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?", + "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s can't be previewed. Do you want to join it?", + "%(roomName)s does not exist.": "%(roomName)s does not exist.", + "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?", + "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", + "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 submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", + "Never lose encrypted messages": "Never lose encrypted 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 secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", + "Not now": "Not now", + "Don't ask me again": "Don't ask me again", + "Options": "Options", + "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", + "%(count)s unread messages including mentions.|one": "1 unread mention.", + "%(count)s unread messages.|other": "%(count)s unread messages.", + "%(count)s unread messages.|one": "1 unread message.", + "Unread mentions.": "Unread mentions.", + "Unread messages.": "Unread messages.", + "Add a topic": "Add a topic", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", + "This room has already been upgraded.": "This room has already been upgraded.", + "This room is running room version , which this homeserver has marked as unstable.": "This room is running room version , which this homeserver has marked as unstable.", + "Only room administrators will see this warning": "Only room administrators will see this warning", + "This Room": "This Room", + "All Rooms": "All Rooms", + "Search…": "Search…", + "Server error": "Server error", + "Command error": "Command error", + "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", + "Failed to connect to integration manager": "Failed to connect to integration manager", + "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", + "Add some now": "Add some now", + "Stickerpack": "Stickerpack", + "Hide Stickers": "Hide Stickers", + "Show Stickers": "Show Stickers", + "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.", + "Revoke invite": "Revoke invite", + "Invited by %(sender)s": "Invited by %(sender)s", + "Jump to first unread message.": "Jump to first unread message.", + "Error updating main address": "Error updating main address", + "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", + "Error creating alias": "Error creating alias", + "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", + "Error removing alias": "Error removing alias", + "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", + "Main address": "Main address", + "not specified": "not specified", + "Remote addresses for this room:": "Remote addresses for this room:", + "Local addresses for this room:": "Local addresses for this room:", + "This room has no local addresses": "This room has no local addresses", + "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", + "Error updating flair": "Error updating flair", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.", + "Invalid community ID": "Invalid community ID", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID", + "Showing flair for these communities:": "Showing flair for these communities:", + "This room is not showing flair for any communities": "This room is not showing flair for any communities", + "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)", + "Room Name": "Room Name", + "Room Topic": "Room Topic", + "Room avatar": "Room avatar", + "You have enabled URL previews by default.": "You have enabled URL previews by default.", + "You have disabled URL previews by default.": "You have disabled URL previews by default.", + "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.", + "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", + "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.", + "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.": "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.", + "Verify User": "Verify User", + "For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.", + "For maximum security, do this in person.": "For maximum security, do this in person.", + "Start Verification": "Start Verification", + "Members": "Members", + "Files": "Files", + "Trusted": "Trusted", + "Not trusted": "Not trusted", + "Hide verified sessions": "Hide verified sessions", + "%(count)s verified sessions|other": "%(count)s verified sessions", + "%(count)s verified sessions|one": "1 verified session", + "Direct message": "Direct message", + "Remove from community": "Remove from community", + "Disinvite this user from community?": "Disinvite this user from community?", + "Remove this user from community?": "Remove this user from community?", + "Failed to withdraw invitation": "Failed to withdraw invitation", + "Failed to remove user from community": "Failed to remove user from community", + "%(role)s in %(roomName)s": "%(role)s in %(roomName)s", + "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", + "Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.", + "Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.", + "Security": "Security", + "Sunday": "Sunday", + "Monday": "Monday", + "Tuesday": "Tuesday", + "Wednesday": "Wednesday", + "Thursday": "Thursday", + "Friday": "Friday", + "Saturday": "Saturday", + "Today": "Today", + "Yesterday": "Yesterday", + "View Source": "View Source", + "Error decrypting audio": "Error decrypting audio", + "React": "React", + "Reply": "Reply", + "Edit": "Edit", + "Message Actions": "Message Actions", + "Attachment": "Attachment", + "Error decrypting attachment": "Error decrypting attachment", + "Decrypt %(text)s": "Decrypt %(text)s", + "Download %(text)s": "Download %(text)s", + "Invalid file%(extra)s": "Invalid file%(extra)s", + "Error decrypting image": "Error decrypting image", + "Show image": "Show image", + "You have ignored this user, so their message is hidden. Show anyways.": "You have ignored this user, so their message is hidden. Show anyways.", + "You verified %(name)s": "You verified %(name)s", + "You cancelled verifying %(name)s": "You cancelled verifying %(name)s", + "%(name)s cancelled verifying": "%(name)s cancelled verifying", + "You accepted": "You accepted", + "%(name)s accepted": "%(name)s accepted", + "You cancelled": "You cancelled", + "%(name)s cancelled": "%(name)s cancelled", + "%(name)s wants to verify": "%(name)s wants to verify", + "You sent a verification request": "You sent a verification request", + "Error decrypting video": "Error decrypting video", + "Show all": "Show all", + "Reactions": "Reactions", + " reacted with %(content)s": " reacted with %(content)s", + "reacted with %(shortName)s": "reacted with %(shortName)s", + "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", + "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", + "This room is a continuation of another conversation.": "This room is a continuation of another conversation.", + "Click here to see older messages.": "Click here to see older messages.", + "Copied!": "Copied!", + "Failed to copy": "Failed to copy", + "Add an Integration": "Add an Integration", + "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", + "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", + "edited": "edited", + "Removed or unknown message type": "Removed or unknown message type", + "Message removed by %(userId)s": "Message removed by %(userId)s", + "Message removed": "Message removed", + "Failed to load group members": "Failed to load group members", + "Filter community members": "Filter community members", + "Invite to this community": "Invite to this community", + "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)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": "Failed to remove '%(roomName)s' from %(groupId)s", + "Something went wrong!": "Something went wrong!", + "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "The visibility of '%(roomName)s' in %(groupId)s could not be updated.", + "Visibility in Room List": "Visibility in Room List", + "Visible to everyone": "Visible to everyone", + "Only visible to community members": "Only visible to community members", + "Add rooms to this community": "Add rooms to this community", + "Filter community rooms": "Filter community rooms", + "Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.", + "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.", + "You're not currently a member of any communities.": "You're not currently a member of any communities.", + "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).", + "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.", + "Yes, I want to help!": "Yes, I want to help!", + "You are not receiving desktop notifications": "You are not receiving desktop notifications", + "Enable them now": "Enable them now", + "What's New": "What's New", + "Update": "Update", + "What's new?": "What's new?", + "A new version of Riot is available.": "A new version of Riot is available.", + "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", + "Set Password": "Set Password", + "Please contact your service administrator to get this limit increased.": "Please contact your service administrator to get this limit increased.", + "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.": "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.", + "This homeserver has exceeded one of its resource limits so some users will not be able to log in.": "This homeserver has exceeded one of its resource limits so some users will not be able to log in.", + "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", + "Checking for an update...": "Checking for an update...", + "No update available.": "No update available.", + "Downloading update...": "Downloading update...", + "Frequently Used": "Frequently Used", + "Smileys & People": "Smileys & People", + "Animals & Nature": "Animals & Nature", + "Food & Drink": "Food & Drink", + "Activities": "Activities", + "Travel & Places": "Travel & Places", + "Objects": "Objects", + "Symbols": "Symbols", + "Flags": "Flags", + "Quick Reactions": "Quick Reactions", + "Cancel search": "Cancel search", + "Unknown Address": "Unknown Address", + "Any of the following data may be shared:": "Any of the following data may be shared:", + "Your display name": "Your display name", + "Your avatar URL": "Your avatar URL", + "Your user ID": "Your user ID", + "Your theme": "Your theme", + "Riot URL": "Riot URL", + "Room ID": "Room ID", + "Widget ID": "Widget ID", + "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data with %(widgetDomain)s & your Integration Manager.", + "Using this widget may share data with %(widgetDomain)s.": "Using this widget may share data with %(widgetDomain)s.", + "Widgets do not use message encryption.": "Widgets do not use message encryption.", + "Widget added by": "Widget added by", + "This widget may use cookies.": "This widget may use cookies.", + "Delete Widget": "Delete Widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", + "Delete widget": "Delete widget", + "Failed to remove widget": "Failed to remove widget", + "An error ocurred whilst trying to remove the widget from the room": "An error ocurred whilst trying to remove the widget from the room", + "Minimize apps": "Minimize apps", + "Maximize apps": "Maximize apps", + "Popout widget": "Popout widget", + "More options": "More options", + "Create new room": "Create new room", + "Unblacklist": "Unblacklist", + "Blacklist": "Blacklist", + "Unverify": "Unverify", + "Verify...": "Verify...", + "Join": "Join", + "No results": "No results", + "Yes": "Yes", + "No": "No", + "Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.", + "collapse": "collapse", + "expand": "expand", + "Communities": "Communities", + "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", + "Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s", + "Rotate Left": "Rotate Left", + "Rotate counter-clockwise": "Rotate counter-clockwise", + "Rotate Right": "Rotate Right", + "Rotate clockwise": "Rotate clockwise", + "Download this file": "Download this file", + "Language Dropdown": "Language Dropdown", + "Manage Integrations": "Manage Integrations", + "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", + "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", + "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", + "%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times", + "%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined", + "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times", + "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft", + "%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times", + "%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft", + "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times", + "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left", + "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times", + "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left", + "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times", + "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined", + "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times", + "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined", + "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times", + "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations", + "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times", + "%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation", + "%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times", + "%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn", + "%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times", + "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn", + "were invited %(count)s times|other": "were invited %(count)s times", + "were invited %(count)s times|one": "were invited", + "was invited %(count)s times|other": "was invited %(count)s times", + "was invited %(count)s times|one": "was invited", + "were banned %(count)s times|other": "were banned %(count)s times", + "were banned %(count)s times|one": "were banned", + "was banned %(count)s times|other": "was banned %(count)s times", + "was banned %(count)s times|one": "was banned", + "were unbanned %(count)s times|other": "were unbanned %(count)s times", + "were unbanned %(count)s times|one": "were unbanned", + "was unbanned %(count)s times|other": "was unbanned %(count)s times", + "was unbanned %(count)s times|one": "was unbanned", + "were kicked %(count)s times|other": "were kicked %(count)s times", + "were kicked %(count)s times|one": "were kicked", + "was kicked %(count)s times|other": "was kicked %(count)s times", + "was kicked %(count)s times|one": "was kicked", + "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times", + "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name", + "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times", + "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name", + "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar", + "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)smade no changes %(count)s times", + "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)smade no changes", + "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)smade no changes %(count)s times", + "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes", + "Power level": "Power level", + "Custom level": "Custom level", + "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", + "In reply to ": "In reply to ", + "Room alias": "Room alias", + "e.g. my-room": "e.g. my-room", + "Some characters not allowed": "Some characters not allowed", + "Please provide a room alias": "Please provide a room alias", + "This alias is available to use": "This alias is available to use", + "This alias is already in use": "This alias is already in use", + "Room directory": "Room directory", + "And %(count)s more...|other": "And %(count)s more...", + "ex. @bob:example.com": "ex. @bob:example.com", + "Add User": "Add User", + "Matrix ID": "Matrix ID", + "Matrix Room ID": "Matrix Room ID", + "email address": "email address", + "That doesn't look like a valid email address": "That doesn't look like a valid email address", + "You have entered an invalid address.": "You have entered an invalid address.", + "Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.", + "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.", + "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", + "The following users may not exist": "The following users may not exist", + "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?", + "Invite anyway and never warn me again": "Invite anyway and never warn me again", + "Invite anyway": "Invite anyway", + "Close dialog": "Close dialog", + "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.", + "Preparing to send logs": "Preparing to send logs", + "Logs sent": "Logs sent", + "Thank you!": "Thank you!", + "Failed to send logs: ": "Failed to send logs: ", + "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Before submitting logs, you must create a GitHub issue to describe your problem.", + "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.": "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.", + "Send logs": "Send logs", + "Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s", + "Unavailable": "Unavailable", + "Changelog": "Changelog", + "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", + "Removing…": "Removing…", + "Confirm Removal": "Confirm Removal", + "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", + "Clear all data on this device?": "Clear all data on this device?", + "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.", + "Clear all data": "Clear all data", + "Community IDs cannot be empty.": "Community IDs cannot be empty.", + "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'", + "Something went wrong whilst creating your community": "Something went wrong whilst creating your community", + "Create Community": "Create Community", + "Community Name": "Community Name", + "Example": "Example", + "Community ID": "Community ID", + "example": "example", + "Create": "Create", + "Please enter a name for the room": "Please enter a name for the room", + "Set a room alias to easily share your room with other people.": "Set a room alias to easily share your room with other people.", + "This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.", + "Create a public room": "Create a public room", + "Create a private room": "Create a private room", + "Name": "Name", + "Topic (optional)": "Topic (optional)", + "Make this room public": "Make this room public", + "Hide advanced": "Hide advanced", + "Show advanced": "Show advanced", + "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)", + "Create Room": "Create Room", + "Sign out": "Sign out", + "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 Riot to do this": "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 Riot to do this", + "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ", + "Incompatible Database": "Incompatible Database", + "Continue With Encryption Disabled": "Continue With Encryption Disabled", + "Unknown error": "Unknown error", + "Incorrect password": "Incorrect password", + "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.", + "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.", + "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", + "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", + "To continue, please enter your password:": "To continue, please enter your password:", + "Verify device": "Verify device", + "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)", + "Verify by comparing a short text string.": "Verify by comparing a short text string.", + "Begin Verifying": "Begin Verifying", + "Waiting for partner to accept...": "Waiting for partner to accept...", + "Nothing appearing? Not all clients support interactive verification yet. .": "Nothing appearing? Not all clients support interactive verification yet. .", + "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...", + "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:": "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:", + "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:", + "Use two-way text verification": "Use two-way text verification", + "Device name": "Device name", + "Device ID": "Device ID", + "Device key": "Device key", + "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.", + "I verify that the keys match": "I verify that the keys match", + "Back": "Back", + "Send Custom Event": "Send Custom Event", + "You must specify an event type!": "You must specify an event type!", + "Event sent!": "Event sent!", + "Failed to send custom event.": "Failed to send custom event.", + "Event Type": "Event Type", + "State Key": "State Key", + "Event Content": "Event Content", + "Send Account Data": "Send Account Data", + "Filter results": "Filter results", + "Explore Room State": "Explore Room State", + "Explore Account Data": "Explore Account Data", + "View Servers in Room": "View Servers in Room", + "Toolbox": "Toolbox", + "Developer Tools": "Developer Tools", + "An error has occurred.": "An error has occurred.", + "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.", + "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.", + "Waiting for partner to confirm...": "Waiting for partner to confirm...", + "Incoming Verification Request": "Incoming Verification Request", + "Integrations are disabled": "Integrations are disabled", + "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", + "Integrations not allowed": "Integrations not allowed", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.", + "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.", + "Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.", + "Failed to find the following users": "Failed to find the following users", + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s", + "Recent Conversations": "Recent Conversations", + "Suggestions": "Suggestions", + "Recently Direct Messaged": "Recently Direct Messaged", + "Show more": "Show more", + "Direct Messages": "Direct Messages", + "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", + "Go": "Go", + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", + "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", + "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", + "Start verification": "Start verification", + "Share without verifying": "Share without verifying", + "Ignore request": "Ignore request", + "Loading device info...": "Loading device info...", + "Encryption key request": "Encryption key request", + "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.", + "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.", + "Incompatible local cache": "Incompatible local cache", + "Clear cache and resync": "Clear cache and resync", + "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", + "Updating Riot": "Updating Riot", + "I don't want my encrypted messages": "I don't want my encrypted messages", + "Manually export keys": "Manually export keys", + "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", + "Are you sure you want to sign out?": "Are you sure you want to sign out?", + "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", + "Message edits": "Message edits", + "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.", + "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.", + "Report bugs & give feedback": "Report bugs & give feedback", + "Please fill why you're reporting.": "Please fill why you're reporting.", + "Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator", + "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.": "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.", + "Send report": "Send report", + "Room Settings - %(roomName)s": "Room Settings - %(roomName)s", + "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": "Upgrade this room to version %(version)s", + "Upgrade Room Version": "Upgrade Room Version", + "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:", + "Create a new room with the same name, description and avatar": "Create a new room with the same name, description and avatar", + "Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room", + "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room", + "Put a link back to the old room at the start of the new room so people can see old messages": "Put a link back to the old room at the start of the new room so people can see old messages", + "Automatically invite users": "Automatically invite users", + "Upgrade private room": "Upgrade private room", + "Upgrade public room": "Upgrade public room", + "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.", + "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.", + "You'll upgrade this room from to .": "You'll upgrade this room from to .", + "Upgrade": "Upgrade", + "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", + "Clear Storage and Sign Out": "Clear Storage and Sign Out", + "Send Logs": "Send Logs", + "Refresh": "Refresh", + "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.", + "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.", + "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.", + "Verification Pending": "Verification Pending", + "Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.", + "Email address": "Email address", + "This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.", + "Skip": "Skip", + "A username can only contain lower case letters, numbers and '=_-./'": "A username can only contain lower case letters, numbers and '=_-./'", + "Username not available": "Username not available", + "Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s", + "An error occurred: %(error_string)s": "An error occurred: %(error_string)s", + "Checking...": "Checking...", + "Username available": "Username available", + "To get started, please pick a username!": "To get started, please pick a username!", + "This will be your account name on the homeserver, or you can pick a different server.": "This will be your account name on the homeserver, or you can pick a different server.", + "If you already have a Matrix account you can log in instead.": "If you already have a Matrix account you can log in instead.", + "You have successfully set a password!": "You have successfully set a password!", + "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", + "You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.", + "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.", + "(HTTP status %(httpStatus)s)": "(HTTP status %(httpStatus)s)", + "Please set a password!": "Please set a password!", + "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", + "Share Room": "Share Room", + "Link to most recent message": "Link to most recent message", + "Share User": "Share User", + "Share Community": "Share Community", + "Share Room Message": "Share Room Message", + "Link to selected message": "Link to selected message", + "COPY": "COPY", + "Command Help": "Command Help", + "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", + "Missing session data": "Missing session data", + "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.", + "Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.", + "Integration Manager": "Integration Manager", + "Find others by phone or email": "Find others by phone or email", + "Be found by phone or email": "Be found by phone or email", + "Use bots, bridges, widgets and sticker packs": "Use bots, bridges, widgets and sticker packs", + "Terms of Service": "Terms of Service", + "To continue you need to accept the terms of this service.": "To continue you need to accept the terms of this service.", + "Service": "Service", + "Summary": "Summary", + "Document": "Document", + "Next": "Next", + "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.", + "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", + "Room contains unknown devices": "Room contains unknown devices", + "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", + "Unknown devices": "Unknown devices", + "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", + "Upload files": "Upload files", + "Upload all": "Upload all", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", + "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.", + "Upload %(count)s other files|other": "Upload %(count)s other files", + "Upload %(count)s other files|one": "Upload %(count)s other file", + "Cancel All": "Cancel All", + "Upload Error": "Upload Error", + "A widget would like to verify your identity": "A widget would like to verify your identity", + "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.", + "Remember my selection for this widget": "Remember my selection for this widget", + "Allow": "Allow", + "Deny": "Deny", + "Enter secret storage passphrase": "Enter secret storage passphrase", + "Unable to access secret storage. Please verify that you entered the correct passphrase.": "Unable to access secret storage. Please verify that you entered the correct passphrase.", + "Warning: You should only access secret storage from a trusted computer.": "Warning: You should only access secret storage from a trusted computer.", + "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.", + "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.", + "Enter secret storage recovery key": "Enter secret storage recovery key", + "This looks like a valid recovery key!": "This looks like a valid recovery key!", + "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", + "Not a valid recovery key": "Not a valid recovery key", + "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.", + "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .", + "Unable to load backup status": "Unable to load backup status", + "Recovery Key Mismatch": "Recovery Key Mismatch", + "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", + "Incorrect Recovery Passphrase": "Incorrect Recovery Passphrase", + "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.", + "Unable to restore backup": "Unable to restore backup", + "No backup found!": "No backup found!", + "Backup Restored": "Backup Restored", + "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", + "Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys", + "Enter Recovery Passphrase": "Enter Recovery Passphrase", + "Warning: you should only set up key backup from a trusted computer.": "Warning: you should only set up key backup from a trusted computer.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", + "Enter Recovery Key": "Enter Recovery Key", + "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", + "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", + "Private Chat": "Private Chat", + "Public Chat": "Public Chat", + "Custom": "Custom", + "Alias (optional)": "Alias (optional)", + "Reject invitation": "Reject invitation", + "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", + "Unable to reject invite": "Unable to reject invite", + "Resend": "Resend", + "Resend edit": "Resend edit", + "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", + "Resend removal": "Resend removal", + "Cancel Sending": "Cancel Sending", + "Forward Message": "Forward Message", + "Pin Message": "Pin Message", + "View Decrypted Source": "View Decrypted Source", + "Unhide Preview": "Unhide Preview", + "Share Permalink": "Share Permalink", + "Share Message": "Share Message", + "Source URL": "Source URL", + "Collapse Reply Thread": "Collapse Reply Thread", + "End-to-end encryption information": "End-to-end encryption information", + "Report Content": "Report Content", + "Failed to set Direct Message status of room": "Failed to set Direct Message status of room", + "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", + "Notification settings": "Notification settings", + "All messages (noisy)": "All messages (noisy)", + "All messages": "All messages", + "Mentions only": "Mentions only", + "Leave": "Leave", + "Forget": "Forget", + "Favourite": "Favourite", + "Low Priority": "Low Priority", + "Clear status": "Clear status", + "Update status": "Update status", + "Set status": "Set status", + "Set a new status...": "Set a new status...", + "View Community": "View Community", + "Hide": "Hide", + "Home": "Home", + "Sign in": "Sign in", + "Help": "Help", + "Reload": "Reload", + "Take picture": "Take picture", + "Remove for everyone": "Remove for everyone", + "Remove for me": "Remove for me", + "User Status": "User Status", + "powered by Matrix": "powered by Matrix", + "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", + "Country Dropdown": "Country Dropdown", + "Custom Server Options": "Custom Server Options", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.", + "To continue, please enter your password.": "To continue, please enter your password.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.", + "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", + "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", + "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", + "Please check your email to continue registration.": "Please check your email to continue registration.", + "Token incorrect": "Token incorrect", + "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", + "Please enter the code it contains:": "Please enter the code it contains:", + "Code": "Code", + "Submit": "Submit", + "Start authentication": "Start authentication", + "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", + "Your Modular server": "Your Modular server", + "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.", + "Server Name": "Server Name", + "The email field must not be blank.": "The email field must not be blank.", + "The username field must not be blank.": "The username field must not be blank.", + "The phone number field must not be blank.": "The phone number field must not be blank.", + "The password field must not be blank.": "The password field must not be blank.", + "Email": "Email", + "Username": "Username", + "Phone": "Phone", + "Not sure of your password? Set a new one": "Not sure of your password? Set a new one", + "Sign in with": "Sign in with", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", + "Use an email address to recover your account": "Use an email address to recover your account", + "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", + "Doesn't look like a valid email address": "Doesn't look like a valid email address", + "Enter password": "Enter password", + "Password is allowed, but unsafe": "Password is allowed, but unsafe", + "Nice, strong password!": "Nice, strong password!", + "Keep going...": "Keep going...", + "Passwords don't match": "Passwords don't match", + "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", + "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", + "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", + "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", + "Enter username": "Enter username", + "Email (optional)": "Email (optional)", + "Confirm": "Confirm", + "Phone (optional)": "Phone (optional)", + "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", + "Create your Matrix account on ": "Create your Matrix account on ", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.", + "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", + "Homeserver URL": "Homeserver URL", + "Enter your custom identity server URL What does this mean?": "Enter your custom identity server URL What does this mean?", + "Identity Server URL": "Identity Server URL", + "Other servers": "Other servers", + "Free": "Free", + "Join millions for free on the largest public server": "Join millions for free on the largest public server", + "Premium": "Premium", + "Premium hosting for organisations Learn more": "Premium hosting for organisations Learn more", + "Find other public servers or use a custom server": "Find other public servers or use a custom server", + "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", + "Sign in to your Matrix account on ": "Sign in to your Matrix account on ", + "Sorry, your browser is not able to run Riot.": "Sorry, your browser is not able to run Riot.", + "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", + "Please install Chrome, Firefox, or Safari for the best experience.": "Please install Chrome, Firefox, or Safari for the best experience.", + "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!", + "I understand the risks and wish to continue": "I understand the risks and wish to continue", + "Couldn't load page": "Couldn't load page", + "You must register to use this functionality": "You must register to use this functionality", + "You must join the room to see its files": "You must join the room to see its files", + "There are no visible files in this room": "There are no visible files in this room", + "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n": "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n", + "Add rooms to the community summary": "Add rooms to the community summary", + "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", + "Add to summary": "Add to summary", + "Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:", + "Add a Room": "Add a Room", + "Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s", + "The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.", + "Add users to the community summary": "Add users to the community summary", + "Who would you like to add to this summary?": "Who would you like to add to this summary?", + "Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:", + "Add a User": "Add a User", + "Failed to remove a user from the summary of %(groupId)s": "Failed to remove a user from the summary of %(groupId)s", + "The user '%(displayName)s' could not be removed from the summary.": "The user '%(displayName)s' could not be removed from the summary.", + "Failed to upload image": "Failed to upload image", + "Failed to update community": "Failed to update community", + "Unable to accept invite": "Unable to accept invite", + "Unable to join community": "Unable to join community", + "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.", + "Leave Community": "Leave Community", + "Leave %(groupName)s?": "Leave %(groupName)s?", + "Unable to leave community": "Unable to leave community", + "Community Settings": "Community Settings", + "Want more than a community? Get your own server": "Want more than a community? Get your own server", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", + "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", + "Featured Rooms:": "Featured Rooms:", + "Featured Users:": "Featured Users:", + "%(inviter)s has invited you to join this community": "%(inviter)s has invited you to join this community", + "Join this community": "Join this community", + "Leave this community": "Leave this community", + "You are an administrator of this community": "You are an administrator of this community", + "You are a member of this community": "You are a member of this community", + "Who can join this community?": "Who can join this community?", + "Everyone": "Everyone", + "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!", + "Long Description (HTML)": "Long Description (HTML)", + "Upload avatar": "Upload avatar", + "Description": "Description", + "Community %(groupId)s not found": "Community %(groupId)s not found", + "This homeserver does not support communities": "This homeserver does not support communities", + "Failed to load %(groupId)s": "Failed to load %(groupId)s", + "Explore": "Explore", + "Filter": "Filter", + "Filter rooms…": "Filter rooms…", + "Failed to reject invitation": "Failed to reject invitation", + "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", + "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", + "Failed to leave room": "Failed to leave room", + "Can't leave Server Notices room": "Can't leave Server Notices room", + "This room is used for important messages from the Homeserver, so you cannot leave it.": "This room is used for important messages from the Homeserver, so you cannot leave it.", + "Signed Out": "Signed Out", + "For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.", + "Terms and Conditions": "Terms and Conditions", + "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.", + "Review terms and conditions": "Review terms and conditions", + "Old cryptography data detected": "Old cryptography data detected", + "Data from an older version of Riot 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.": "Data from an older version of Riot 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.", + "Verification Request": "Verification Request", + "Logout": "Logout", + "%(creator)s created and configured the room.": "%(creator)s created and configured the room.", + "Your Communities": "Your Communities", + "Did you know: you can use communities to filter your Riot.im experience!": "Did you know: you can use communities to filter your Riot.im experience!", + "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.", + "Error whilst fetching joined communities": "Error whilst fetching joined communities", + "Create a new community": "Create a new community", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.", + "You have no visible notifications": "You have no visible notifications", + "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", + "Riot failed to get the public room list.": "Riot failed to get the public room list.", + "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.", + "Delete the room alias %(alias)s and remove %(name)s from the directory?": "Delete the room alias %(alias)s and remove %(name)s from the directory?", + "Remove %(name)s from the directory?": "Remove %(name)s from the directory?", + "Remove from Directory": "Remove from Directory", + "remove %(name)s from the directory.": "remove %(name)s from the directory.", + "delete the alias.": "delete the alias.", + "The server may be unavailable or overloaded": "The server may be unavailable or overloaded", + "Unable to join network": "Unable to join network", + "Riot does not know how to join a room on this network": "Riot does not know how to join a room on this network", + "Room not found": "Room not found", + "Couldn't find a matching Matrix room": "Couldn't find a matching Matrix room", + "Fetching third party location failed": "Fetching third party location failed", + "Unable to look up room ID from server": "Unable to look up room ID from server", + "Preview": "Preview", + "View": "View", + "Find a room…": "Find a room…", + "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", + "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", + "Explore rooms": "Explore rooms", + "Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", + "Show devices, send anyway or cancel.": "Show devices, send anyway or cancel.", + "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", + "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", + "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", + "%(count)s of your messages have not been sent.|other": "Some of your messages have not been sent.", + "%(count)s of your messages have not been sent.|one": "Your message was not sent.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Resend message or cancel message now.", + "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", + "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", + "Active call": "Active call", + "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", + "Jump to first unread room.": "Jump to first unread room.", + "Jump to first invite.": "Jump to first invite.", + "Add room": "Add room", + "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", + "You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?", + "Search failed": "Search failed", + "Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(", + "No more results": "No more results", + "Unknown room %(roomId)s": "Unknown room %(roomId)s", + "Room": "Room", + "Failed to reject invite": "Failed to reject invite", + "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", + "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", + "Fill screen": "Fill screen", + "Click to unmute video": "Click to unmute video", + "Click to mute video": "Click to mute video", + "Click to unmute audio": "Click to unmute audio", + "Click to mute audio": "Click to mute audio", + "Clear filter": "Clear filter", + "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", + "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", + "Failed to load timeline position": "Failed to load timeline position", + " (1/%(totalCount)s)": " (1/%(totalCount)s)", + "Guest": "Guest", + "Your profile": "Your profile", + "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", + "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", + "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", + "Could not load user profile": "Could not load user profile", + "Complete security": "Complete security", + "Verify this session to grant it access to encrypted messages.": "Verify this session to grant it access to encrypted messages.", + "Start": "Start", + "Session verified": "Session verified", + "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.", + "Done": "Done", + "Without completing security on this device, it won’t have access to encrypted messages.": "Without completing security on this device, it won’t have access to encrypted messages.", + "Go Back": "Go Back", + "Failed to send email": "Failed to send email", + "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", + "A new password must be entered.": "A new password must be entered.", + "New passwords must match each other.": "New passwords must match each other.", + "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.", + "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", + "Your Matrix account on ": "Your Matrix account on ", + "No identity server is configured: add one in server settings to reset your password.": "No identity server is configured: add one in server settings to reset your password.", + "Sign in instead": "Sign in instead", + "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.", + "Send Reset Email": "Send Reset Email", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", + "I have verified my email address": "I have verified my email address", + "Your password has been reset.": "Your password has been reset.", + "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.", + "Return to login screen": "Return to login screen", + "Set a new password": "Set a new password", + "Invalid homeserver discovery response": "Invalid homeserver discovery response", + "Failed to get autodiscovery configuration from server": "Failed to get autodiscovery configuration from server", + "Invalid base_url for m.homeserver": "Invalid base_url for m.homeserver", + "Homeserver URL does not appear to be a valid Matrix homeserver": "Homeserver URL does not appear to be a valid Matrix homeserver", + "Invalid identity server discovery response": "Invalid identity server discovery response", + "Invalid base_url for m.identity_server": "Invalid base_url for m.identity_server", + "Identity server URL does not appear to be a valid identity server": "Identity server URL does not appear to be a valid identity server", + "General failure": "General failure", + "This homeserver does not support login using email address.": "This homeserver does not support login using email address.", + "Please contact your service administrator to continue using this service.": "Please contact your service administrator to continue using this service.", + "This account has been deactivated.": "This account has been deactivated.", + "Incorrect username and/or password.": "Incorrect username and/or password.", + "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", + "Failed to perform homeserver discovery": "Failed to perform homeserver discovery", + "The phone number entered looks invalid": "The phone number entered looks invalid", + "This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.", + "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", + "Sign in with single sign-on": "Sign in with single sign-on", + "Create account": "Create account", + "Failed to fetch avatar URL": "Failed to fetch avatar URL", + "Set a display name:": "Set a display name:", + "Upload an avatar:": "Upload an avatar:", + "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", + "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", + "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", + "Continue with previous account": "Continue with previous account", + "Log in to your new account.": "Log in to your new account.", + "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", + "Registration Successful": "Registration Successful", + "Create your account": "Create your account", + "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", + "Failed to re-authenticate": "Failed to re-authenticate", + "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.": "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.", + "Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.", + "Forgotten your password?": "Forgotten your password?", + "Sign in and regain access to your account.": "Sign in and regain access to your account.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "You cannot sign in to your account. Please contact your homeserver admin for more information.", + "You're signed out": "You're signed out", + "Clear personal data": "Clear personal data", + "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.", + "Commands": "Commands", + "Command Autocomplete": "Command Autocomplete", + "Community Autocomplete": "Community Autocomplete", + "Results from DuckDuckGo": "Results from DuckDuckGo", + "DuckDuckGo Results": "DuckDuckGo Results", + "Emoji": "Emoji", + "Emoji Autocomplete": "Emoji Autocomplete", + "Notify the whole room": "Notify the whole room", + "Room Notification": "Room Notification", + "Notification Autocomplete": "Notification Autocomplete", + "Room Autocomplete": "Room Autocomplete", + "Users": "Users", + "User Autocomplete": "User Autocomplete", + "unknown device": "unknown device", + "NOT verified": "NOT verified", + "Blacklisted": "Blacklisted", + "verified": "verified", + "Verification": "Verification", + "Ed25519 fingerprint": "Ed25519 fingerprint", + "User ID": "User ID", + "Curve25519 identity key": "Curve25519 identity key", + "none": "none", + "Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key", + "Algorithm": "Algorithm", + "unencrypted": "unencrypted", + "Decryption error": "Decryption error", + "Session ID": "Session ID", + "Event information": "Event information", + "Sender device information": "Sender device information", + "Passphrases must match": "Passphrases must match", + "Passphrase must not be empty": "Passphrase must not be empty", + "Export room keys": "Export room keys", + "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.", + "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.", + "Enter passphrase": "Enter passphrase", + "Confirm passphrase": "Confirm passphrase", + "Export": "Export", + "Import room keys": "Import room keys", + "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", + "File to import": "File to import", + "Import": "Import", + "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.", + "Restore": "Restore", + "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.", + "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", + "Warning: You should only set up secret storage from a trusted computer.": "Warning: You should only set up secret storage from a trusted computer.", + "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.", + "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Enter a passphrase...": "Enter a passphrase...", + "Set up with a recovery key": "Set up with a recovery key", + "That matches!": "That matches!", + "That doesn't match.": "That doesn't match.", + "Go back to set it again.": "Go back to set it again.", + "Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.", + "Repeat your passphrase...": "Repeat your passphrase...", + "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.": "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.", + "As a safety net, you can use it to restore your access to encrypted messages.": "As a safety net, you can use it to restore your access to encrypted messages.", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.", + "Keep your recovery key somewhere very secure, like a password manager (or a safe).": "Keep your recovery key somewhere very secure, like a password manager (or a safe).", + "Your Recovery Key": "Your Recovery Key", + "Copy to clipboard": "Copy to clipboard", + "Download": "Download", + "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", + "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", + "Print it and store it somewhere safe": "Print it and store it somewhere safe", + "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", + "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", + "Your access to encrypted messages is now protected.": "Your access to encrypted messages is now protected.", + "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.", + "Set up secret storage": "Set up secret storage", + "Restore your Key Backup": "Restore your Key Backup", + "Migrate from Key Backup": "Migrate from Key Backup", + "Secure your encrypted messages with a passphrase": "Secure your encrypted messages with a passphrase", + "Confirm your passphrase": "Confirm your passphrase", + "Recovery key": "Recovery key", + "Keep it safe": "Keep it safe", + "Storing secrets...": "Storing secrets...", + "Success!": "Success!", + "Unable to set up secret storage": "Unable to set up secret storage", + "Retry": "Retry", + "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", + "Set up with a Recovery Key": "Set up with a Recovery Key", + "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.", + "As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.", + "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.", + "Set up Secure Message Recovery": "Set up Secure Message Recovery", + "Secure your backup with a passphrase": "Secure your backup with a passphrase", + "Starting backup...": "Starting backup...", + "Create Key Backup": "Create Key Backup", + "Unable to create key backup": "Unable to create key backup", + "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", + "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", + "Set up": "Set up", + "Don't ask again": "Don't ask again", + "New Recovery Method": "New Recovery Method", + "A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", + "This device is encrypting history using the new recovery method.": "This device is encrypting history using the new recovery method.", + "Go to Settings": "Go to Settings", + "Set up Secure Messages": "Set up Secure Messages", + "Recovery Method Removed": "Recovery Method Removed", + "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.", + "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.", + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", + "Failed to set direct chat tag": "Failed to set direct chat tag", + "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" +} From c5ecd83bc18bc59382be69cff459dfc81347c1e3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 12:34:50 +0000 Subject: [PATCH 52/65] remove .orig file --- src/i18n/strings/en_EN.json.orig | 2040 ------------------------------ 1 file changed, 2040 deletions(-) delete mode 100644 src/i18n/strings/en_EN.json.orig diff --git a/src/i18n/strings/en_EN.json.orig b/src/i18n/strings/en_EN.json.orig deleted file mode 100644 index f0eab6b12d..0000000000 --- a/src/i18n/strings/en_EN.json.orig +++ /dev/null @@ -1,2040 +0,0 @@ -{ - "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", - "Add Email Address": "Add Email Address", - "Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email", - "Add Phone Number": "Add Phone Number", - "The platform you're on": "The platform you're on", - "The version of Riot.im": "The version of Riot.im", - "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)", - "Your language of choice": "Your language of choice", - "Which officially provided instance you are using, if any": "Which officially provided instance you are using, if any", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "Whether or not you're using the Richtext mode of the Rich Text Editor", - "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)", - "Your homeserver's URL": "Your homeserver's URL", - "Your identity server's URL": "Your identity server's URL", - "e.g. %(exampleValue)s": "e.g. %(exampleValue)s", - "Every page you use in the app": "Every page you use in the app", - "e.g. ": "e.g. ", - "Your User Agent": "Your User Agent", - "Your device resolution": "Your device resolution", - "Analytics": "Analytics", - "The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:", - "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.", - "Call Failed": "Call Failed", - "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", - "Review Devices": "Review Devices", - "Call Anyway": "Call Anyway", - "Answer Anyway": "Answer Anyway", - "Call": "Call", - "Answer": "Answer", - "Call Timeout": "Call Timeout", - "The remote side failed to pick up": "The remote side failed to pick up", - "Call failed due to misconfigured server": "Call failed due to misconfigured server", - "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.", - "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.", - "Try using turn.matrix.org": "Try using turn.matrix.org", - "OK": "OK", - "Unable to capture screen": "Unable to capture screen", - "Existing Call": "Existing Call", - "You are already in a call.": "You are already in a call.", - "VoIP is unsupported": "VoIP is unsupported", - "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", - "You cannot place a call with yourself.": "You cannot place a call with yourself.", - "Could not connect to the integration server": "Could not connect to the integration server", - "A conference call could not be started because the integrations server is not available": "A conference call could not be started because the integrations server is not available", - "Call in Progress": "Call in Progress", - "A call is currently being placed!": "A call is currently being placed!", - "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", - "Replying With Files": "Replying With Files", - "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "At this time it is not possible to reply with a file. Would you like to upload this file without replying?", - "Continue": "Continue", - "The file '%(fileName)s' failed to upload.": "The file '%(fileName)s' failed to upload.", - "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads", - "Upload Failed": "Upload Failed", - "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", - "The server does not support the room version specified.": "The server does not support the room version specified.", - "Failure to create room": "Failure to create room", - "Send cross-signing keys to homeserver": "Send cross-signing keys to homeserver", - "Send anyway": "Send anyway", - "Send": "Send", - "Sun": "Sun", - "Mon": "Mon", - "Tue": "Tue", - "Wed": "Wed", - "Thu": "Thu", - "Fri": "Fri", - "Sat": "Sat", - "Jan": "Jan", - "Feb": "Feb", - "Mar": "Mar", - "Apr": "Apr", - "May": "May", - "Jun": "Jun", - "Jul": "Jul", - "Aug": "Aug", - "Sep": "Sep", - "Oct": "Oct", - "Nov": "Nov", - "Dec": "Dec", - "PM": "PM", - "AM": "AM", - "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", - "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)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 %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", - "New Session": "New Session", - "Who would you like to add to this community?": "Who would you like to add to this community?", - "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", - "Invite new community members": "Invite new community members", - "Name or Matrix ID": "Name or Matrix ID", - "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?", - "Show these rooms to non-members on the community page and room list?": "Show these rooms to non-members on the community page and room list?", - "Add rooms to the community": "Add rooms to the community", - "Room name or alias": "Room name or alias", - "Add to community": "Add to community", - "Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:", - "Failed to invite users to community": "Failed to invite users to community", - "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", - "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", - "Unnamed Room": "Unnamed Room", - "Identity server has no terms of service": "Identity server has no terms of service", - "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", - "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", - "Trust": "Trust", - "Error": "Error", - "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", - "Dismiss": "Dismiss", - "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", - "Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again", - "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.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.", - "Registration Required": "Registration Required", - "You need to register to do this. Would you like to register now?": "You need to register to do this. Would you like to register now?", - "Register": "Register", - "Default": "Default", - "Restricted": "Restricted", - "Moderator": "Moderator", - "Admin": "Admin", - "Custom (%(level)s)": "Custom (%(level)s)", - "Start a chat": "Start a chat", - "Who would you like to communicate with?": "Who would you like to communicate with?", - "Email, name or Matrix ID": "Email, name or Matrix ID", - "Start Chat": "Start Chat", - "Invite new room members": "Invite new room members", - "Send Invites": "Send Invites", - "Failed to start chat": "Failed to start chat", - "Operation failed": "Operation failed", - "Failed to invite": "Failed to invite", - "Failed to invite users to the room:": "Failed to invite users to the room:", - "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", - "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.", - "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.", - "You are not in this room.": "You are not in this room.", - "You do not have permission to do that in this room.": "You do not have permission to do that in this room.", - "Missing room_id in request": "Missing room_id in request", - "Room %(roomId)s not visible": "Room %(roomId)s not visible", - "Missing user_id in request": "Missing user_id in request", - "Messages": "Messages", - "Actions": "Actions", - "Advanced": "Advanced", - "Other": "Other", - "Usage": "Usage", - "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message", - "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", - "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", - "/ddg is not a command": "/ddg is not a command", - "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", - "Upgrades a room to a new version": "Upgrades a room to a new version", - "You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.", - "Error upgrading room": "Error upgrading room", - "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", - "Changes your display nickname": "Changes your display nickname", - "Changes your display nickname in the current room only": "Changes your display nickname in the current room only", - "Changes the avatar of the current room": "Changes the avatar of the current room", - "Changes your avatar in this current room only": "Changes your avatar in this current room only", - "Changes your avatar in all rooms": "Changes your avatar in all rooms", - "Gets or sets the room topic": "Gets or sets the room topic", - "This room has no topic.": "This room has no topic.", - "Sets the room name": "Sets the room name", - "Invites user with given id to current room": "Invites user with given id to current room", - "Use an identity server": "Use an identity server", - "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.", - "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", - "Joins room with given alias": "Joins room with given alias", - "Leave room": "Leave room", - "Unrecognised room alias:": "Unrecognised room alias:", - "Kicks user with given id": "Kicks user with given id", - "Bans user with given id": "Bans user with given id", - "Unbans user with given ID": "Unbans user with given 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": "You are now ignoring %(userId)s", - "Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward", - "Unignored user": "Unignored user", - "You are no longer ignoring %(userId)s": "You are no longer ignoring %(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", - "Opens the Developer Tools dialog": "Opens the Developer Tools dialog", - "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": "Please supply a https:// or http:// widget URL", - "You cannot modify widgets in this room.": "You cannot modify widgets in this room.", - "Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple", - "Unknown (user, device) pair:": "Unknown (user, device) pair:", - "Device already verified!": "Device already verified!", - "WARNING: Device already verified, but keys do NOT MATCH!": "WARNING: Device already verified, but keys do NOT MATCH!", - "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!", - "Verified key": "Verified key", - "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", - "Displays action": "Displays action", - "Forces the current outbound group session in an encrypted room to be discarded": "Forces the current outbound group session in an encrypted room to be discarded", - "Sends the given message coloured as a rainbow": "Sends the given message coloured as a rainbow", - "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", - "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", - "Unrecognised command:": "Unrecognised command:", - "Reason": "Reason", - "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", - "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", - "%(senderName)s requested a VoIP conference.": "%(senderName)s requested a VoIP conference.", - "%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.", - "%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.", - "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.", - "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.", - "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).", - "%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.", - "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", - "%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.", - "%(senderName)s made no change.": "%(senderName)s made no change.", - "VoIP conference started.": "VoIP conference started.", - "%(targetName)s joined the room.": "%(targetName)s joined the room.", - "VoIP conference finished.": "VoIP conference finished.", - "%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.", - "%(targetName)s left the room.": "%(targetName)s left the room.", - "%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.", - "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.", - "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.", - "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".", - "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.", - "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.", - "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgraded this room.", - "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s made the room public to whoever knows the link.", - "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s made the room invite only.", - "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s changed the join rule to %(rule)s", - "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s has allowed guests to join the room.", - "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s has prevented guests from joining the room.", - "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s changed guest access to %(rule)s", - "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.", - "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", - "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", - "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", - "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", - "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", - "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", - "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", - "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", - "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", - "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", - "Someone": "Someone", - "(not supported by this browser)": "(not supported by this browser)", - "%(senderName)s answered the call.": "%(senderName)s answered the call.", - "(could not connect media)": "(could not connect media)", - "(no answer)": "(no answer)", - "(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)", - "%(senderName)s ended the call.": "%(senderName)s ended the call.", - "%(senderName)s placed a voice call.": "%(senderName)s placed a voice call.", - "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s placed a voice call. (not supported by this browser)", - "%(senderName)s placed a video call.": "%(senderName)s placed a video call.", - "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s placed a video call. (not supported by this browser)", - "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.", - "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.", - "%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.", - "%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.", - "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", - "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).", - "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", - "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", - "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", - "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", - "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", - "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", - "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s removed the rule banning users matching %(glob)s", - "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s removed the rule banning rooms matching %(glob)s", - "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s removed the rule banning servers matching %(glob)s", - "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s removed a ban rule matching %(glob)s", - "%(senderName)s updated an invalid ban rule": "%(senderName)s updated an invalid ban rule", - "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s", - "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s", - "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s", - "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s", - "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s", - "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s", - "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s", - "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s created a ban rule matching %(glob)s for %(reason)s", - "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "Light theme": "Light theme", - "Dark theme": "Dark theme", - "%(displayName)s is typing …": "%(displayName)s is typing …", - "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", - "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", - "%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …", - "Cannot reach homeserver": "Cannot reach homeserver", - "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", - "Your Riot is misconfigured": "Your Riot is misconfigured", - "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Ask your Riot admin to check your config for incorrect or duplicate entries.", - "Cannot reach identity server": "Cannot reach identity server", - "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", - "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", - "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", - "No homeserver URL provided": "No homeserver URL provided", - "Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration", - "Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration", - "The message you are trying to send is too large.": "The message you are trying to send is too large.", - "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", - "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", - "Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.", - "Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...", - "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", - "%(items)s and %(count)s others|one": "%(items)s and one other", - "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", - "a few seconds ago": "a few seconds ago", - "about a minute ago": "about a minute ago", - "%(num)s minutes ago": "%(num)s minutes ago", - "about an hour ago": "about an hour ago", - "%(num)s hours ago": "%(num)s hours ago", - "about a day ago": "about a day ago", - "%(num)s days ago": "%(num)s days ago", - "a few seconds from now": "a few seconds from now", - "about a minute from now": "about a minute from now", - "%(num)s minutes from now": "%(num)s minutes from now", - "about an hour from now": "about an hour from now", - "%(num)s hours from now": "%(num)s hours from now", - "about a day from now": "about a day from now", - "%(num)s days from now": "%(num)s days from now", - "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", - "Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions", - "Not a valid Riot keyfile": "Not a valid Riot keyfile", - "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", - "Unrecognised address": "Unrecognised address", - "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": "User %(userId)s is already in the room", - "User %(user_id)s does not exist": "User %(user_id)s does not exist", - "User %(user_id)s may or may not exist": "User %(user_id)s may or may not exist", - "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", - "No need for symbols, digits, or uppercase letters": "No need for symbols, digits, or uppercase letters", - "Use a longer keyboard pattern with more turns": "Use a longer keyboard pattern with more turns", - "Avoid repeated words and characters": "Avoid repeated words and characters", - "Avoid sequences": "Avoid sequences", - "Avoid recent years": "Avoid recent years", - "Avoid years that are associated with you": "Avoid years that are associated with you", - "Avoid dates and years that are associated with you": "Avoid dates and years that are associated with you", - "Capitalization doesn't help very much": "Capitalization doesn't help very much", - "All-uppercase is almost as easy to guess as all-lowercase": "All-uppercase is almost as easy to guess as all-lowercase", - "Reversed words aren't much harder to guess": "Reversed words aren't much harder to guess", - "Predictable substitutions like '@' instead of 'a' don't help very much": "Predictable substitutions like '@' instead of 'a' don't help very much", - "Add another word or two. Uncommon words are better.": "Add another word or two. Uncommon words are better.", - "Repeats like \"aaa\" are easy to guess": "Repeats like \"aaa\" are easy to guess", - "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"", - "Sequences like abc or 6543 are easy to guess": "Sequences like abc or 6543 are easy to guess", - "Recent years are easy to guess": "Recent years are easy to guess", - "Dates are often easy to guess": "Dates are often easy to guess", - "This is a top-10 common password": "This is a top-10 common password", - "This is a top-100 common password": "This is a top-100 common password", - "This is a very common password": "This is a very common password", - "This is similar to a commonly used password": "This is similar to a commonly used password", - "A word by itself is easy to guess": "A word by itself is easy to guess", - "Names and surnames by themselves are easy to guess": "Names and surnames by themselves are easy to guess", - "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", - "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", - "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", - "There was an error joining the room": "There was an error joining the room", - "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", - "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", - "Failed to join room": "Failed to join room", - "Message Pinning": "Message Pinning", - "Custom user status messages": "Custom user status messages", - "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", - "Render simple counters in room header": "Render simple counters in room header", - "Multiple integration managers": "Multiple integration managers", - "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", - "New invite dialog": "New invite dialog", - "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", - "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", - "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", - "Show info about bridges in room settings": "Show info about bridges in room settings", - "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", - "Use compact timeline layout": "Use compact timeline layout", - "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", - "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", - "Always show message timestamps": "Always show message timestamps", - "Autoplay GIFs and videos": "Autoplay GIFs and videos", - "Always show encryption icons": "Always show encryption icons", - "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms", - "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", - "Show avatars in user and room mentions": "Show avatars in user and room mentions", - "Enable big emoji in chat": "Enable big emoji in chat", - "Send typing notifications": "Send typing notifications", - "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", - "Mirror local video feed": "Mirror local video feed", - "Enable Community Filter Panel": "Enable Community Filter Panel", - "Match system theme": "Match system theme", - "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", - "Send analytics data": "Send analytics data", - "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", - "Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device", - "Enable inline URL previews by default": "Enable inline URL previews by default", - "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", - "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", - "Room Colour": "Room Colour", - "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", - "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", - "Show developer tools": "Show developer tools", - "Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent", - "Show recently visited rooms above the room list": "Show recently visited rooms above the room list", - "Show hidden events in timeline": "Show hidden events in timeline", - "Low bandwidth mode": "Low bandwidth mode", - "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)", - "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", - "Show previews/thumbnails for images": "Show previews/thumbnails for images", - "Collecting app version information": "Collecting app version information", - "Collecting logs": "Collecting logs", - "Uploading report": "Uploading report", - "Waiting for response from server": "Waiting for response from server", - "Messages containing my display name": "Messages containing my display name", - "Messages containing my username": "Messages containing my username", - "Messages containing @room": "Messages containing @room", - "Messages in one-to-one chats": "Messages in one-to-one chats", - "Encrypted messages in one-to-one chats": "Encrypted messages in one-to-one chats", - "Messages in group chats": "Messages in group chats", - "Encrypted messages in group chats": "Encrypted messages in group chats", - "When I'm invited to a room": "When I'm invited to a room", - "Call invitation": "Call invitation", - "Messages sent by bot": "Messages sent by bot", - "When rooms are upgraded": "When rooms are upgraded", - "My Ban List": "My Ban List", - "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!", - "Active call (%(roomName)s)": "Active call (%(roomName)s)", - "unknown caller": "unknown caller", - "Incoming voice call from %(name)s": "Incoming voice call from %(name)s", - "Incoming video call from %(name)s": "Incoming video call from %(name)s", - "Incoming call from %(name)s": "Incoming call from %(name)s", - "Decline": "Decline", - "Accept": "Accept", - "The other party cancelled the verification.": "The other party cancelled the verification.", - "Verified!": "Verified!", - "You've successfully verified this user.": "You've successfully verified this user.", - "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.", - "Got It": "Got It", - "Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.", - "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.", - "Unable to find a supported verification method.": "Unable to find a supported verification method.", - "Cancel": "Cancel", - "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.", - "Dog": "Dog", - "Cat": "Cat", - "Lion": "Lion", - "Horse": "Horse", - "Unicorn": "Unicorn", - "Pig": "Pig", - "Elephant": "Elephant", - "Rabbit": "Rabbit", - "Panda": "Panda", - "Rooster": "Rooster", - "Penguin": "Penguin", - "Turtle": "Turtle", - "Fish": "Fish", - "Octopus": "Octopus", - "Butterfly": "Butterfly", - "Flower": "Flower", - "Tree": "Tree", - "Cactus": "Cactus", - "Mushroom": "Mushroom", - "Globe": "Globe", - "Moon": "Moon", - "Cloud": "Cloud", - "Fire": "Fire", - "Banana": "Banana", - "Apple": "Apple", - "Strawberry": "Strawberry", - "Corn": "Corn", - "Pizza": "Pizza", - "Cake": "Cake", - "Heart": "Heart", - "Smiley": "Smiley", - "Robot": "Robot", - "Hat": "Hat", - "Glasses": "Glasses", - "Spanner": "Spanner", - "Santa": "Santa", - "Thumbs up": "Thumbs up", - "Umbrella": "Umbrella", - "Hourglass": "Hourglass", - "Clock": "Clock", - "Gift": "Gift", - "Light bulb": "Light bulb", - "Book": "Book", - "Pencil": "Pencil", - "Paperclip": "Paperclip", - "Scissors": "Scissors", - "Lock": "Lock", - "Key": "Key", - "Hammer": "Hammer", - "Telephone": "Telephone", - "Flag": "Flag", - "Train": "Train", - "Bicycle": "Bicycle", - "Aeroplane": "Aeroplane", - "Rocket": "Rocket", - "Trophy": "Trophy", - "Ball": "Ball", - "Guitar": "Guitar", - "Trumpet": "Trumpet", - "Bell": "Bell", - "Anchor": "Anchor", - "Headphones": "Headphones", - "Folder": "Folder", - "Pin": "Pin", - "Other users may not trust it": "Other users may not trust it", - "Later": "Later", - "Verify": "Verify", - "Decline (%(counter)s)": "Decline (%(counter)s)", - "Accept to continue:": "Accept to continue:", - "Upload": "Upload", - "Remove": "Remove", - "Failed to upload profile picture!": "Failed to upload profile picture!", - "Upload new:": "Upload new:", - "No display name": "No display name", - "New passwords don't match": "New passwords don't match", - "Passwords can't be empty": "Passwords can't be empty", - "Warning!": "Warning!", - "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", - "Export E2E room keys": "Export E2E room keys", - "Do you want to set an email address?": "Do you want to set an email address?", - "Current password": "Current password", - "Password": "Password", - "New Password": "New Password", - "Confirm password": "Confirm password", - "Change Password": "Change Password", - "Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.", - "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.", - "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.", - "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", - "Cross-signing public keys:": "Cross-signing public keys:", - "on device": "on device", - "not found": "not found", - "Cross-signing private keys:": "Cross-signing private keys:", - "in secret storage": "in secret storage", - "Secret storage public key:": "Secret storage public key:", - "in account data": "in account data", - "Your homeserver does not support device management.": "Your homeserver does not support device management.", - "Unable to load device list": "Unable to load device list", - "Authentication": "Authentication", - "Delete %(count)s devices|other": "Delete %(count)s devices", - "Delete %(count)s devices|one": "Delete device", - "ID": "ID", - "Public Name": "Public Name", - "Last seen": "Last seen", - "Failed to set display name": "Failed to set display name", - "Disable Notifications": "Disable Notifications", - "Enable Notifications": "Enable Notifications", - "Connecting to integration manager...": "Connecting 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.", - "Delete Backup": "Delete Backup", - "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", - "Unable to load key backup status": "Unable to load key backup status", - "Restore from Backup": "Restore from Backup", - "This device is backing up your keys. ": "This device is backing up your keys. ", - "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", - "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.", - "Connect this device to Key Backup": "Connect this device to Key Backup", - "not stored": "not stored", - "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", - "All keys backed up": "All keys backed up", - "Backup has a valid signature from this user": "Backup has a valid signature from this user", - "Backup has a invalid signature from this user": "Backup has a invalid signature from this user", - "Backup has a signature from unknown user with ID %(deviceId)s": "Backup has a signature from unknown user with ID %(deviceId)s", - "Backup has a signature from unknown device with ID %(deviceId)s": "Backup has a signature from unknown device with ID %(deviceId)s", - "Backup has a valid signature from this device": "Backup has a valid signature from this device", - "Backup has an invalid signature from this device": "Backup has an invalid signature from this device", - "Backup has a valid signature from verified device ": "Backup has a valid signature from verified device ", - "Backup has a valid signature from unverified device ": "Backup has a valid signature from unverified device ", - "Backup has an invalid signature from verified device ": "Backup has an invalid signature from verified device ", - "Backup has an invalid signature from unverified device ": "Backup has an invalid signature from unverified device ", - "Backup is not signed by any of your devices": "Backup is not signed by any of your devices", - "This backup is trusted because it has been restored on this device": "This backup is trusted because it has been restored on this device", - "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.": "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.", - "Backup version: ": "Backup version: ", - "Algorithm: ": "Algorithm: ", - "Backup key stored: ": "Backup key stored: ", - "Your keys are not being backed up from this device.": "Your keys are not being backed up from this device.", - "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", - "Start using Key Backup": "Start using Key Backup", - "Error saving email notification preferences": "Error saving email notification preferences", - "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.", - "Keywords": "Keywords", - "Enter keywords separated by a comma:": "Enter keywords separated by a comma:", - "Failed to change settings": "Failed to change settings", - "Can't update user notification settings": "Can't update user notification settings", - "Failed to update keywords": "Failed to update keywords", - "Messages containing keywords": "Messages containing keywords", - "Notify for all other messages/rooms": "Notify for all other messages/rooms", - "Notify me for anything else": "Notify me for anything else", - "Enable notifications for this account": "Enable notifications for this account", - "Clear notifications": "Clear notifications", - "All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.", - "Add an email address to configure email notifications": "Add an email address to configure email notifications", - "Enable email notifications": "Enable email notifications", - "Notifications on the following keywords follow rules which can’t be displayed here:": "Notifications on the following keywords follow rules which can’t be displayed here:", - "Unable to fetch notification target list": "Unable to fetch notification target list", - "Notification targets": "Notification targets", - "Advanced notification settings": "Advanced notification settings", - "There are advanced notifications which are not shown here": "There are advanced notifications which are not shown here", - "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply", - "Enable desktop notifications for this device": "Enable desktop notifications for this device", - "Show message in desktop notification": "Show message in desktop notification", - "Enable audible notifications for this device": "Enable audible notifications for this device", - "Off": "Off", - "On": "On", - "Noisy": "Noisy", - "Upgrade to your own domain": "Upgrade to your own domain", - "Display Name": "Display Name", - "Profile picture": "Profile picture", - "Save": "Save", - "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", - "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", - "Could not connect to Identity Server": "Could not connect to Identity Server", - "Checking server": "Checking server", - "Change identity server": "Change identity server", - "Disconnect from the identity server and connect to instead?": "Disconnect from the identity server and connect to instead?", - "Terms of service not accepted or the identity server is invalid.": "Terms of service not accepted or the identity server is invalid.", - "The identity server you have chosen does not have any terms of service.": "The identity server you have chosen does not have any terms of service.", - "Disconnect identity server": "Disconnect identity server", - "Disconnect from the identity server ?": "Disconnect from the identity server ?", - "Disconnect": "Disconnect", - "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.", - "You should:": "You should:", - "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "check your browser plugins for anything that might block the identity server (such as Privacy Badger)", - "contact the administrators of identity server ": "contact the administrators of identity server ", - "wait and try again later": "wait and try again later", - "Disconnect anyway": "Disconnect anyway", - "You are still sharing your personal data on the identity server .": "You are still sharing your personal data on the identity server .", - "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.", - "Go back": "Go back", - "Identity Server (%(server)s)": "Identity Server (%(server)s)", - "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.", - "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.", - "Identity Server": "Identity Server", - "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.", - "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.", - "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.", - "Do not use an identity server": "Do not use an identity server", - "Enter a new identity server": "Enter a new identity server", - "Change": "Change", - "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.", - "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.", - "Manage integrations": "Manage integrations", - "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.", - "Flair": "Flair", - "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", - "Success": "Success", - "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them", - "Profile": "Profile", - "Email addresses": "Email addresses", - "Phone numbers": "Phone numbers", - "Account": "Account", - "Set a new account password...": "Set a new account password...", - "Language and region": "Language and region", - "Theme": "Theme", - "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", - "Account management": "Account management", - "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", - "Deactivate Account": "Deactivate Account", - "Warning": "Warning", - "General": "General", - "Discovery": "Discovery", - "Deactivate account": "Deactivate account", - "Legal": "Legal", - "Credits": "Credits", - "For help with using Riot, click here.": "For help with using Riot, click here.", - "For help with using Riot, click here or start a chat with our bot using the button below.": "For help with using Riot, click here or start a chat with our bot using the button below.", - "Chat with Riot Bot": "Chat with Riot Bot", - "Check for update": "Check for update", - "Help & About": "Help & About", - "Bug reporting": "Bug reporting", - "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 and the usernames of other users. They do not contain messages.": "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 and the usernames of other users. They do not contain messages.", - "Submit debug logs": "Submit debug logs", - "Clear cache and reload": "Clear cache and reload", - "FAQ": "FAQ", - "Versions": "Versions", - "matrix-react-sdk version:": "matrix-react-sdk version:", - "riot-web version:": "riot-web version:", - "olm version:": "olm version:", - "Homeserver is": "Homeserver is", - "Identity Server is": "Identity Server is", - "Access Token:": "Access Token:", - "click to reveal": "click to reveal", - "Labs": "Labs", - "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", - "Ignored/Blocked": "Ignored/Blocked", - "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 alias and try again.": "Please verify the room ID or alias and try again.", - "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.", - "None": "None", - "Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s", - "Server rules": "Server rules", - "User rules": "User rules", - "Close": "Close", - "You have not ignored anyone.": "You have not ignored anyone.", - "You are currently ignoring:": "You are currently ignoring:", - "You are not subscribed to any lists": "You are not subscribed to any lists", - "Unsubscribe": "Unsubscribe", - "View rules": "View rules", - "You are currently subscribed to:": "You are currently subscribed to:", - "Ignored users": "Ignored users", - "⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.", - "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.", - "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": "Server or user ID to ignore", - "eg: @bot:* or example.org": "eg: @bot:* or 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 alias of ban list": "Room ID or alias of ban list", - "Subscribe": "Subscribe", - "Notifications": "Notifications", - "Start automatically after system login": "Start automatically after system login", - "Always show the window menu bar": "Always show the window menu bar", - "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", - "Preferences": "Preferences", - "Composer": "Composer", - "Timeline": "Timeline", - "Room list": "Room list", - "Autocomplete delay (ms)": "Autocomplete delay (ms)", - "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", - "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", - "Unignore": "Unignore", - "": "", - "Import E2E room keys": "Import E2E room keys", - "Cryptography": "Cryptography", - "Device ID:": "Device ID:", - "Device key:": "Device key:", - "Bulk options": "Bulk options", - "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", - "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", - "Key backup": "Key backup", - "Cross-signing": "Cross-signing", - "Security & Privacy": "Security & Privacy", - "Devices": "Devices", - "A device's public name is visible to people you communicate with": "A device's public name is visible to people you communicate with", - "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.", - "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", - "Learn more about how we use analytics.": "Learn more about how we use analytics.", - "No media permissions": "No media permissions", - "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", - "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", - "Request media permissions": "Request media permissions", - "No Audio Outputs detected": "No Audio Outputs detected", - "No Microphones detected": "No Microphones detected", - "No Webcams detected": "No Webcams detected", - "Default Device": "Default Device", - "Audio Output": "Audio Output", - "Microphone": "Microphone", - "Camera": "Camera", - "Voice & Video": "Voice & Video", - "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", - "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", - "Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version", - "this room": "this room", - "View older messages in %(roomName)s.": "View older messages in %(roomName)s.", - "Room information": "Room information", - "Internal room ID:": "Internal room ID:", - "Room version": "Room version", - "Room version:": "Room version:", - "Developer options": "Developer options", - "Open Devtools": "Open Devtools", - "This bridge was provisioned by ": "This bridge was provisioned by ", - "This bridge is managed by .": "This bridge is managed by .", - "Bridged into , on ": "Bridged into , on ", - "Connected to on ": "Connected to on ", - "Connected via %(protocolName)s": "Connected via %(protocolName)s", - "Bridge Info": "Bridge Info", - "Below is a list of bridges connected to this room.": "Below is a list of bridges connected to this room.", - "Room Addresses": "Room Addresses", - "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", - "URL Previews": "URL Previews", - "Uploaded sound": "Uploaded sound", - "Sounds": "Sounds", - "Notification sound": "Notification sound", - "Reset": "Reset", - "Set a new custom sound": "Set a new custom sound", - "Browse": "Browse", - "Change room avatar": "Change room avatar", - "Change room name": "Change room name", - "Change main address for the room": "Change main address for the room", - "Change history visibility": "Change history visibility", - "Change permissions": "Change permissions", - "Change topic": "Change topic", - "Upgrade the room": "Upgrade the room", - "Enable room encryption": "Enable room encryption", - "Modify widgets": "Modify widgets", - "Failed to unban": "Failed to unban", - "Unban": "Unban", - "Banned by %(displayName)s": "Banned by %(displayName)s", - "Error changing power level requirement": "Error changing power level requirement", - "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.", - "Error changing power level": "Error changing power level", - "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.", - "Default role": "Default role", - "Send messages": "Send messages", - "Invite users": "Invite users", - "Change settings": "Change settings", - "Kick users": "Kick users", - "Ban users": "Ban users", - "Remove messages": "Remove messages", - "Notify everyone": "Notify everyone", - "No users have specific privileges in this room": "No users have specific privileges in this room", - "Privileged Users": "Privileged Users", - "Muted Users": "Muted Users", - "Banned users": "Banned users", - "Send %(eventType)s events": "Send %(eventType)s events", - "Roles & Permissions": "Roles & Permissions", - "Permissions": "Permissions", - "Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room", - "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. Learn more about 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. Learn more about encryption.", - "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", - "Click here to fix": "Click here to fix", - "To link to this room, please add an alias.": "To link to this room, please add an alias.", - "Only people who have been invited": "Only people who have been invited", - "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", - "Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests", - "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.", - "Anyone": "Anyone", - "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)", - "Encryption": "Encryption", - "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", - "Encrypted": "Encrypted", - "Who can access this room?": "Who can access this room?", - "Who can read history?": "Who can read history?", - "Unable to revoke sharing for email address": "Unable to revoke sharing for email 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.", - "Unable to verify email address.": "Unable to verify email address.", - "Verify the link in your inbox": "Verify the link in your inbox", - "Complete": "Complete", - "Revoke": "Revoke", - "Share": "Share", - "Discovery options will appear once you have added an email above.": "Discovery options will appear once you have added an email above.", - "Unable to revoke sharing for phone number": "Unable to revoke sharing for phone number", - "Unable to share phone number": "Unable to share phone number", - "Unable to verify phone number.": "Unable to verify phone number.", - "Incorrect verification code": "Incorrect verification code", - "Please enter verification code sent via text.": "Please enter verification code sent via text.", - "Verification code": "Verification code", - "Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.", - "Unable to remove contact information": "Unable to remove contact information", - "Remove %(email)s?": "Remove %(email)s?", - "Invalid Email Address": "Invalid Email Address", - "This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address", - "Unable to add email address": "Unable to add email address", - "Add": "Add", - "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.", - "Email Address": "Email Address", - "Remove %(phone)s?": "Remove %(phone)s?", - "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", - "Phone Number": "Phone Number", - "Cannot add any more widgets": "Cannot add any more widgets", - "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", - "Add a widget": "Add a widget", - "Drop File Here": "Drop File Here", - "Drop file here to upload": "Drop file here to upload", - " (unsupported)": " (unsupported)", - "Join as voice or video.": "Join as voice or video.", - "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", - "This user has not verified all of their devices.": "This user has not verified all of their devices.", - "You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.", - "You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.", - "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.", - "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.", - "Some devices for this user are not trusted": "Some devices for this user are not trusted", - "All devices for this user are trusted": "All devices for this user are trusted", - "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", - "All devices in this encrypted room are trusted": "All devices in this encrypted room are trusted", - "Edit message": "Edit message", - "This event could not be displayed": "This event could not be displayed", - "%(senderName)s sent an image": "%(senderName)s sent an image", - "%(senderName)s sent a video": "%(senderName)s sent a video", - "%(senderName)s uploaded a file": "%(senderName)s uploaded a file", - "Your key share request has been sent - please check your other devices for key share requests.": "Your key share request has been sent - please check your other devices for key share requests.", - "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.": "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.", - "If your other devices do not have the key for this message you will not be able to decrypt them.": "If your other devices do not have the key for this message you will not be able to decrypt them.", - "Key request sent.": "Key request sent.", - "Re-request encryption keys from your other devices.": "Re-request encryption keys from your other devices.", - "This message cannot be decrypted": "This message cannot be decrypted", - "Encrypted by an unverified device": "Encrypted by an unverified device", - "Unencrypted": "Unencrypted", - "Please select the destination room for this message": "Please select the destination room for this message", - "Scroll to bottom of page": "Scroll to bottom of page", - "Close preview": "Close preview", - "device id: ": "device id: ", - "Disinvite": "Disinvite", - "Kick": "Kick", - "Disinvite this user?": "Disinvite this user?", - "Kick this user?": "Kick this user?", - "Failed to kick": "Failed to kick", - "Ban": "Ban", - "Unban this user?": "Unban this user?", - "Ban this user?": "Ban this user?", - "Failed to ban user": "Failed to ban user", - "No recent messages by %(user)s found": "No recent messages by %(user)s found", - "Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.", - "Remove recent messages by %(user)s": "Remove recent messages by %(user)s", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?", - "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.", - "Remove %(count)s messages|other": "Remove %(count)s messages", - "Remove %(count)s messages|one": "Remove 1 message", - "Demote yourself?": "Demote yourself?", - "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.", - "Demote": "Demote", - "Failed to mute user": "Failed to mute user", - "Failed to toggle moderator status": "Failed to toggle moderator status", - "Deactivate user?": "Deactivate 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", - "Failed to deactivate user": "Failed to deactivate user", - "Failed to change power level": "Failed to change power level", - "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.", - "Are you sure?": "Are you sure?", - "No devices with registered encryption keys": "No devices with registered encryption keys", - "Jump to read receipt": "Jump to read receipt", - "Mention": "Mention", - "Invite": "Invite", - "Share Link to User": "Share Link to User", - "User Options": "User Options", - "Direct chats": "Direct chats", - "Remove recent messages": "Remove recent messages", - "Unmute": "Unmute", - "Mute": "Mute", - "Revoke Moderator": "Revoke Moderator", - "Make Moderator": "Make Moderator", - "Admin Tools": "Admin Tools", - "and %(count)s others...|other": "and %(count)s others...", - "and %(count)s others...|one": "and one other...", - "Invite to this room": "Invite to this room", - "Invited": "Invited", - "Filter room members": "Filter room members", - "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", - "Voice call": "Voice call", - "Video call": "Video call", - "Hangup": "Hangup", - "Upload file": "Upload file", - "Send an encrypted reply…": "Send an encrypted reply…", - "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", - "Send an encrypted message…": "Send an encrypted message…", - "Send a message (unencrypted)…": "Send a message (unencrypted)…", - "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.", - "You do not have permission to post to this room": "You do not have permission to post to this room", - "Bold": "Bold", - "Italics": "Italics", - "Strikethrough": "Strikethrough", - "Code block": "Code block", - "Quote": "Quote", - "No pinned messages.": "No pinned messages.", - "Loading...": "Loading...", - "Pinned Messages": "Pinned Messages", - "Unpin Message": "Unpin Message", - "Jump to message": "Jump to message", - "%(duration)ss": "%(duration)ss", - "%(duration)sm": "%(duration)sm", - "%(duration)sh": "%(duration)sh", - "%(duration)sd": "%(duration)sd", - "Online for %(duration)s": "Online for %(duration)s", - "Idle for %(duration)s": "Idle for %(duration)s", - "Offline for %(duration)s": "Offline for %(duration)s", - "Unknown for %(duration)s": "Unknown for %(duration)s", - "Online": "Online", - "Idle": "Idle", - "Offline": "Offline", - "Unknown": "Unknown", - "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", - "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", - "Replying": "Replying", - "Direct Chat": "Direct Chat", - "Room %(name)s": "Room %(name)s", - "Recent rooms": "Recent rooms", - "No rooms to show": "No rooms to show", - "Unnamed room": "Unnamed room", - "World readable": "World readable", - "Guests can join": "Guests can join", - "(~%(count)s results)|other": "(~%(count)s results)", - "(~%(count)s results)|one": "(~%(count)s result)", - "Join Room": "Join Room", - "Settings": "Settings", - "Forget room": "Forget room", - "Search": "Search", - "Share room": "Share room", - "Community Invites": "Community Invites", - "Invites": "Invites", - "Favourites": "Favourites", - "People": "People", - "Start chat": "Start chat", - "Rooms": "Rooms", - "Low priority": "Low priority", - "Historical": "Historical", - "System Alerts": "System Alerts", - "This room": "This room", - "Joining room …": "Joining room …", - "Loading …": "Loading …", - "Rejecting invite …": "Rejecting invite …", - "Join the conversation with an account": "Join the conversation with an account", - "Sign Up": "Sign Up", - "Sign In": "Sign In", - "Loading room preview": "Loading room preview", - "You were kicked from %(roomName)s by %(memberName)s": "You were kicked from %(roomName)s by %(memberName)s", - "Reason: %(reason)s": "Reason: %(reason)s", - "Forget this room": "Forget this room", - "Re-join": "Re-join", - "You were banned from %(roomName)s by %(memberName)s": "You were banned from %(roomName)s by %(memberName)s", - "Something went wrong with your invite to %(roomName)s": "Something went wrong with your invite to %(roomName)s", - "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.", - "unknown error code": "unknown error code", - "You can only join it with a working invite.": "You can only join it with a working invite.", - "Try to join anyway": "Try to join anyway", - "You can still join it because this is a public room.": "You can still join it because this is a public room.", - "Join the discussion": "Join the discussion", - "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "This invite to %(roomName)s was sent to %(email)s which is not associated with your account", - "Link this email with your account in Settings to receive invites directly in Riot.": "Link this email with your account in Settings to receive invites directly in Riot.", - "This invite to %(roomName)s was sent to %(email)s": "This invite to %(roomName)s was sent to %(email)s", - "Use an identity server in Settings to receive invites directly in Riot.": "Use an identity server in Settings to receive invites directly in Riot.", - "Share this email in Settings to receive invites directly in Riot.": "Share this email in Settings to receive invites directly in Riot.", - "Do you want to chat with %(user)s?": "Do you want to chat with %(user)s?", - " wants to chat": " wants to chat", - "Start chatting": "Start chatting", - "Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?", - " invited you": " invited you", - "Reject": "Reject", - "You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?", - "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s can't be previewed. Do you want to join it?", - "%(roomName)s does not exist.": "%(roomName)s does not exist.", - "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?", - "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", - "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 submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", - "Never lose encrypted messages": "Never lose encrypted 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 secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", - "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", - "Not now": "Not now", - "Don't ask me again": "Don't ask me again", - "Options": "Options", - "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", - "%(count)s unread messages including mentions.|one": "1 unread mention.", - "%(count)s unread messages.|other": "%(count)s unread messages.", - "%(count)s unread messages.|one": "1 unread message.", - "Unread mentions.": "Unread mentions.", - "Unread messages.": "Unread messages.", - "Add a topic": "Add a topic", - "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", - "This room has already been upgraded.": "This room has already been upgraded.", - "This room is running room version , which this homeserver has marked as unstable.": "This room is running room version , which this homeserver has marked as unstable.", - "Only room administrators will see this warning": "Only room administrators will see this warning", - "This Room": "This Room", - "All Rooms": "All Rooms", - "Search…": "Search…", - "Server error": "Server error", - "Command error": "Command error", - "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", - "Failed to connect to integration manager": "Failed to connect to integration manager", - "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", - "Add some now": "Add some now", - "Stickerpack": "Stickerpack", - "Hide Stickers": "Hide Stickers", - "Show Stickers": "Show Stickers", - "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.", - "Revoke invite": "Revoke invite", - "Invited by %(sender)s": "Invited by %(sender)s", - "Jump to first unread message.": "Jump to first unread message.", - "Error updating main address": "Error updating main address", - "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", - "Error creating alias": "Error creating alias", - "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", - "Error removing alias": "Error removing alias", - "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", - "Main address": "Main address", - "not specified": "not specified", - "Remote addresses for this room:": "Remote addresses for this room:", - "Local addresses for this room:": "Local addresses for this room:", - "This room has no local addresses": "This room has no local addresses", - "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", - "Error updating flair": "Error updating flair", - "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.", - "Invalid community ID": "Invalid community ID", - "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID", - "Showing flair for these communities:": "Showing flair for these communities:", - "This room is not showing flair for any communities": "This room is not showing flair for any communities", - "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)", - "Room Name": "Room Name", - "Room Topic": "Room Topic", - "Room avatar": "Room avatar", - "You have enabled URL previews by default.": "You have enabled URL previews by default.", - "You have disabled URL previews by default.": "You have disabled URL previews by default.", - "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.", - "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", - "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.", - "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.": "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.", - "Verify User": "Verify User", - "For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.", - "For maximum security, do this in person.": "For maximum security, do this in person.", - "Start Verification": "Start Verification", - "Members": "Members", - "Files": "Files", - "Trusted": "Trusted", - "Not trusted": "Not trusted", - "Hide verified sessions": "Hide verified sessions", - "%(count)s verified sessions|other": "%(count)s verified sessions", - "%(count)s verified sessions|one": "1 verified session", - "Direct message": "Direct message", - "Remove from community": "Remove from community", - "Disinvite this user from community?": "Disinvite this user from community?", - "Remove this user from community?": "Remove this user from community?", - "Failed to withdraw invitation": "Failed to withdraw invitation", - "Failed to remove user from community": "Failed to remove user from community", - "%(role)s in %(roomName)s": "%(role)s in %(roomName)s", - "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", - "Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.", - "Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.", - "Security": "Security", - "Sunday": "Sunday", - "Monday": "Monday", - "Tuesday": "Tuesday", - "Wednesday": "Wednesday", - "Thursday": "Thursday", - "Friday": "Friday", - "Saturday": "Saturday", - "Today": "Today", - "Yesterday": "Yesterday", - "View Source": "View Source", - "Error decrypting audio": "Error decrypting audio", - "React": "React", - "Reply": "Reply", - "Edit": "Edit", - "Message Actions": "Message Actions", - "Attachment": "Attachment", - "Error decrypting attachment": "Error decrypting attachment", - "Decrypt %(text)s": "Decrypt %(text)s", - "Download %(text)s": "Download %(text)s", - "Invalid file%(extra)s": "Invalid file%(extra)s", - "Error decrypting image": "Error decrypting image", - "Show image": "Show image", - "You have ignored this user, so their message is hidden. Show anyways.": "You have ignored this user, so their message is hidden. Show anyways.", - "You verified %(name)s": "You verified %(name)s", - "You cancelled verifying %(name)s": "You cancelled verifying %(name)s", - "%(name)s cancelled verifying": "%(name)s cancelled verifying", - "You accepted": "You accepted", - "%(name)s accepted": "%(name)s accepted", - "You cancelled": "You cancelled", - "%(name)s cancelled": "%(name)s cancelled", - "%(name)s wants to verify": "%(name)s wants to verify", - "You sent a verification request": "You sent a verification request", - "Error decrypting video": "Error decrypting video", - "Show all": "Show all", - "Reactions": "Reactions", - " reacted with %(content)s": " reacted with %(content)s", - "reacted with %(shortName)s": "reacted with %(shortName)s", - "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", - "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", - "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", - "This room is a continuation of another conversation.": "This room is a continuation of another conversation.", - "Click here to see older messages.": "Click here to see older messages.", - "Copied!": "Copied!", - "Failed to copy": "Failed to copy", - "Add an Integration": "Add an Integration", - "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", - "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", - "edited": "edited", - "Removed or unknown message type": "Removed or unknown message type", - "Message removed by %(userId)s": "Message removed by %(userId)s", - "Message removed": "Message removed", - "Failed to load group members": "Failed to load group members", - "Filter community members": "Filter community members", - "Invite to this community": "Invite to this community", - "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)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": "Failed to remove '%(roomName)s' from %(groupId)s", - "Something went wrong!": "Something went wrong!", - "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "The visibility of '%(roomName)s' in %(groupId)s could not be updated.", - "Visibility in Room List": "Visibility in Room List", - "Visible to everyone": "Visible to everyone", - "Only visible to community members": "Only visible to community members", - "Add rooms to this community": "Add rooms to this community", - "Filter community rooms": "Filter community rooms", - "Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.", - "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.", - "You're not currently a member of any communities.": "You're not currently a member of any communities.", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.", - "Yes, I want to help!": "Yes, I want to help!", - "You are not receiving desktop notifications": "You are not receiving desktop notifications", - "Enable them now": "Enable them now", - "What's New": "What's New", - "Update": "Update", - "What's new?": "What's new?", - "A new version of Riot is available.": "A new version of Riot is available.", - "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", - "Set Password": "Set Password", - "Please contact your service administrator to get this limit increased.": "Please contact your service administrator to get this limit increased.", - "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.": "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.", - "This homeserver has exceeded one of its resource limits so some users will not be able to log in.": "This homeserver has exceeded one of its resource limits so some users will not be able to log in.", - "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", - "Checking for an update...": "Checking for an update...", - "No update available.": "No update available.", - "Downloading update...": "Downloading update...", - "Frequently Used": "Frequently Used", - "Smileys & People": "Smileys & People", - "Animals & Nature": "Animals & Nature", - "Food & Drink": "Food & Drink", - "Activities": "Activities", - "Travel & Places": "Travel & Places", - "Objects": "Objects", - "Symbols": "Symbols", - "Flags": "Flags", - "Quick Reactions": "Quick Reactions", - "Cancel search": "Cancel search", - "Unknown Address": "Unknown Address", - "Any of the following data may be shared:": "Any of the following data may be shared:", - "Your display name": "Your display name", - "Your avatar URL": "Your avatar URL", - "Your user ID": "Your user ID", - "Your theme": "Your theme", - "Riot URL": "Riot URL", - "Room ID": "Room ID", - "Widget ID": "Widget ID", - "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data with %(widgetDomain)s & your Integration Manager.", - "Using this widget may share data with %(widgetDomain)s.": "Using this widget may share data with %(widgetDomain)s.", - "Widgets do not use message encryption.": "Widgets do not use message encryption.", - "Widget added by": "Widget added by", - "This widget may use cookies.": "This widget may use cookies.", - "Delete Widget": "Delete Widget", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", - "Delete widget": "Delete widget", - "Failed to remove widget": "Failed to remove widget", - "An error ocurred whilst trying to remove the widget from the room": "An error ocurred whilst trying to remove the widget from the room", - "Minimize apps": "Minimize apps", - "Maximize apps": "Maximize apps", - "Popout widget": "Popout widget", - "More options": "More options", - "Create new room": "Create new room", - "Unblacklist": "Unblacklist", - "Blacklist": "Blacklist", - "Unverify": "Unverify", - "Verify...": "Verify...", - "Join": "Join", - "No results": "No results", - "Yes": "Yes", - "No": "No", - "Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.", - "collapse": "collapse", - "expand": "expand", - "Communities": "Communities", - "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", - "Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s", - "Rotate Left": "Rotate Left", - "Rotate counter-clockwise": "Rotate counter-clockwise", - "Rotate Right": "Rotate Right", - "Rotate clockwise": "Rotate clockwise", - "Download this file": "Download this file", - "Language Dropdown": "Language Dropdown", - "Manage Integrations": "Manage Integrations", - "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", - "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", - "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", - "%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times", - "%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined", - "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times", - "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft", - "%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times", - "%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft", - "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times", - "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left", - "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times", - "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left", - "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times", - "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined", - "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times", - "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined", - "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times", - "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations", - "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times", - "%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation", - "%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times", - "%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn", - "%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times", - "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn", - "were invited %(count)s times|other": "were invited %(count)s times", - "were invited %(count)s times|one": "were invited", - "was invited %(count)s times|other": "was invited %(count)s times", - "was invited %(count)s times|one": "was invited", - "were banned %(count)s times|other": "were banned %(count)s times", - "were banned %(count)s times|one": "were banned", - "was banned %(count)s times|other": "was banned %(count)s times", - "was banned %(count)s times|one": "was banned", - "were unbanned %(count)s times|other": "were unbanned %(count)s times", - "were unbanned %(count)s times|one": "were unbanned", - "was unbanned %(count)s times|other": "was unbanned %(count)s times", - "was unbanned %(count)s times|one": "was unbanned", - "were kicked %(count)s times|other": "were kicked %(count)s times", - "were kicked %(count)s times|one": "were kicked", - "was kicked %(count)s times|other": "was kicked %(count)s times", - "was kicked %(count)s times|one": "was kicked", - "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times", - "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name", - "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times", - "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name", - "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times", - "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar", - "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times", - "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar", - "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)smade no changes %(count)s times", - "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)smade no changes", - "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)smade no changes %(count)s times", - "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes", - "Power level": "Power level", - "Custom level": "Custom level", - "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", - "In reply to ": "In reply to ", - "Room alias": "Room alias", - "e.g. my-room": "e.g. my-room", - "Some characters not allowed": "Some characters not allowed", - "Please provide a room alias": "Please provide a room alias", - "This alias is available to use": "This alias is available to use", - "This alias is already in use": "This alias is already in use", - "Room directory": "Room directory", - "And %(count)s more...|other": "And %(count)s more...", - "ex. @bob:example.com": "ex. @bob:example.com", - "Add User": "Add User", - "Matrix ID": "Matrix ID", - "Matrix Room ID": "Matrix Room ID", - "email address": "email address", - "That doesn't look like a valid email address": "That doesn't look like a valid email address", - "You have entered an invalid address.": "You have entered an invalid address.", - "Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.", - "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.", - "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", - "The following users may not exist": "The following users may not exist", - "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?", - "Invite anyway and never warn me again": "Invite anyway and never warn me again", - "Invite anyway": "Invite anyway", - "Close dialog": "Close dialog", - "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.", - "Preparing to send logs": "Preparing to send logs", - "Logs sent": "Logs sent", - "Thank you!": "Thank you!", - "Failed to send logs: ": "Failed to send logs: ", - "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.", - "Before submitting logs, you must create a GitHub issue to describe your problem.": "Before submitting logs, you must create a GitHub issue to describe your problem.", - "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.": "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.", - "Send logs": "Send logs", - "Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s", - "Unavailable": "Unavailable", - "Changelog": "Changelog", - "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", - "Removing…": "Removing…", - "Confirm Removal": "Confirm Removal", - "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", - "Clear all data on this device?": "Clear all data on this device?", - "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.", - "Clear all data": "Clear all data", - "Community IDs cannot be empty.": "Community IDs cannot be empty.", - "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'", - "Something went wrong whilst creating your community": "Something went wrong whilst creating your community", - "Create Community": "Create Community", - "Community Name": "Community Name", - "Example": "Example", - "Community ID": "Community ID", - "example": "example", - "Create": "Create", - "Please enter a name for the room": "Please enter a name for the room", - "Set a room alias to easily share your room with other people.": "Set a room alias to easily share your room with other people.", - "This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.", - "Create a public room": "Create a public room", - "Create a private room": "Create a private room", - "Name": "Name", - "Topic (optional)": "Topic (optional)", - "Make this room public": "Make this room public", - "Hide advanced": "Hide advanced", - "Show advanced": "Show advanced", - "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)", - "Create Room": "Create Room", - "Sign out": "Sign out", - "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 Riot to do this": "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 Riot to do this", - "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ", - "Incompatible Database": "Incompatible Database", - "Continue With Encryption Disabled": "Continue With Encryption Disabled", - "Unknown error": "Unknown error", - "Incorrect password": "Incorrect password", - "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.", - "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.", - "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", - "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", - "To continue, please enter your password:": "To continue, please enter your password:", - "Verify device": "Verify device", - "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)", - "Verify by comparing a short text string.": "Verify by comparing a short text string.", - "Begin Verifying": "Begin Verifying", - "Waiting for partner to accept...": "Waiting for partner to accept...", - "Nothing appearing? Not all clients support interactive verification yet. .": "Nothing appearing? Not all clients support interactive verification yet. .", - "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...", - "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:": "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:", - "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:", - "Use two-way text verification": "Use two-way text verification", - "Device name": "Device name", - "Device ID": "Device ID", - "Device key": "Device key", - "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.", - "I verify that the keys match": "I verify that the keys match", - "Back": "Back", - "Send Custom Event": "Send Custom Event", - "You must specify an event type!": "You must specify an event type!", - "Event sent!": "Event sent!", - "Failed to send custom event.": "Failed to send custom event.", - "Event Type": "Event Type", - "State Key": "State Key", - "Event Content": "Event Content", - "Send Account Data": "Send Account Data", - "Filter results": "Filter results", - "Explore Room State": "Explore Room State", - "Explore Account Data": "Explore Account Data", - "View Servers in Room": "View Servers in Room", - "Toolbox": "Toolbox", - "Developer Tools": "Developer Tools", - "An error has occurred.": "An error has occurred.", - "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.", - "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.", - "Waiting for partner to confirm...": "Waiting for partner to confirm...", - "Incoming Verification Request": "Incoming Verification Request", - "Integrations are disabled": "Integrations are disabled", - "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", - "Integrations not allowed": "Integrations not allowed", - "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.", - "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s", - "We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.", - "Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.", - "We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.", - "Failed to find the following users": "Failed to find the following users", - "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s", - "Recent Conversations": "Recent Conversations", - "Suggestions": "Suggestions", - "Recently Direct Messaged": "Recently Direct Messaged", - "Show more": "Show more", - "Direct Messages": "Direct Messages", - "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", - "Go": "Go", - "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", - "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", - "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", - "Start verification": "Start verification", - "Share without verifying": "Share without verifying", - "Ignore request": "Ignore request", - "Loading device info...": "Loading device info...", - "Encryption key request": "Encryption key request", - "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.", - "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.", - "Incompatible local cache": "Incompatible local cache", - "Clear cache and resync": "Clear cache and resync", - "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", - "Updating Riot": "Updating Riot", - "I don't want my encrypted messages": "I don't want my encrypted messages", - "Manually export keys": "Manually export keys", - "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", - "Are you sure you want to sign out?": "Are you sure you want to sign out?", - "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", - "Message edits": "Message edits", - "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.", - "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.", - "Report bugs & give feedback": "Report bugs & give feedback", - "Please fill why you're reporting.": "Please fill why you're reporting.", - "Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator", - "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.": "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.", - "Send report": "Send report", - "Room Settings - %(roomName)s": "Room Settings - %(roomName)s", - "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": "Upgrade this room to version %(version)s", - "Upgrade Room Version": "Upgrade Room Version", - "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:", - "Create a new room with the same name, description and avatar": "Create a new room with the same name, description and avatar", - "Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room", - "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room", - "Put a link back to the old room at the start of the new room so people can see old messages": "Put a link back to the old room at the start of the new room so people can see old messages", - "Automatically invite users": "Automatically invite users", - "Upgrade private room": "Upgrade private room", - "Upgrade public room": "Upgrade public room", - "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.", - "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.", - "You'll upgrade this room from to .": "You'll upgrade this room from to .", - "Upgrade": "Upgrade", - "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", - "Clear Storage and Sign Out": "Clear Storage and Sign Out", - "Send Logs": "Send Logs", - "Refresh": "Refresh", - "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.", - "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.", - "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.", - "Verification Pending": "Verification Pending", - "Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.", - "Email address": "Email address", - "This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.", - "Skip": "Skip", - "A username can only contain lower case letters, numbers and '=_-./'": "A username can only contain lower case letters, numbers and '=_-./'", - "Username not available": "Username not available", - "Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s", - "An error occurred: %(error_string)s": "An error occurred: %(error_string)s", - "Checking...": "Checking...", - "Username available": "Username available", - "To get started, please pick a username!": "To get started, please pick a username!", - "This will be your account name on the homeserver, or you can pick a different server.": "This will be your account name on the homeserver, or you can pick a different server.", - "If you already have a Matrix account you can log in instead.": "If you already have a Matrix account you can log in instead.", - "You have successfully set a password!": "You have successfully set a password!", - "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", - "You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.", - "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.", - "(HTTP status %(httpStatus)s)": "(HTTP status %(httpStatus)s)", - "Please set a password!": "Please set a password!", - "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", - "Share Room": "Share Room", - "Link to most recent message": "Link to most recent message", - "Share User": "Share User", - "Share Community": "Share Community", - "Share Room Message": "Share Room Message", - "Link to selected message": "Link to selected message", - "COPY": "COPY", - "Command Help": "Command Help", - "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", - "Missing session data": "Missing session data", - "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.", - "Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.", - "Integration Manager": "Integration Manager", - "Find others by phone or email": "Find others by phone or email", - "Be found by phone or email": "Be found by phone or email", - "Use bots, bridges, widgets and sticker packs": "Use bots, bridges, widgets and sticker packs", - "Terms of Service": "Terms of Service", - "To continue you need to accept the terms of this service.": "To continue you need to accept the terms of this service.", - "Service": "Service", - "Summary": "Summary", - "Document": "Document", - "Next": "Next", - "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.", - "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", - "Room contains unknown devices": "Room contains unknown devices", - "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", - "Unknown devices": "Unknown devices", - "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", - "Upload files": "Upload files", - "Upload all": "Upload all", - "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", - "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", - "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.", - "Upload %(count)s other files|other": "Upload %(count)s other files", - "Upload %(count)s other files|one": "Upload %(count)s other file", - "Cancel All": "Cancel All", - "Upload Error": "Upload Error", - "A widget would like to verify your identity": "A widget would like to verify your identity", - "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.", - "Remember my selection for this widget": "Remember my selection for this widget", - "Allow": "Allow", - "Deny": "Deny", - "Enter secret storage passphrase": "Enter secret storage passphrase", - "Unable to access secret storage. Please verify that you entered the correct passphrase.": "Unable to access secret storage. Please verify that you entered the correct passphrase.", - "Warning: You should only access secret storage from a trusted computer.": "Warning: You should only access secret storage from a trusted computer.", - "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.", - "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.", - "Enter secret storage recovery key": "Enter secret storage recovery key", - "This looks like a valid recovery key!": "This looks like a valid recovery key!", - "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", - "Not a valid recovery key": "Not a valid recovery key", - "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.", - "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .", - "Unable to load backup status": "Unable to load backup status", - "Recovery Key Mismatch": "Recovery Key Mismatch", - "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", - "Incorrect Recovery Passphrase": "Incorrect Recovery Passphrase", - "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.", - "Unable to restore backup": "Unable to restore backup", - "No backup found!": "No backup found!", - "Backup Restored": "Backup Restored", - "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", - "Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys", - "Enter Recovery Passphrase": "Enter Recovery Passphrase", - "Warning: you should only set up key backup from a trusted computer.": "Warning: you should only set up key backup from a trusted computer.", - "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", - "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", - "Enter Recovery Key": "Enter Recovery Key", - "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", - "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", - "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", - "Private Chat": "Private Chat", - "Public Chat": "Public Chat", - "Custom": "Custom", - "Alias (optional)": "Alias (optional)", - "Reject invitation": "Reject invitation", - "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", - "Unable to reject invite": "Unable to reject invite", - "Resend": "Resend", - "Resend edit": "Resend edit", - "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", - "Resend removal": "Resend removal", - "Cancel Sending": "Cancel Sending", - "Forward Message": "Forward Message", - "Pin Message": "Pin Message", - "View Decrypted Source": "View Decrypted Source", - "Unhide Preview": "Unhide Preview", - "Share Permalink": "Share Permalink", - "Share Message": "Share Message", - "Source URL": "Source URL", - "Collapse Reply Thread": "Collapse Reply Thread", - "End-to-end encryption information": "End-to-end encryption information", - "Report Content": "Report Content", - "Failed to set Direct Message status of room": "Failed to set Direct Message status of room", - "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", - "Notification settings": "Notification settings", - "All messages (noisy)": "All messages (noisy)", - "All messages": "All messages", - "Mentions only": "Mentions only", - "Leave": "Leave", - "Forget": "Forget", - "Favourite": "Favourite", - "Low Priority": "Low Priority", - "Clear status": "Clear status", - "Update status": "Update status", - "Set status": "Set status", - "Set a new status...": "Set a new status...", - "View Community": "View Community", - "Hide": "Hide", - "Home": "Home", - "Sign in": "Sign in", - "Help": "Help", - "Reload": "Reload", - "Take picture": "Take picture", - "Remove for everyone": "Remove for everyone", - "Remove for me": "Remove for me", - "User Status": "User Status", - "powered by Matrix": "powered by Matrix", - "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", - "Country Dropdown": "Country Dropdown", - "Custom Server Options": "Custom Server Options", - "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.", - "To continue, please enter your password.": "To continue, please enter your password.", - "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.", - "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", - "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", - "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", - "Please check your email to continue registration.": "Please check your email to continue registration.", - "Token incorrect": "Token incorrect", - "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", - "Please enter the code it contains:": "Please enter the code it contains:", - "Code": "Code", - "Submit": "Submit", - "Start authentication": "Start authentication", - "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", - "Your Modular server": "Your Modular server", - "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.", - "Server Name": "Server Name", - "The email field must not be blank.": "The email field must not be blank.", - "The username field must not be blank.": "The username field must not be blank.", - "The phone number field must not be blank.": "The phone number field must not be blank.", - "The password field must not be blank.": "The password field must not be blank.", - "Email": "Email", - "Username": "Username", - "Phone": "Phone", - "Not sure of your password? Set a new one": "Not sure of your password? Set a new one", - "Sign in with": "Sign in with", - "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", - "Use an email address to recover your account": "Use an email address to recover your account", - "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", - "Doesn't look like a valid email address": "Doesn't look like a valid email address", - "Enter password": "Enter password", - "Password is allowed, but unsafe": "Password is allowed, but unsafe", - "Nice, strong password!": "Nice, strong password!", - "Keep going...": "Keep going...", - "Passwords don't match": "Passwords don't match", - "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", - "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", - "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", - "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", - "Enter username": "Enter username", - "Email (optional)": "Email (optional)", - "Confirm": "Confirm", - "Phone (optional)": "Phone (optional)", - "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Create your Matrix account on ": "Create your Matrix account on ", - "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", - "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.", - "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", - "Homeserver URL": "Homeserver URL", - "Enter your custom identity server URL What does this mean?": "Enter your custom identity server URL What does this mean?", - "Identity Server URL": "Identity Server URL", - "Other servers": "Other servers", - "Free": "Free", - "Join millions for free on the largest public server": "Join millions for free on the largest public server", - "Premium": "Premium", - "Premium hosting for organisations Learn more": "Premium hosting for organisations Learn more", - "Find other public servers or use a custom server": "Find other public servers or use a custom server", - "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", - "Sign in to your Matrix account on ": "Sign in to your Matrix account on ", - "Sorry, your browser is not able to run Riot.": "Sorry, your browser is not able to run Riot.", - "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", - "Please install Chrome, Firefox, or Safari for the best experience.": "Please install Chrome, Firefox, or Safari for the best experience.", - "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!", - "I understand the risks and wish to continue": "I understand the risks and wish to continue", - "Couldn't load page": "Couldn't load page", - "You must register to use this functionality": "You must register to use this functionality", - "You must join the room to see its files": "You must join the room to see its files", - "There are no visible files in this room": "There are no visible files in this room", - "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n": "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n", - "Add rooms to the community summary": "Add rooms to the community summary", - "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", - "Add to summary": "Add to summary", - "Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:", - "Add a Room": "Add a Room", - "Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s", - "The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.", - "Add users to the community summary": "Add users to the community summary", - "Who would you like to add to this summary?": "Who would you like to add to this summary?", - "Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:", - "Add a User": "Add a User", - "Failed to remove a user from the summary of %(groupId)s": "Failed to remove a user from the summary of %(groupId)s", - "The user '%(displayName)s' could not be removed from the summary.": "The user '%(displayName)s' could not be removed from the summary.", - "Failed to upload image": "Failed to upload image", - "Failed to update community": "Failed to update community", - "Unable to accept invite": "Unable to accept invite", - "Unable to join community": "Unable to join community", - "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.", - "Leave Community": "Leave Community", - "Leave %(groupName)s?": "Leave %(groupName)s?", - "Unable to leave community": "Unable to leave community", - "Community Settings": "Community Settings", - "Want more than a community? Get your own server": "Want more than a community? Get your own server", - "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", - "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", - "Featured Rooms:": "Featured Rooms:", - "Featured Users:": "Featured Users:", - "%(inviter)s has invited you to join this community": "%(inviter)s has invited you to join this community", - "Join this community": "Join this community", - "Leave this community": "Leave this community", - "You are an administrator of this community": "You are an administrator of this community", - "You are a member of this community": "You are a member of this community", - "Who can join this community?": "Who can join this community?", - "Everyone": "Everyone", - "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!", - "Long Description (HTML)": "Long Description (HTML)", - "Upload avatar": "Upload avatar", - "Description": "Description", - "Community %(groupId)s not found": "Community %(groupId)s not found", - "This homeserver does not support communities": "This homeserver does not support communities", - "Failed to load %(groupId)s": "Failed to load %(groupId)s", - "Explore": "Explore", - "Filter": "Filter", - "Filter rooms…": "Filter rooms…", - "Failed to reject invitation": "Failed to reject invitation", - "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", - "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", - "Failed to leave room": "Failed to leave room", - "Can't leave Server Notices room": "Can't leave Server Notices room", - "This room is used for important messages from the Homeserver, so you cannot leave it.": "This room is used for important messages from the Homeserver, so you cannot leave it.", - "Signed Out": "Signed Out", - "For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.", - "Terms and Conditions": "Terms and Conditions", - "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.", - "Review terms and conditions": "Review terms and conditions", - "Old cryptography data detected": "Old cryptography data detected", - "Data from an older version of Riot 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.": "Data from an older version of Riot 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.", - "Verification Request": "Verification Request", - "Logout": "Logout", - "%(creator)s created and configured the room.": "%(creator)s created and configured the room.", - "Your Communities": "Your Communities", - "Did you know: you can use communities to filter your Riot.im experience!": "Did you know: you can use communities to filter your Riot.im experience!", - "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.", - "Error whilst fetching joined communities": "Error whilst fetching joined communities", - "Create a new community": "Create a new community", - "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.", - "You have no visible notifications": "You have no visible notifications", - "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", - "Riot failed to get the public room list.": "Riot failed to get the public room list.", - "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.", - "Delete the room alias %(alias)s and remove %(name)s from the directory?": "Delete the room alias %(alias)s and remove %(name)s from the directory?", - "Remove %(name)s from the directory?": "Remove %(name)s from the directory?", - "Remove from Directory": "Remove from Directory", - "remove %(name)s from the directory.": "remove %(name)s from the directory.", - "delete the alias.": "delete the alias.", - "The server may be unavailable or overloaded": "The server may be unavailable or overloaded", - "Unable to join network": "Unable to join network", - "Riot does not know how to join a room on this network": "Riot does not know how to join a room on this network", - "Room not found": "Room not found", - "Couldn't find a matching Matrix room": "Couldn't find a matching Matrix room", - "Fetching third party location failed": "Fetching third party location failed", - "Unable to look up room ID from server": "Unable to look up room ID from server", - "Preview": "Preview", - "View": "View", - "Find a room…": "Find a room…", - "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", - "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", - "Explore rooms": "Explore rooms", - "Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", - "Show devices, send anyway or cancel.": "Show devices, send anyway or cancel.", - "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", - "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", - "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", - "%(count)s of your messages have not been sent.|other": "Some of your messages have not been sent.", - "%(count)s of your messages have not been sent.|one": "Your message was not sent.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Resend message or cancel message now.", - "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", - "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", - "Active call": "Active call", - "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", - "Jump to first unread room.": "Jump to first unread room.", - "Jump to first invite.": "Jump to first invite.", - "Add room": "Add room", - "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", - "You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?", - "Search failed": "Search failed", - "Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(", - "No more results": "No more results", - "Unknown room %(roomId)s": "Unknown room %(roomId)s", - "Room": "Room", - "Failed to reject invite": "Failed to reject invite", - "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", - "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", - "Fill screen": "Fill screen", - "Click to unmute video": "Click to unmute video", - "Click to mute video": "Click to mute video", - "Click to unmute audio": "Click to unmute audio", - "Click to mute audio": "Click to mute audio", - "Clear filter": "Clear filter", - "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", - "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", - "Failed to load timeline position": "Failed to load timeline position", - " (1/%(totalCount)s)": " (1/%(totalCount)s)", - "Guest": "Guest", - "Your profile": "Your profile", - "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", - "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", - "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", - "Could not load user profile": "Could not load user profile", - "Complete security": "Complete security", - "Verify this session to grant it access to encrypted messages.": "Verify this session to grant it access to encrypted messages.", - "Start": "Start", - "Session verified": "Session verified", - "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.", - "Done": "Done", - "Without completing security on this device, it won’t have access to encrypted messages.": "Without completing security on this device, it won’t have access to encrypted messages.", - "Go Back": "Go Back", - "Failed to send email": "Failed to send email", - "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", - "A new password must be entered.": "A new password must be entered.", - "New passwords must match each other.": "New passwords must match each other.", - "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.", - "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", - "Your Matrix account on ": "Your Matrix account on ", - "No identity server is configured: add one in server settings to reset your password.": "No identity server is configured: add one in server settings to reset your password.", - "Sign in instead": "Sign in instead", - "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.", - "Send Reset Email": "Send Reset Email", - "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", - "I have verified my email address": "I have verified my email address", - "Your password has been reset.": "Your password has been reset.", - "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.", - "Return to login screen": "Return to login screen", - "Set a new password": "Set a new password", - "Invalid homeserver discovery response": "Invalid homeserver discovery response", - "Failed to get autodiscovery configuration from server": "Failed to get autodiscovery configuration from server", - "Invalid base_url for m.homeserver": "Invalid base_url for m.homeserver", - "Homeserver URL does not appear to be a valid Matrix homeserver": "Homeserver URL does not appear to be a valid Matrix homeserver", - "Invalid identity server discovery response": "Invalid identity server discovery response", - "Invalid base_url for m.identity_server": "Invalid base_url for m.identity_server", - "Identity server URL does not appear to be a valid identity server": "Identity server URL does not appear to be a valid identity server", - "General failure": "General failure", - "This homeserver does not support login using email address.": "This homeserver does not support login using email address.", - "Please contact your service administrator to continue using this service.": "Please contact your service administrator to continue using this service.", - "This account has been deactivated.": "This account has been deactivated.", - "Incorrect username and/or password.": "Incorrect username and/or password.", - "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", - "Failed to perform homeserver discovery": "Failed to perform homeserver discovery", - "The phone number entered looks invalid": "The phone number entered looks invalid", - "This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.", - "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", - "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", - "Sign in with single sign-on": "Sign in with single sign-on", - "Create account": "Create account", - "Failed to fetch avatar URL": "Failed to fetch avatar URL", - "Set a display name:": "Set a display name:", - "Upload an avatar:": "Upload an avatar:", - "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", - "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", - "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", - "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", - "Continue with previous account": "Continue with previous account", - "Log in to your new account.": "Log in to your new account.", - "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", - "Registration Successful": "Registration Successful", - "Create your account": "Create your account", - "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", - "Failed to re-authenticate": "Failed to re-authenticate", - "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.": "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.", - "Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.", - "Forgotten your password?": "Forgotten your password?", - "Sign in and regain access to your account.": "Sign in and regain access to your account.", - "You cannot sign in to your account. Please contact your homeserver admin for more information.": "You cannot sign in to your account. Please contact your homeserver admin for more information.", - "You're signed out": "You're signed out", - "Clear personal data": "Clear personal data", - "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.", - "Commands": "Commands", - "Command Autocomplete": "Command Autocomplete", - "Community Autocomplete": "Community Autocomplete", - "Results from DuckDuckGo": "Results from DuckDuckGo", - "DuckDuckGo Results": "DuckDuckGo Results", - "Emoji": "Emoji", - "Emoji Autocomplete": "Emoji Autocomplete", - "Notify the whole room": "Notify the whole room", - "Room Notification": "Room Notification", - "Notification Autocomplete": "Notification Autocomplete", - "Room Autocomplete": "Room Autocomplete", - "Users": "Users", - "User Autocomplete": "User Autocomplete", - "unknown device": "unknown device", - "NOT verified": "NOT verified", - "Blacklisted": "Blacklisted", - "verified": "verified", - "Verification": "Verification", - "Ed25519 fingerprint": "Ed25519 fingerprint", - "User ID": "User ID", - "Curve25519 identity key": "Curve25519 identity key", - "none": "none", - "Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key", - "Algorithm": "Algorithm", - "unencrypted": "unencrypted", - "Decryption error": "Decryption error", - "Session ID": "Session ID", - "Event information": "Event information", - "Sender device information": "Sender device information", - "Passphrases must match": "Passphrases must match", - "Passphrase must not be empty": "Passphrase must not be empty", - "Export room keys": "Export room keys", - "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.", - "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.", - "Enter passphrase": "Enter passphrase", - "Confirm passphrase": "Confirm passphrase", - "Export": "Export", - "Import room keys": "Import room keys", - "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", - "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", - "File to import": "File to import", - "Import": "Import", - "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.", - "Restore": "Restore", - "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.", - "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", - "Warning: You should only set up secret storage from a trusted computer.": "Warning: You should only set up secret storage from a trusted computer.", - "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.", - "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", - "Enter a passphrase...": "Enter a passphrase...", - "Set up with a recovery key": "Set up with a recovery key", - "That matches!": "That matches!", - "That doesn't match.": "That doesn't match.", - "Go back to set it again.": "Go back to set it again.", - "Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.", - "Repeat your passphrase...": "Repeat your passphrase...", - "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.": "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.", - "As a safety net, you can use it to restore your access to encrypted messages.": "As a safety net, you can use it to restore your access to encrypted messages.", - "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.", - "Keep your recovery key somewhere very secure, like a password manager (or a safe).": "Keep your recovery key somewhere very secure, like a password manager (or a safe).", - "Your Recovery Key": "Your Recovery Key", - "Copy to clipboard": "Copy to clipboard", - "Download": "Download", - "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", - "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", - "Print it and store it somewhere safe": "Print it and store it somewhere safe", - "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", - "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", - "Your access to encrypted messages is now protected.": "Your access to encrypted messages is now protected.", - "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.", - "Set up secret storage": "Set up secret storage", - "Restore your Key Backup": "Restore your Key Backup", - "Migrate from Key Backup": "Migrate from Key Backup", - "Secure your encrypted messages with a passphrase": "Secure your encrypted messages with a passphrase", - "Confirm your passphrase": "Confirm your passphrase", - "Recovery key": "Recovery key", - "Keep it safe": "Keep it safe", - "Storing secrets...": "Storing secrets...", - "Success!": "Success!", - "Unable to set up secret storage": "Unable to set up secret storage", - "Retry": "Retry", - "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", - "Set up with a Recovery Key": "Set up with a Recovery Key", - "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.", - "As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.", - "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", - "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.", - "Set up Secure Message Recovery": "Set up Secure Message Recovery", - "Secure your backup with a passphrase": "Secure your backup with a passphrase", - "Starting backup...": "Starting backup...", - "Create Key Backup": "Create Key Backup", - "Unable to create key backup": "Unable to create key backup", - "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", - "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", - "Set up": "Set up", - "Don't ask again": "Don't ask again", - "New Recovery Method": "New Recovery Method", - "A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.", - "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", - "This device is encrypting history using the new recovery method.": "This device is encrypting history using the new recovery method.", - "Go to Settings": "Go to Settings", - "Set up Secure Messages": "Set up Secure Messages", - "Recovery Method Removed": "Recovery Method Removed", - "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.", - "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.", - "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", - "Failed to set direct chat tag": "Failed to set direct chat tag", - "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" -} From 590ff29e60bac1283c15c512a7afc5089e60de94 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 13:15:57 +0000 Subject: [PATCH 53/65] Unused import --- src/AsyncWrapper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js index 63b856a882..b7b81688e1 100644 --- a/src/AsyncWrapper.js +++ b/src/AsyncWrapper.js @@ -16,7 +16,6 @@ limitations under the License. */ import createReactClass from 'create-react-class'; -import Analytics from './Analytics'; import * as sdk from './index'; import PropTypes from 'prop-types'; import { _t } from './languageHandler'; From 66768416a628c10c0c80ecc7e0099c36e9b8a89b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 14:20:48 +0100 Subject: [PATCH 54/65] dont assume the room exist ... not sure why though --- src/utils/KeyVerificationStateObserver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.js index 7da532109c..a29d2ea1aa 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.js @@ -20,7 +20,7 @@ import { _t } from '../languageHandler'; export function getNameForEventRoom(userId, roomId) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); - const member = room.getMember(userId); + const member = room && room.getMember(userId); return member ? member.name : userId; } From 3534cd42023882785cc83c6222a1e478302f8131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 14:23:43 +0100 Subject: [PATCH 55/65] FilePanel: Add comments to explain what's going on with the event index. --- src/components/structures/FilePanel.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index e03c587e61..4c02f925fc 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -30,6 +30,8 @@ import { _t } from '../../languageHandler'; */ const FilePanel = createReactClass({ displayName: 'FilePanel', + // This is used to track if a decrypted event was a live event and should be + // added to the timeline. decryptingEvents: new Set(), propTypes: { @@ -84,6 +86,14 @@ const FilePanel = createReactClass({ if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + // The timelineSets filter makes sure that encrypted events that contain + // URLs never get added to the timeline, even if they are live events. + // These methods are here to manually listen for such events and add + // them despite the filter's best efforts. + // + // We do this only for encrypted rooms and if an event index exists, + // this could be made more general in the future or the filter logic + // could be fixed. if (EventIndexPeg.get() !== null) { client.on('Room.timeline', this.onRoomTimeline.bind(this)); client.on('Event.decrypted', this.onEventDecrypted.bind(this)); @@ -133,6 +143,10 @@ const FilePanel = createReactClass({ const room = client.getRoom(roomId); + // We override the pagination request for encrypted rooms so that we ask + // the event index to fulfill the pagination request. Asking the server + // to paginate won't ever work since the server can't correctly filter + // out events containing URLs if (client.isRoomEncrypted(roomId) && eventIndex !== null) { return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit); } else { @@ -153,6 +167,15 @@ const FilePanel = createReactClass({ try { timelineSet = await this.fetchFileEventsServer(room); + // If this room is encrypted the file panel won't be populated + // correctly since the defined filter doesn't support encrypted + // events and the server can't check if encrypted events contain + // URLs. + // + // This is where our event index comes into place, we ask the + // event index to populate the timelineSet for us. This call + // will add 10 events to the live timeline of the set. More can + // be requested using pagination. if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10); From 37f289b120ff23752204fd6a98b47b79b648df67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 14:24:25 +0100 Subject: [PATCH 56/65] EventIndex: Add docstrings for the FilePanel methods. --- src/indexing/EventIndex.js | 72 +++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 2b432ab1a1..b6e29c455d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -407,6 +407,27 @@ export default class EventIndex { return indexManager.searchEventIndex(searchArgs); } + /** + * Load events that contain URLs from the event index. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {number} limit The maximum number of events to fetch. + * + * @param {string} fromEvent From which event should we continue fetching + * events from the index. This is only needed if we're continuing to fill + * the timeline, e.g. if we're paginating. This needs to be set to a event + * id of an event that was previously fetched with this function. + * + * @param {string} direction The direction in which we will continue + * fetching events. EventTimeline.BACKWARDS to continue fetching events that + * are older than the event given in fromEvent, EventTimeline.FORWARDS to + * fetch newer events. + * + * @returns {Promise} Resolves to an array of events that + * contain URLs. + */ async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); @@ -472,6 +493,33 @@ export default class EventIndex { return matrixEvents; } + /** + * Fill a timeline with events that contain URLs. + * + * @param {TimelineSet} timelineSet The TimelineSet the Timeline belongs to, + * used to check if we're adding duplicate events. + * + * @param {Timeline} timeline The Timeline which should be filed with + * events. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {number} limit The maximum number of events to fetch. + * + * @param {string} fromEvent From which event should we continue fetching + * events from the index. This is only needed if we're continuing to fill + * the timeline, e.g. if we're paginating. This needs to be set to a event + * id of an event that was previously fetched with this function. + * + * @param {string} direction The direction in which we will continue + * fetching events. EventTimeline.BACKWARDS to continue fetching events that + * are older than the event given in fromEvent, EventTimeline.FORWARDS to + * fetch newer events. + * + * @returns {Promise} Resolves to true if events were added to the + * timeline, false otherwise. + */ async populateFileTimeline(timelineSet, timeline, room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); @@ -486,7 +534,7 @@ export default class EventIndex { direction = direction == EventTimeline.BACKWARDS ? EventTimeline.FORWARDS: EventTimeline.BACKWARDS; } - // Add the events to the live timeline of the file panel. + // Add the events to the timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS); @@ -503,6 +551,28 @@ export default class EventIndex { } } + /** + * Emulate a TimelineWindow pagination() request with the event index as the event source + * + * Might not fetch events from the index if the timeline already contains + * events that the window isn't showing. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {TimelineWindow} timelineWindow The timeline window that should be + * populated with new events. + * + * @param {string} direction The direction in which we should paginate. + * EventTimeline.BACKWARDS to paginate back, EventTimeline.FORWARDS to + * paginate forwards. + * + * @param {number} limit The maximum number of events to fetch while + * paginating. + * + * @returns {Promise} Resolves to a boolean which is true if more + * events were successfully retrieved. + */ paginateTimelineWindow(room, timelineWindow, direction, limit) { const tl = timelineWindow.getTimelineIndex(direction); From 75da5b7944ded503a50b099a2a467abec3c40858 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 24 Jan 2020 12:59:46 +0000 Subject: [PATCH 57/65] Design pass for room icons --- src/components/views/rooms/RoomList.js | 2 +- src/components/views/rooms/RoomTile.js | 6 +++--- src/i18n/strings/en_EN.json | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index ee3100b535..f41400ecfc 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -719,7 +719,7 @@ export default createReactClass({ }, { list: this.state.lists['im.vector.fake.direct'], - label: _t('People'), + label: _t('Direct Messages'), tagName: "im.vector.fake.direct", order: "recent", incomingCall: incomingCallIfTaggedAs('im.vector.fake.direct'), diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 0b50d85ff6..c546b7503e 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -478,7 +478,8 @@ export default createReactClass({ let dmIndicator; let dmOnline; - if (dmUserId) { + // If we can place a shield, do that instead + if (dmUserId && !this.state.e2eStatus) { dmIndicator = ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..1d5f534a36 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1012,7 +1012,7 @@ "Community Invites": "Community Invites", "Invites": "Invites", "Favourites": "Favourites", - "People": "People", + "Direct Messages": "Direct Messages", "Start chat": "Start chat", "Rooms": "Rooms", "Low priority": "Low priority", @@ -1471,7 +1471,6 @@ "Suggestions": "Suggestions", "Recently Direct Messaged": "Recently Direct Messaged", "Show more": "Show more", - "Direct Messages": "Direct Messages", "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", "Go": "Go", "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", From 098d09792215d4a1922a906e05e0ca23dc26d685 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:03:31 +0000 Subject: [PATCH 58/65] Re-enable stylelint on CI --- .buildkite/pipeline.yaml | 8 ++++++++ res/css/views/rooms/_RoomHeader.scss | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index f5f63b647a..1e08d2cd3b 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -25,6 +25,14 @@ steps: plugins: - docker#v3.0.1: image: "node:12" + - label: ":stylelint: Style Lint" + command: + - "echo '--- Install'" + - "yarn install --ignore-scripts" + - "yarn lint:style" + plugins: + - docker#v3.0.1: + image: "node:12" - label: ":jest: Tests" agents: diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 0ac2e99b97..6f0377b29c 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -24,7 +24,7 @@ limitations under the License. bottom: -1px; right: -2px; height: 10px; - width: 10px + width: 10px; } } From 5536384866eb72aa61500fcbd844b61c0b7a00d3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:21:28 +0000 Subject: [PATCH 59/65] Do less unnecessary work on CI We were checking out & installing the develop js-sdk explicitly in cases where we didn't need it at all. We were babeling the src folder many, many times over (in some cases twice in the same job) and never using the output at all. --- .buildkite/pipeline.yaml | 43 ++++++++++++++------------------------ scripts/ci/install-deps.sh | 4 ++-- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 1e08d2cd3b..39bba6637a 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -2,7 +2,8 @@ steps: - label: ":eslint: JS Lint" command: - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" + - "./scripts/ci/install-deps.sh --ignore-scripts" + - "echo '+++ Lint" - "yarn lint:js" plugins: - docker#v3.0.1: @@ -10,8 +11,9 @@ steps: - label: ":eslint: TS Lint" command: - - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" + - "echo '--- Install" + - "yarn install --ignore-scripts" + - "echo '+++ Lint" - "yarn lint:ts" plugins: - docker#v3.0.1: @@ -19,8 +21,9 @@ steps: - label: ":eslint: Types Lint" command: - - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" + - "echo '--- Install" + - "yarn install --ignore-scripts" + - "echo '+++ Lint" - "yarn lint:types" plugins: - docker#v3.0.1: @@ -41,13 +44,10 @@ steps: queue: "medium" command: - "echo '--- Install js-sdk'" - # TODO: Remove hacky chmod for BuildKite - - "chmod +x ./scripts/ci/*.sh" - - "chmod +x ./scripts/*" - - "echo '--- Installing Dependencies'" - - "./scripts/ci/install-deps.sh" - - "echo '--- Running initial build steps'" - - "yarn build" + # We don't use the babel-ed output for anything so we can --ignore-scripts + # to save transpiling the files. We run the transpile step explicitly in + # the 'build' job. + - "./scripts/ci/install-deps.sh --ignore-scripts" - "echo '+++ Running Tests'" - "yarn test" plugins: @@ -56,10 +56,8 @@ steps: - label: "🛠 Build" command: - - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" - - "echo '+++ Building Project'" - - "yarn build" + - "echo '+++ Install & Build'" + - "yarn install" plugins: - docker#v3.0.1: image: "node:12" @@ -70,14 +68,8 @@ steps: # e2e tests otherwise take +-8min queue: "xlarge" command: - # TODO: Remove hacky chmod for BuildKite - - "echo '--- Setup'" - - "chmod +x ./scripts/ci/*.sh" - - "chmod +x ./scripts/*" - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" - - "echo '--- Running initial build steps'" - - "yarn build" + - "./scripts/ci/install-deps.sh --ignore-scripts" - "echo '+++ Running Tests'" - "./scripts/ci/end-to-end-tests.sh" plugins: @@ -96,9 +88,6 @@ steps: # webpack loves to gorge itself on resources. queue: "medium" command: - # TODO: Remove hacky chmod for BuildKite - - "chmod +x ./scripts/ci/*.sh" - - "chmod +x ./scripts/*" - "echo '+++ Running Tests'" - "./scripts/ci/riot-unit-tests.sh" plugins: @@ -110,7 +99,7 @@ steps: - label: "🌐 i18n" command: - "echo '--- Fetching Dependencies'" - - "yarn install" + - "yarn install --ignore-scripts" - "echo '+++ Testing i18n output'" - "yarn diff-i18n" plugins: diff --git a/scripts/ci/install-deps.sh b/scripts/ci/install-deps.sh index a2e2e59a45..14b5fc5393 100755 --- a/scripts/ci/install-deps.sh +++ b/scripts/ci/install-deps.sh @@ -6,9 +6,9 @@ scripts/fetchdep.sh matrix-org matrix-js-sdk pushd matrix-js-sdk yarn link -yarn install +yarn install $@ yarn build popd yarn link matrix-js-sdk -yarn install +yarn install $@ From 57ee99d6f797115d051914d8513b262ca50441e2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:30:41 +0000 Subject: [PATCH 60/65] chmod --- scripts/ci/layered-riot-web.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/ci/layered-riot-web.sh diff --git a/scripts/ci/layered-riot-web.sh b/scripts/ci/layered-riot-web.sh old mode 100644 new mode 100755 From 19c49eedec75ce21e7ecc982ec209b6b4203b2bc Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:36:12 +0000 Subject: [PATCH 61/65] reskindex for tests --- .buildkite/pipeline.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 39bba6637a..7dbb959dea 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -48,6 +48,7 @@ steps: # to save transpiling the files. We run the transpile step explicitly in # the 'build' job. - "./scripts/ci/install-deps.sh --ignore-scripts" + - "yarn run reskindex" - "echo '+++ Running Tests'" - "yarn test" plugins: From de5c47960c8a34f7412dcf0c836349348378e65a Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:51:11 +0000 Subject: [PATCH 62/65] quotes --- .buildkite/pipeline.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 7dbb959dea..698ce43c93 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -3,7 +3,7 @@ steps: command: - "echo '--- Install js-sdk'" - "./scripts/ci/install-deps.sh --ignore-scripts" - - "echo '+++ Lint" + - "echo '+++ Lint'" - "yarn lint:js" plugins: - docker#v3.0.1: @@ -13,7 +13,7 @@ steps: command: - "echo '--- Install" - "yarn install --ignore-scripts" - - "echo '+++ Lint" + - "echo '+++ Lint'" - "yarn lint:ts" plugins: - docker#v3.0.1: @@ -21,9 +21,9 @@ steps: - label: ":eslint: Types Lint" command: - - "echo '--- Install" + - "echo '--- Install'" - "yarn install --ignore-scripts" - - "echo '+++ Lint" + - "echo '+++ Lint'" - "yarn lint:types" plugins: - docker#v3.0.1: From b17f38856ca5f49efe593aa92755583cdc5058c9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:56:00 +0000 Subject: [PATCH 63/65] More quote failing --- .buildkite/pipeline.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 698ce43c93..842ea091fb 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -11,7 +11,7 @@ steps: - label: ":eslint: TS Lint" command: - - "echo '--- Install" + - "echo '--- Install'" - "yarn install --ignore-scripts" - "echo '+++ Lint'" - "yarn lint:ts" From 26aaa58e3cc2c05ceeb239a753747cde6f720d34 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:58:46 +0000 Subject: [PATCH 64/65] Comments are great --- .buildkite/pipeline.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 842ea091fb..4bc69a76bd 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -1,6 +1,7 @@ steps: - label: ":eslint: JS Lint" command: + # We fetch the develop js-sdk to get our latest eslint rules - "echo '--- Install js-sdk'" - "./scripts/ci/install-deps.sh --ignore-scripts" - "echo '+++ Lint'" From abc2808b62d1cacb94d7a4e2403c5ee5f74faab8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 24 Jan 2020 08:57:03 -0700 Subject: [PATCH 65/65] Fix i18n post-merge --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3c758ecbfb..eb06518c7a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1475,7 +1475,6 @@ "Suggestions": "Suggestions", "Recently Direct Messaged": "Recently Direct Messaged", "Show more": "Show more", - "Direct Messages": "Direct Messages", "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.", "Go": "Go", "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.",