diff --git a/CHANGELOG.md b/CHANGELOG.md index 8483e2a0a2..5e35c20d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,123 @@ +Changes in [0.12.9](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.9) (2018-07-09) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.9-rc.2...v0.12.9) + + * No changes since rc.1 + +Changes in [0.12.9-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.9-rc.2) (2018-07-06) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.9-rc.1...v0.12.9-rc.2) + + * Implement aggregation by error type for tracked decryption failures + [\#2045](https://github.com/matrix-org/matrix-react-sdk/pull/2045) + * make new hiding of roomsublist behaviour opt-in + [\#2044](https://github.com/matrix-org/matrix-react-sdk/pull/2044) + * Implement aggregation by error type for tracked decryption failures + [\#2043](https://github.com/matrix-org/matrix-react-sdk/pull/2043) + * make new hiding of roomsublist behaviour opt-in + [\#2030](https://github.com/matrix-org/matrix-react-sdk/pull/2030) + +Changes in [0.12.9-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.9-rc.1) (2018-07-04) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.8...v0.12.9-rc.1) + + * Update from Weblate. + [\#2040](https://github.com/matrix-org/matrix-react-sdk/pull/2040) + * Import react as React in src/components/views/messages/MStickerBody.js + [\#2039](https://github.com/matrix-org/matrix-react-sdk/pull/2039) + * Import react as React in src/GroupAddressPicker.js + [\#2038](https://github.com/matrix-org/matrix-react-sdk/pull/2038) + * Give PersistedElement a key + [\#2036](https://github.com/matrix-org/matrix-react-sdk/pull/2036) + * Revert " make click to insert nick work on join/parts, /me's etc" + [\#2034](https://github.com/matrix-org/matrix-react-sdk/pull/2034) + * Track an event name when tracking a decryption failure + [\#2033](https://github.com/matrix-org/matrix-react-sdk/pull/2033) + * warn on self-mute + [\#1974](https://github.com/matrix-org/matrix-react-sdk/pull/1974) + * make click to insert nick work on join/parts, /me's etc + [\#1945](https://github.com/matrix-org/matrix-react-sdk/pull/1945) + * Fix layout bug introduced by #2025 + [\#2029](https://github.com/matrix-org/matrix-react-sdk/pull/2029) + * Fix room topics/names resetting when UserSetting re-renders + [\#2028](https://github.com/matrix-org/matrix-react-sdk/pull/2028) + * Improve tracking of UISIs + [\#2027](https://github.com/matrix-org/matrix-react-sdk/pull/2027) + * Replace share icons + [\#2026](https://github.com/matrix-org/matrix-react-sdk/pull/2026) + * Improve status bar errors (namely the consent error) + [\#2025](https://github.com/matrix-org/matrix-react-sdk/pull/2025) + * Fix incorrectly positioned copy button on `
` blocks
+   [\#2023](https://github.com/matrix-org/matrix-react-sdk/pull/2023)
+ * Redact pathnames with origin `file://`
+   [\#2018](https://github.com/matrix-org/matrix-react-sdk/pull/2018)
+ * Update package-lock.json
+   [\#2022](https://github.com/matrix-org/matrix-react-sdk/pull/2022)
+ * on room sub list badge click goto first relevant room
+   [\#2021](https://github.com/matrix-org/matrix-react-sdk/pull/2021)
+ * improve linkifier AGAIN
+   [\#2020](https://github.com/matrix-org/matrix-react-sdk/pull/2020)
+ * fix historical section
+   [\#2016](https://github.com/matrix-org/matrix-react-sdk/pull/2016)
+ * Fix RoomSubList headers by re-commiting 1faecfd
+   [\#2014](https://github.com/matrix-org/matrix-react-sdk/pull/2014)
+ * don't fire share dialog when clicking timestamp of event,
+   [\#2017](https://github.com/matrix-org/matrix-react-sdk/pull/2017)
+ * Revert "affix copyButton so that it doesn't get scrolled horizontally"
+   [\#2013](https://github.com/matrix-org/matrix-react-sdk/pull/2013)
+ * when the user switches room, close room settings
+   [\#2019](https://github.com/matrix-org/matrix-react-sdk/pull/2019)
+ * Refactor widgets code
+   [\#2015](https://github.com/matrix-org/matrix-react-sdk/pull/2015)
+ * Login local errors for blank fields
+   [\#2009](https://github.com/matrix-org/matrix-react-sdk/pull/2009)
+ * Update lolex to 2.7.0
+   [\#1917](https://github.com/matrix-org/matrix-react-sdk/pull/1917)
+ * Improve Linkifier
+   [\#2011](https://github.com/matrix-org/matrix-react-sdk/pull/2011)
+ * use enum constants for EventStatus and correct isSent check
+   [\#2010](https://github.com/matrix-org/matrix-react-sdk/pull/2010)
+ * accent insensitive autocomplete
+   [\#2007](https://github.com/matrix-org/matrix-react-sdk/pull/2007)
+ * default to not showing url previews in e2ee rooms.
+   [\#2001](https://github.com/matrix-org/matrix-react-sdk/pull/2001)
+ * allow chaining right click contextmenus
+   [\#1999](https://github.com/matrix-org/matrix-react-sdk/pull/1999)
+ * hide empty roomsublists when filtering via search/tagpanel
+   [\#1954](https://github.com/matrix-org/matrix-react-sdk/pull/1954)
+ * prevent user,room,group autocomplete firing mid-word
+   [\#2012](https://github.com/matrix-org/matrix-react-sdk/pull/2012)
+ * fix instances of composer not getting/regaining focus
+   [\#2008](https://github.com/matrix-org/matrix-react-sdk/pull/2008)
+ * notif panel fixes
+   [\#2006](https://github.com/matrix-org/matrix-react-sdk/pull/2006)
+ * factor out conditional LanguageSelector as functional component
+   [\#2003](https://github.com/matrix-org/matrix-react-sdk/pull/2003)
+ * Autocomplete and Pillify Communities
+   [\#1993](https://github.com/matrix-org/matrix-react-sdk/pull/1993)
+ * Very basic Jitsi integration
+   [\#1971](https://github.com/matrix-org/matrix-react-sdk/pull/1971)
+ * add additional classes which protect the text from overflowing
+   [\#1994](https://github.com/matrix-org/matrix-react-sdk/pull/1994)
+ * Upload File confirmation modal steals focus, send it back to composer
+   [\#1992](https://github.com/matrix-org/matrix-react-sdk/pull/1992)
+ * delint MImageBody, fixes anonymous class and hyphenated style keys which
+   made react cry
+   [\#1991](https://github.com/matrix-org/matrix-react-sdk/pull/1991)
+ * allow using tab to navigate room list in a smarter way
+   [\#1977](https://github.com/matrix-org/matrix-react-sdk/pull/1977)
+ * fix no displayname usersettings
+   [\#1990](https://github.com/matrix-org/matrix-react-sdk/pull/1990)
+ * trigger TagTile context menu on right click
+   [\#1989](https://github.com/matrix-org/matrix-react-sdk/pull/1989)
+ * hide already chosen results from AddressPickerDialog
+   [\#2000](https://github.com/matrix-org/matrix-react-sdk/pull/2000)
+ * delint ChatCreateOrReuseDialog
+   [\#2002](https://github.com/matrix-org/matrix-react-sdk/pull/2002)
+ * fix set password & email flow possible to get stuck and onBlur murdering
+   your email
+   [\#1982](https://github.com/matrix-org/matrix-react-sdk/pull/1982)
+
 Changes in [0.12.8](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.8) (2018-06-29)
 =====================================================================================================
 [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.8-rc.2...v0.12.8)
diff --git a/package.json b/package.json
index 47a8f2c47a..8c0ec922f2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "matrix-react-sdk",
-  "version": "0.12.8",
+  "version": "0.12.9",
   "description": "SDK for matrix.org using React",
   "author": "matrix.org",
   "repository": {
@@ -76,7 +76,7 @@
     "linkifyjs": "^2.1.6",
     "lodash": "^4.13.1",
     "lolex": "2.3.2",
-    "matrix-js-sdk": "0.10.5",
+    "matrix-js-sdk": "0.10.6",
     "optimist": "^0.6.1",
     "pako": "^1.0.5",
     "prop-types": "^15.5.8",
diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index d95ed2ded0..737f22a0ac 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -31,7 +31,6 @@ limitations under the License.
     top: 14px;
     left: 8px;
     cursor: pointer;
-    z-index: 2;
 }
 
 .mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
diff --git a/src/DecryptionFailureTracker.js b/src/DecryptionFailureTracker.js
index e7809d2f6c..b02a5e937b 100644
--- a/src/DecryptionFailureTracker.js
+++ b/src/DecryptionFailureTracker.js
@@ -14,22 +14,25 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-class DecryptionFailure {
-    constructor(failedEventId) {
+export class DecryptionFailure {
+    constructor(failedEventId, errorCode) {
         this.failedEventId = failedEventId;
+        this.errorCode = errorCode;
         this.ts = Date.now();
     }
 }
 
-export default class DecryptionFailureTracker {
+export class DecryptionFailureTracker {
     // Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list
     // is checked for failures that happened > `GRACE_PERIOD_MS` ago. Those that did
-    // are added to `failuresToTrack`.
+    // are accumulated in `failureCounts`.
     failures = [];
 
-    // Every TRACK_INTERVAL_MS (so as to spread the number of hits done on Analytics),
-    // one DecryptionFailure of this FIFO is removed and tracked.
-    failuresToTrack = [];
+    // A histogram of the number of failures that will be tracked at the next tracking
+    // interval, split by failure error code.
+    failureCounts = {
+        // [errorCode]: 42
+    };
 
     // Event IDs of failures that were tracked previously
     trackedEventHashMap = {
@@ -46,16 +49,35 @@ export default class DecryptionFailureTracker {
     // Call `checkFailures` every `CHECK_INTERVAL_MS`.
     static CHECK_INTERVAL_MS = 5000;
 
-    // Give events a chance to be decrypted by waiting `GRACE_PERIOD_MS` before moving
-    // the failure to `failuresToTrack`.
+    // Give events a chance to be decrypted by waiting `GRACE_PERIOD_MS` before counting
+    // the failure in `failureCounts`.
     static GRACE_PERIOD_MS = 60000;
 
-    constructor(fn) {
+    /**
+     * Create a new DecryptionFailureTracker.
+     *
+     * Call `eventDecrypted(event, err)` on this instance when an event is decrypted.
+     *
+     * Call `start()` to start the tracker, and `stop()` to stop tracking.
+     *
+     * @param {function} fn The tracking function, which will be called when failures
+     * are tracked. The function should have a signature `(count, trackedErrorCode) => {...}`,
+     * where `count` is the number of failures and `errorCode` matches the `.code` of
+     * provided DecryptionError errors (by default, unless `errorCodeMapFn` is specified.
+     * @param {function?} errorCodeMapFn The function used to map error codes to the
+     * trackedErrorCode. If not provided, the `.code` of errors will be used.
+     */
+    constructor(fn, errorCodeMapFn) {
         if (!fn || typeof fn !== 'function') {
             throw new Error('DecryptionFailureTracker requires tracking function');
         }
 
-        this.trackDecryptionFailure = fn;
+        if (errorCodeMapFn && typeof errorCodeMapFn !== 'function') {
+            throw new Error('DecryptionFailureTracker second constructor argument should be a function');
+        }
+
+        this._trackDecryptionFailure = fn;
+        this._mapErrorCode = errorCodeMapFn;
     }
 
     // loadTrackedEventHashMap() {
@@ -66,17 +88,17 @@ export default class DecryptionFailureTracker {
     //     localStorage.setItem('mx-decryption-failure-event-id-hashes', JSON.stringify(this.trackedEventHashMap));
     // }
 
-    eventDecrypted(e) {
-        if (e.isDecryptionFailure()) {
-            this.addDecryptionFailureForEvent(e);
+    eventDecrypted(e, err) {
+        if (err) {
+            this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code));
         } else {
             // Could be an event in the failures, remove it
             this.removeDecryptionFailuresForEvent(e);
         }
     }
 
-    addDecryptionFailureForEvent(e) {
-        this.failures.push(new DecryptionFailure(e.getId()));
+    addDecryptionFailure(failure) {
+        this.failures.push(failure);
     }
 
     removeDecryptionFailuresForEvent(e) {
@@ -93,7 +115,7 @@ export default class DecryptionFailureTracker {
         );
 
         this.trackInterval = setInterval(
-            () => this.trackFailure(),
+            () => this.trackFailures(),
             DecryptionFailureTracker.TRACK_INTERVAL_MS,
         );
     }
@@ -106,7 +128,7 @@ export default class DecryptionFailureTracker {
         clearInterval(this.trackInterval);
 
         this.failures = [];
-        this.failuresToTrack = [];
+        this.failureCounts = {};
     }
 
     /**
@@ -153,20 +175,28 @@ export default class DecryptionFailureTracker {
 
         const dedupedFailures = dedupedFailuresMap.values();
 
-        this.failuresToTrack = [...this.failuresToTrack, ...dedupedFailures];
+        this._aggregateFailures(dedupedFailures);
+    }
+
+    _aggregateFailures(failures) {
+        for (const failure of failures) {
+            const errorCode = failure.errorCode;
+            this.failureCounts[errorCode] = (this.failureCounts[errorCode] || 0) + 1;
+        }
     }
 
     /**
      * If there are failures that should be tracked, call the given trackDecryptionFailure
      * function with the number of failures that should be tracked.
      */
-    trackFailure() {
-        if (this.failuresToTrack.length > 0) {
-            // Remove all failures, and expose the number of failures for now.
-            //
-            // TODO: Track a histogram of error types to cardinailty to allow for
-            // aggregation by error type.
-            this.trackDecryptionFailure(this.failuresToTrack.splice(0).length);
+    trackFailures() {
+        for (const errorCode of Object.keys(this.failureCounts)) {
+            if (this.failureCounts[errorCode] > 0) {
+                const trackedErrorCode = this._mapErrorCode ? this._mapErrorCode(errorCode) : errorCode;
+
+                this._trackDecryptionFailure(this.failureCounts[errorCode], trackedErrorCode);
+                this.failureCounts[errorCode] = 0;
+            }
         }
     }
 }
diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js
index 792fd73733..ea7eeba756 100644
--- a/src/FromWidgetPostMessageApi.js
+++ b/src/FromWidgetPostMessageApi.js
@@ -18,6 +18,7 @@ import URL from 'url';
 import dis from './dispatcher';
 import IntegrationManager from './IntegrationManager';
 import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
+import ActiveWidgetStore from './stores/ActiveWidgetStore';
 
 const WIDGET_API_VERSION = '0.0.1'; // Current API version
 const SUPPORTED_WIDGET_API_VERSIONS = [
@@ -155,6 +156,14 @@ export default class FromWidgetPostMessageApi {
             const integType = (data && data.integType) ? data.integType : null;
             const integId = (data && data.integId) ? data.integId : null;
             IntegrationManager.open(integType, integId);
+        } else if (action === 'set_always_on_screen') {
+            // This is a new message: there is no reason to support the deprecated widgetData here
+            const data = event.data.data;
+            const val = data.value;
+
+            if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
+                ActiveWidgetStore.setWidgetPersistence(widgetId, val);
+            }
         } else {
             console.warn('Widget postMessage event unhandled');
             this.sendError(event, {message: 'The postMessage was unhandled'});
diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js
index 91380b6eed..532ee23c25 100644
--- a/src/GroupAddressPicker.js
+++ b/src/GroupAddressPicker.js
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+import React from 'react';
 import Modal from './Modal';
 import sdk from './';
 import MultiInviter from './utils/MultiInviter';
diff --git a/src/Notifier.js b/src/Notifier.js
index b823c4df05..80e8be1084 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -170,15 +170,15 @@ const Notifier = {
                     value: true,
                 });
             });
-            // clear the notifications_hidden flag, so that if notifications are
-            // disabled again in the future, we will show the banner again.
-            this.setToolbarHidden(true);
         } else {
             dis.dispatch({
                 action: "notifier_enabled",
                 value: false,
             });
         }
+        // set the notifications_hidden flag, as the user has knowingly interacted
+        // with the setting we shouldn't nag them any further
+        this.setToolbarHidden(true);
     },
 
     isEnabled: function() {
diff --git a/src/TextForEvent.js b/src/TextForEvent.js
index 712150af4d..15c67526d9 100644
--- a/src/TextForEvent.js
+++ b/src/TextForEvent.js
@@ -129,6 +129,64 @@ function textForRoomNameEvent(ev) {
     });
 }
 
+function textForServerACLEvent(ev) {
+    const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
+    const prevContent = ev.getPrevContent();
+    const changes = [];
+    const current = ev.getContent();
+    const prev = {
+        deny: Array.isArray(prevContent.deny) ? prevContent.deny : [],
+        allow: Array.isArray(prevContent.allow) ? prevContent.allow : [],
+        allow_ip_literals: !(prevContent.allow_ip_literals === false),
+    };
+    let text = "";
+    if (prev.deny.length === 0 && prev.allow.length === 0) {
+        text = `${senderDisplayName} set server ACLs for this room: `;
+    } else {
+        text = `${senderDisplayName} changed the server ACLs for this room: `;
+    }
+
+    if (!Array.isArray(current.allow)) {
+        current.allow = [];
+    }
+    /* If we know for sure everyone is banned, don't bother showing the diff view */
+    if (current.allow.length === 0) {
+        return text + "🎉 All servers are banned from participating! This room can no longer be used.";
+    }
+
+    if (!Array.isArray(current.deny)) {
+        current.deny = [];
+    }
+
+    const bannedServers = current.deny.filter((srv) => typeof(srv) === 'string' && !prev.deny.includes(srv));
+    const unbannedServers = prev.deny.filter((srv) => typeof(srv) === 'string' && !current.deny.includes(srv));
+    const allowedServers = current.allow.filter((srv) => typeof(srv) === 'string' && !prev.allow.includes(srv));
+    const unallowedServers = prev.allow.filter((srv) => typeof(srv) === 'string' && !current.allow.includes(srv));
+
+    if (bannedServers.length > 0) {
+        changes.push(`Servers matching ${bannedServers.join(", ")} are now banned.`);
+    }
+
+    if (unbannedServers.length > 0) {
+        changes.push(`Servers matching ${unbannedServers.join(", ")} were removed from the ban list.`);
+    }
+
+    if (allowedServers.length > 0) {
+        changes.push(`Servers matching ${allowedServers.join(", ")} are now allowed.`);
+    }
+
+    if (unallowedServers.length > 0) {
+        changes.push(`Servers matching ${unallowedServers.join(", ")} were removed from the allowed list.`);
+    }
+
+    if (prev.allow_ip_literals !== current.allow_ip_literals) {
+        const allowban = current.allow_ip_literals ? "allowed" : "banned";
+        changes.push(`Participating from a server using an IP literal hostname is now ${allowban}.`);
+    }
+
+    return text + changes.join(" ");
+}
+
 function textForMessageEvent(ev) {
     const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
     let message = senderDisplayName + ': ' + ev.getContent().body;
@@ -309,6 +367,7 @@ const stateHandlers = {
     'm.room.encryption': textForEncryptionEvent,
     'm.room.power_levels': textForPowerEvent,
     'm.room.pinned_events': textForPinnedEvent,
+    'm.room.server_acl': textForServerACLEvent,
 
     'im.vector.modular.widgets': textForWidgetEvent,
 };
diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js
index adc8dfd11c..7295fd45d3 100644
--- a/src/components/structures/ContextualMenu.js
+++ b/src/components/structures/ContextualMenu.js
@@ -220,7 +220,8 @@ export default class ContextualMenu extends React.Component {
                 { chevron }
                 
             
-            { props.hasBackground && 
} + { props.hasBackground &&
}
; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 9ea0cb42c8..e0bbf50d5a 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -23,7 +23,7 @@ import PropTypes from 'prop-types'; import Matrix from "matrix-js-sdk"; import Analytics from "../../Analytics"; -import DecryptionFailureTracker from "../../DecryptionFailureTracker"; +import { DecryptionFailureTracker } from "../../DecryptionFailureTracker"; import MatrixClientPeg from "../../MatrixClientPeg"; import PlatformPeg from "../../PlatformPeg"; import SdkConfig from "../../SdkConfig"; @@ -1304,9 +1304,20 @@ export default React.createClass({ } }); - const dft = new DecryptionFailureTracker((total) => { - // TODO: Pass reason for failure as third argument to trackEvent - Analytics.trackEvent('E2E', 'Decryption failure', 'unspecified_error', total); + const dft = new DecryptionFailureTracker((total, errorCode) => { + Analytics.trackEvent('E2E', 'Decryption failure', errorCode, total); + }, (errorCode) => { + // Map JS-SDK error codes to tracker codes for aggregation + switch (errorCode) { + case 'MEGOLM_UNKNOWN_INBOUND_SESSION_ID': + return 'olm_keys_not_sent_error'; + case 'OLM_UNKNOWN_MESSAGE_INDEX': + return 'olm_index_error'; + case undefined: + return 'unexpected_error'; + default: + return 'unspecified_error'; + } }); // Shelved for later date when we have time to think about persisting history of @@ -1317,7 +1328,7 @@ export default React.createClass({ // When logging out, stop tracking failures and destroy state cli.on("Session.logged_out", () => dft.stop()); - cli.on("Event.decrypted", (e) => dft.eventDecrypted(e)); + cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err)); const krh = new KeyRequestHandler(cli); cli.on("crypto.roomKeyRequest", (req) => { diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 0c78043a42..d798070659 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -27,6 +27,7 @@ import * as RoomNotifs from '../../RoomNotifs'; import * as FormattingUtils from '../../utils/FormattingUtils'; import { KeyCode } from '../../Keyboard'; import { Group } from 'matrix-js-sdk'; +import PropTypes from 'prop-types'; // turn this on for drop & drag console debugging galore @@ -40,27 +41,28 @@ const RoomSubList = React.createClass({ debug: debug, propTypes: { - list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - label: React.PropTypes.string.isRequired, - tagName: React.PropTypes.string, - editable: React.PropTypes.bool, + list: PropTypes.arrayOf(PropTypes.object).isRequired, + label: PropTypes.string.isRequired, + tagName: PropTypes.string, + editable: PropTypes.bool, - order: React.PropTypes.string.isRequired, + order: PropTypes.string.isRequired, // passed through to RoomTile and used to highlight room with `!` regardless of notifications count - isInvite: React.PropTypes.bool, + isInvite: PropTypes.bool, - startAsHidden: React.PropTypes.bool, - showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded - collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed? - onHeaderClick: React.PropTypes.func, - alwaysShowHeader: React.PropTypes.bool, - incomingCall: React.PropTypes.object, - onShowMoreRooms: React.PropTypes.func, - searchFilter: React.PropTypes.string, - emptyContent: React.PropTypes.node, // content shown if the list is empty - headerItems: React.PropTypes.node, // content shown in the sublist header - extraTiles: React.PropTypes.arrayOf(React.PropTypes.node), // extra elements added beneath tiles + startAsHidden: PropTypes.bool, + showSpinner: PropTypes.bool, // true to show a spinner if 0 elements when expanded + collapsed: PropTypes.bool.isRequired, // is LeftPanel collapsed? + onHeaderClick: PropTypes.func, + alwaysShowHeader: PropTypes.bool, + incomingCall: PropTypes.object, + onShowMoreRooms: PropTypes.func, + searchFilter: PropTypes.string, + emptyContent: PropTypes.node, // content shown if the list is empty + headerItems: PropTypes.node, // content shown in the sublist header + extraTiles: PropTypes.arrayOf(PropTypes.node), // extra elements added beneath tiles + showEmpty: PropTypes.bool, }, getInitialState: function() { @@ -79,6 +81,7 @@ const RoomSubList = React.createClass({ }, // NOP extraTiles: [], isInvite: false, + showEmpty: true, }; }, @@ -392,17 +395,29 @@ const RoomSubList = React.createClass({ const TruncatedList = sdk.getComponent('elements.TruncatedList'); let content; - if (this.state.sortedList.length === 0 && this.props.extraTiles.length === 0) { - // if no search filter is applied and there is a placeholder defined then show it, otherwise show nothing - if (!this.props.searchFilter && this.props.emptyContent) { + + if (this.props.showEmpty) { + // this is new behaviour with still controversial UX in that in hiding RoomSubLists the drop zones for DnD + // are also gone so when filtering users can't DnD rooms to some tags but is a lot cleaner otherwise. + if (this.state.sortedList.length === 0 && !this.props.searchFilter && this.props.extraTiles.length === 0) { content = this.props.emptyContent; } else { - // don't show an empty sublist - return null; + content = this.makeRoomTiles(); + content.push(...this.props.extraTiles); } } else { - content = this.makeRoomTiles(); - content.push(...this.props.extraTiles); + if (this.state.sortedList.length === 0 && this.props.extraTiles.length === 0) { + // if no search filter is applied and there is a placeholder defined then show it, otherwise show nothing + if (!this.props.searchFilter && this.props.emptyContent) { + content = this.props.emptyContent; + } else { + // don't show an empty sublist + return null; + } + } else { + content = this.makeRoomTiles(); + content.push(...this.props.extraTiles); + } } if (this.state.sortedList.length > 0 || this.props.extraTiles.length > 0 || this.props.editable) { diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 6397e73434..d02d8b23e5 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -81,6 +81,7 @@ const SIMPLE_SETTINGS = [ { id: "VideoView.flipVideoHorizontally" }, { id: "TagPanel.disableTagPanel" }, { id: "enableWidgetScreenshots" }, + { id: "RoomSubList.showEmpty" }, ]; // These settings must be defined in SettingsStore diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.js index 04f99a0e15..6e14d1cf66 100644 --- a/src/components/views/dialogs/CreateGroupDialog.js +++ b/src/components/views/dialogs/CreateGroupDialog.js @@ -56,7 +56,7 @@ export default React.createClass({ _checkGroupId: function(e) { let error = null; if (!this.state.groupId) { - error = _t("Community IDs cannot not be empty."); + error = _t("Community IDs cannot be empty."); } else if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) { error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'"); } diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js index 1566302e42..3078944b62 100644 --- a/src/components/views/dialogs/DevtoolsDialog.js +++ b/src/components/views/dialogs/DevtoolsDialog.js @@ -242,6 +242,9 @@ class SendAccountData extends GenericEditor { } } +const INITIAL_LOAD_TILES = 20; +const LOAD_TILES_STEP_SIZE = 50; + class FilteredList extends React.Component { static propTypes = { children: PropTypes.any, @@ -249,31 +252,65 @@ class FilteredList extends React.Component { onChange: PropTypes.func, }; + static filterChildren(children, query) { + if (!query) return children; + const lcQuery = query.toLowerCase(); + return children.filter((child) => child.key.toLowerCase().includes(lcQuery)); + } + constructor(props, context) { super(props, context); - this.onQuery = this.onQuery.bind(this); + + this.state = { + filteredChildren: FilteredList.filterChildren(this.props.children, this.props.query), + truncateAt: INITIAL_LOAD_TILES, + }; } - onQuery(ev) { + componentWillReceiveProps(nextProps) { + if (this.props.children === nextProps.children && this.props.query === nextProps.query) return; + this.setState({ + filteredChildren: FilteredList.filterChildren(nextProps.children, nextProps.query), + truncateAt: INITIAL_LOAD_TILES, + }); + } + + showAll = () => { + this.setState({ + truncateAt: this.state.truncateAt + LOAD_TILES_STEP_SIZE, + }); + }; + + createOverflowElement = (overflowCount: number, totalCount: number) => { + return ; + }; + + onQuery = (ev) => { if (this.props.onChange) this.props.onChange(ev.target.value); - } + }; - filterChildren() { - if (this.props.query) { - const lowerQuery = this.props.query.toLowerCase(); - return this.props.children.filter((child) => child.key.toLowerCase().includes(lowerQuery)); - } - return this.props.children; - } + getChildren = (start: number, end: number) => { + return this.state.filteredChildren.slice(start, end); + }; + + getChildCount = (): number => { + return this.state.filteredChildren.length; + }; render() { + const TruncatedList = sdk.getComponent("elements.TruncatedList"); return
- { this.filterChildren() } +
; } } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 1eaa1837ed..12be1b3669 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -1,5 +1,6 @@ /** Copyright 2017 Vector Creations Ltd +Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -33,6 +34,7 @@ import AppWarning from './AppWarning'; import MessageSpinner from './MessageSpinner'; import WidgetUtils from '../../../utils/WidgetUtils'; import dis from '../../../dispatcher'; +import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -40,9 +42,13 @@ const ENABLE_REACT_PERF = false; export default class AppTile extends React.Component { constructor(props) { super(props); + + // The key used for PersistedElement + this._persistKey = 'widget_' + this.props.id; + this.state = this._getNewState(props); - this._onWidgetAction = this._onWidgetAction.bind(this); + this._onAction = this._onAction.bind(this); this._onMessage = this._onMessage.bind(this); this._onLoaded = this._onLoaded.bind(this); this._onEditClick = this._onEditClick.bind(this); @@ -50,7 +56,6 @@ export default class AppTile extends React.Component { this._onSnapshotClick = this._onSnapshotClick.bind(this); this.onClickMenuBar = this.onClickMenuBar.bind(this); this._onMinimiseClick = this._onMinimiseClick.bind(this); - this._onInitialLoad = this._onInitialLoad.bind(this); this._grantWidgetPermission = this._grantWidgetPermission.bind(this); this._revokeWidgetPermission = this._revokeWidgetPermission.bind(this); this._onPopoutWidgetClick = this._onPopoutWidgetClick.bind(this); @@ -66,9 +71,12 @@ export default class AppTile extends React.Component { _getNewState(newProps) { const widgetPermissionId = [newProps.room.roomId, encodeURIComponent(newProps.url)].join('_'); const hasPermissionToLoad = localStorage.getItem(widgetPermissionId); + + const PersistedElement = sdk.getComponent("elements.PersistedElement"); return { initialising: true, // True while we are mangling the widget URL - loading: this.props.waitForIframeLoad, // True while the iframe content is loading + // True while the iframe content is loading + loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey), widgetUrl: this._addWurlParams(newProps.url), widgetPermissionId: widgetPermissionId, // Assume that widget has permission to load if we are the user who @@ -77,9 +85,6 @@ export default class AppTile extends React.Component { error: null, deleting: false, widgetPageTitle: newProps.widgetPageTitle, - allowedCapabilities: (this.props.whitelistCapabilities && this.props.whitelistCapabilities.length > 0) ? - this.props.whitelistCapabilities : [], - requestedCapabilities: [], }; } @@ -89,7 +94,7 @@ export default class AppTile extends React.Component { * @return {Boolean} True if capability supported */ _hasCapability(capability) { - return this.state.allowedCapabilities.some((c) => {return c === capability;}); + return ActiveWidgetStore.widgetHasCapability(this.props.id, capability); } /** @@ -142,30 +147,24 @@ export default class AppTile extends React.Component { window.addEventListener('message', this._onMessage, false); // Widget action listeners - this.dispatcherRef = dis.register(this._onWidgetAction); - } - - componentDidUpdate() { - // Allow parents to access widget messaging - if (this.props.collectWidgetMessaging) { - this.props.collectWidgetMessaging(this.widgetMessaging); - } + this.dispatcherRef = dis.register(this._onAction); } componentWillUnmount() { // Widget action listeners dis.unregister(this.dispatcherRef); - // Widget postMessage listeners - try { - if (this.widgetMessaging) { - this.widgetMessaging.stop(); - } - } catch (e) { - console.error('Failed to stop listening for widgetMessaging events', e.message); - } // Jitsi listener window.removeEventListener('message', this._onMessage); + + // if it's not remaining on screen, get rid of the PersistedElement container + if (!ActiveWidgetStore.getWidgetPersistence(this.props.id)) { + // FIXME: ActiveWidgetStore should probably worry about this? + const PersistedElement = sdk.getComponent("elements.PersistedElement"); + PersistedElement.destroyElement(this._persistKey); + ActiveWidgetStore.delWidgetMessaging(this.props.id); + ActiveWidgetStore.delWidgetCapabilities(this.props.id); + } } /** @@ -286,7 +285,7 @@ export default class AppTile extends React.Component { _onSnapshotClick(e) { console.warn("Requesting widget snapshot"); - this.widgetMessaging.getScreenshot() + ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot() .catch((err) => { console.error("Failed to get screenshot", err); }) @@ -347,19 +346,19 @@ export default class AppTile extends React.Component { * Called when widget iframe has finished loading */ _onLoaded() { - if (!this.widgetMessaging) { - this._onInitialLoad(); + if (!ActiveWidgetStore.getWidgetMessaging(this.props.id)) { + this._setupWidgetMessaging(); } this.setState({loading: false}); } - /** - * Called on initial load of the widget iframe - */ - _onInitialLoad() { - this.widgetMessaging = new WidgetMessaging(this.props.id, this.props.url, this.refs.appFrame.contentWindow); - this.widgetMessaging.getCapabilities().then((requestedCapabilities) => { - console.log(`Widget ${this.props.id} requested capabilities:`, requestedCapabilities); + _setupWidgetMessaging() { + // FIXME: There's probably no reason to do this here: it should probably be done entirely + // in ActiveWidgetStore. + const widgetMessaging = new WidgetMessaging(this.props.id, this.props.url, this.refs.appFrame.contentWindow); + ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging); + widgetMessaging.getCapabilities().then((requestedCapabilities) => { + console.log(`Widget ${this.props.id} requested capabilities: ` + requestedCapabilities); requestedCapabilities = requestedCapabilities || []; // Allow whitelisted capabilities @@ -371,16 +370,15 @@ export default class AppTile extends React.Component { }, this.props.whitelistCapabilities); if (requestedWhitelistCapabilies.length > 0 ) { - console.warn(`Widget ${this.props.id} allowing requested, whitelisted properties:`, - requestedWhitelistCapabilies); + console.warn(`Widget ${this.props.id} allowing requested, whitelisted properties: ` + + requestedWhitelistCapabilies, + ); } } // TODO -- Add UI to warn about and optionally allow requested capabilities - this.setState({ - requestedCapabilities, - allowedCapabilities: this.state.allowedCapabilities.concat(requestedWhitelistCapabilies), - }); + + ActiveWidgetStore.setWidgetCapabilities(this.props.id, requestedWhitelistCapabilies); if (this.props.onCapabilityRequest) { this.props.onCapabilityRequest(requestedCapabilities); @@ -390,7 +388,7 @@ export default class AppTile extends React.Component { }); } - _onWidgetAction(payload) { + _onAction(payload) { if (payload.widgetId === this.props.id) { switch (payload.action) { case 'm.sticker': @@ -568,6 +566,15 @@ export default class AppTile extends React.Component { >
); + // if the widget would be allowed to remian on screen, we must put it in + // a PersistedElement from the get-go, otherwise the iframe will be + // re-mounted later when we do. + if (this.props.whitelistCapabilities.includes('m.always_on_screen')) { + const PersistedElement = sdk.getComponent("elements.PersistedElement"); + appTileBody = + {appTileBody} + ; + } } } else { const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); diff --git a/src/components/views/elements/PersistedElement.js b/src/components/views/elements/PersistedElement.js index 8115a36eeb..6f4eb09898 100644 --- a/src/components/views/elements/PersistedElement.js +++ b/src/components/views/elements/PersistedElement.js @@ -16,28 +16,28 @@ limitations under the License. const React = require('react'); const ReactDOM = require('react-dom'); +const PropTypes = require('prop-types'); // Shamelessly ripped off Modal.js. There's probably a better way // of doing reusable widgets like dialog boxes & menus where we go and // pass in a custom control as the actual body. -const ContainerId = "mx_PersistedElement"; +function getContainer(containerId) { + return document.getElementById(containerId); +} -function getOrCreateContainer() { - let container = document.getElementById(ContainerId); +function getOrCreateContainer(containerId) { + let container = getContainer(containerId); if (!container) { container = document.createElement("div"); - container.id = ContainerId; + container.id = containerId; document.body.appendChild(container); } return container; } -// Greater than that of the ContextualMenu -const PE_Z_INDEX = 5000; - /* * Class of component that renders its children in a separate ReactDOM virtual tree * in a container element appended to document.body. @@ -50,12 +50,38 @@ const PE_Z_INDEX = 5000; * bounding rect as the parent of PE. */ export default class PersistedElement extends React.Component { + + static propTypes = { + // Unique identifier for this PersistedElement instance + // Any PersistedElements with the same persistKey will use + // the same DOM container. + persistKey: PropTypes.string.isRequired, + }; + constructor() { super(); this.collectChildContainer = this.collectChildContainer.bind(this); this.collectChild = this.collectChild.bind(this); } + /** + * Removes the DOM elements created when a PersistedElement with the given + * persistKey was mounted. The DOM elements will be re-added if another + * PeristedElement is mounted in the future. + * + * @param {string} persistKey Key used to uniquely identify this PersistedElement + */ + static destroyElement(persistKey) { + const container = getContainer('mx_persistedElement_' + persistKey); + if (container) { + container.remove(); + } + } + + static isMounted(persistKey) { + return Boolean(getContainer('mx_persistedElement_' + persistKey)); + } + collectChildContainer(ref) { this.childContainer = ref; } @@ -97,18 +123,16 @@ export default class PersistedElement extends React.Component { left: parentRect.left + 'px', width: parentRect.width + 'px', height: parentRect.height + 'px', - zIndex: PE_Z_INDEX, }); } render() { - const content =
+ const content =
{this.props.children}
; - ReactDOM.render(content, getOrCreateContainer()); + ReactDOM.render(content, getOrCreateContainer('mx_persistedElement_'+this.props.persistKey)); return
; } } - diff --git a/src/components/views/messages/MStickerBody.js b/src/components/views/messages/MStickerBody.js index d9ed668e42..82a530d503 100644 --- a/src/components/views/messages/MStickerBody.js +++ b/src/components/views/messages/MStickerBody.js @@ -16,6 +16,7 @@ limitations under the License. 'use strict'; +import React from 'react'; import MImageBody from './MImageBody'; import sdk from '../../../index'; diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 3b140729f1..751e2aa265 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd +Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -220,25 +221,30 @@ module.exports = React.createClass({ render: function() { const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", this.props.room.room_id); - const apps = this.state.apps.map( - (app, index, arr) => { - return (); - }, - ); + const apps = this.state.apps.map((app, index, arr) => { + const capWhitelist = enableScreenshots ? ["m.capability.screenshot"] : []; + + // Obviously anyone that can add a widget can claim it's a jitsi widget, + // so this doesn't really offer much over the set of domains we load + // widgets from at all, but it probably makes sense for sanity. + if (app.type == 'jitsi') capWhitelist.push("m.always_on_screen"); + + return (); + }); let addWidget; if (this.props.showApps && diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 7f376502d7..7e73c01330 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -56,6 +56,7 @@ const stateEventTileTypes = { 'm.room.topic': 'messages.TextualEvent', 'm.room.power_levels': 'messages.TextualEvent', 'm.room.pinned_events': 'messages.TextualEvent', + 'm.room.server_acl': 'messages.TextualEvent', 'im.vector.modular.widgets': 'messages.TextualEvent', }; @@ -484,12 +485,19 @@ module.exports = withMatrixClient(React.createClass({ // Info messages are basically information about commands processed on a room const isInfoMessage = (eventType !== 'm.room.message' && eventType !== 'm.sticker'); - const EventTileType = sdk.getComponent(getHandlerTile(this.props.mxEvent)); + const tileHandler = getHandlerTile(this.props.mxEvent); // This shouldn't happen: the caller should check we support this type // before trying to instantiate us - if (!EventTileType) { - throw new Error("Event type not supported"); + if (!tileHandler) { + const {mxEvent} = this.props; + console.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`); + return
+
+ { _t('This event could not be displayed') } +
+
; } + const EventTileType = sdk.getComponent(tileHandler); const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1); const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted; @@ -694,7 +702,6 @@ module.exports = withMatrixClient(React.createClass({
{ readAvatars }
- { avatar } { sender } + { + // The avatar goes after the event tile as it's absolutly positioned to be over the + // event tile line, so needs to be later in the DOM so it appears on top (this avoids + // the need for further z-indexing chaos) + } + { avatar }
); } diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 62ae96f47f..8533e3f61a 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -16,6 +16,8 @@ limitations under the License. */ 'use strict'; +import SettingsStore from "../../../settings/SettingsStore"; + const React = require("react"); const ReactDOM = require("react-dom"); import PropTypes from 'prop-types'; @@ -608,6 +610,10 @@ module.exports = React.createClass({ const RoomSubList = sdk.getComponent('structures.RoomSubList'); const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); + // XXX: we can't detect device-level (localStorage) settings onChange as the SettingsStore does not notify + // so checking on every render is the sanest thing at this time. + const showEmpty = SettingsStore.getValue('RoomSubList.showEmpty'); + const self = this; return ( + onShowMoreRooms={self.onShowMoreRooms} + showEmpty={showEmpty} /> + onShowMoreRooms={self.onShowMoreRooms} + showEmpty={showEmpty} /> + onShowMoreRooms={self.onShowMoreRooms} + showEmpty={showEmpty} /> { Object.keys(self.state.lists).map((tagName) => { if (!tagName.match(STANDARD_TAGS_REGEX)) { @@ -688,7 +699,8 @@ module.exports = React.createClass({ collapsed={self.props.collapsed} searchFilter={self.props.searchFilter} onHeaderClick={self.onSubListHeaderClick} - onShowMoreRooms={self.onShowMoreRooms} />; + onShowMoreRooms={self.onShowMoreRooms} + showEmpty={showEmpty} />; } }) } @@ -702,7 +714,8 @@ module.exports = React.createClass({ collapsed={self.props.collapsed} searchFilter={self.props.searchFilter} onHeaderClick={self.onSubListHeaderClick} - onShowMoreRooms={self.onShowMoreRooms} /> + onShowMoreRooms={self.onShowMoreRooms} + showEmpty={showEmpty} /> + onShowMoreRooms={self.onShowMoreRooms} + showEmpty={showEmpty} /> ); diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 4cb7c59ce6..841cfb9b03 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -24,9 +24,14 @@ import ScalarAuthClient from '../../../ScalarAuthClient'; import dis from '../../../dispatcher'; import AccessibleButton from '../elements/AccessibleButton'; import WidgetUtils from '../../../utils/WidgetUtils'; +import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; const widgetType = 'm.stickerpicker'; +// We sit in a context menu, so the persisted element container needs to float +// above it, so it needs a greater z-index than the ContextMenu +const STICKERPICKER_Z_INDEX = 5000; + export default class Stickerpicker extends React.Component { constructor(props) { super(props); @@ -39,8 +44,6 @@ export default class Stickerpicker extends React.Component { this._onResize = this._onResize.bind(this); this._onFinished = this._onFinished.bind(this); - this._collectWidgetMessaging = this._collectWidgetMessaging.bind(this); - this.popoverWidth = 300; this.popoverHeight = 300; @@ -162,17 +165,11 @@ export default class Stickerpicker extends React.Component { ); } - _collectWidgetMessaging(widgetMessaging) { - this._appWidgetMessaging = widgetMessaging; - - // Do this now instead of in componentDidMount because we might not have had the - // reference to widgetMessaging when mounting - this._sendVisibilityToWidget(true); - } - _sendVisibilityToWidget(visible) { - if (this._appWidgetMessaging && visible !== this._prevSentVisibility) { - this._appWidgetMessaging.sendVisibility(visible); + if (!this.state.stickerpickerWidget) return; + const widgetMessaging = ActiveWidgetStore.getWidgetMessaging(this.state.stickerpickerWidget.id); + if (widgetMessaging && visible !== this._prevSentVisibility) { + widgetMessaging.sendVisibility(visible); this._prevSentVisibility = visible; } } @@ -211,9 +208,8 @@ export default class Stickerpicker extends React.Component { width: this.popoverWidth, }} > - + { + const roomMembers = room.getEncryptionTargetMembers().map((m) => { return m.userId; }); return matrixClient.downloadKeys(roomMembers, false).then((devices) => { diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json index 5ec9a93bc5..bdbab0812a 100644 --- a/src/i18n/strings/bg.json +++ b/src/i18n/strings/bg.json @@ -498,7 +498,7 @@ "Failed to remove room from community": "Неуспешно премахване на стаята от общността", "Only visible to community members": "Видимо само за членове на общността", "Filter community rooms": "Филтрирай стаи на общността", - "Community IDs cannot not be empty.": "Идентификаторите на общността не могат да бъдат празни.", + "Community IDs cannot be empty.": "Идентификаторите на общността не могат да бъдат празни.", "Create Community": "Създай общност", "Community Name": "Име на общност", "Community ID": "Идентификатор на общност", @@ -1181,5 +1181,32 @@ "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "За да продължите да ползвате %(homeserverDomain)s е необходимо да прегледате и да се съгласите с правилата и условията за ползване.", "Review terms and conditions": "Прегледай правилата и условията", "Failed to indicate account erasure": "Неуспешно указване на желанието за изтриване на акаунта", - "Try the app first": "Първо пробвайте приложението" + "Try the app first": "Първо пробвайте приложението", + "Encrypting": "Шифроване", + "Encrypted, not sent": "Шифровано, неизпратено", + "Share Link to User": "Сподели връзка с потребител", + "Share room": "Сподели стая", + "Share Room": "Споделяне на стая", + "Link to most recent message": "Създай връзка към най-новото съобщение", + "Share User": "Споделяне на потребител", + "Share Community": "Споделяне на общност", + "Share Room Message": "Споделяне на съобщение от стая", + "Link to selected message": "Създай връзка към избраното съобщение", + "COPY": "КОПИРАЙ", + "Share Message": "Сподели съобщението", + "No Audio Outputs detected": "Не са открити аудио изходи", + "Audio Output": "Аудио изходи", + "Jitsi Conference Calling": "Jitsi конферентни разговори", + "Call in Progress": "Тече разговор", + "A call is already in progress!": "В момента вече тече разговор!", + "You have no historical rooms": "Нямате стаи в архива", + "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.": "В шифровани стаи като тази, по подразбиране URL прегледите са изключени, за да се подсигури че сървърът (където става генерирането на прегледите) не може да събира информация за връзките споделени в стаята.", + "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.": "Когато някой сподели URL връзка в съобщение, може да бъде показан URL преглед даващ повече информация за връзката (заглавие, описание и картинка от уебсайта).", + "The email field must not be blank.": "Имейл полето не може да бъде празно.", + "The user name field must not be blank.": "Полето за потребителско име не може да е празно.", + "The phone number field must not be blank.": "Полето за телефонен номер не може да е празно.", + "The password field must not be blank.": "Полето за парола не може да е празно.", + "You can't send any messages until you review and agree to our terms and conditions.": "Не можете да изпращате съобщения докато не прегледате и се съгласите с нашите правила и условия.", + "Demote yourself?": "Понижете себе си?", + "Demote": "Понижение" } diff --git a/src/i18n/strings/ca.json b/src/i18n/strings/ca.json index 98d51e99ac..3006063df7 100644 --- a/src/i18n/strings/ca.json +++ b/src/i18n/strings/ca.json @@ -654,7 +654,7 @@ "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)s han sortit %(count)s vegades", "%(oneUser)sleft %(count)s times|other": "%(oneUser)s ha sortit %(count)s vegades", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Les ID de les comunitats només poden contendre caràcters a-z, 0-9, o '=_-./'", - "Community IDs cannot not be empty.": "Les ID de les comunitats no poden estar buides.", + "Community IDs cannot be empty.": "Les ID de les comunitats no poden estar buides.", "Something went wrong whilst creating your community": "S'ha produït un error al crear la vostra comunitat", "Create Community": "Crea una comunitat", "Community Name": "Nom de la comunitat", diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index b7298f80ab..9ac753e285 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -1076,5 +1076,9 @@ "Collapse panel": "Sbalit panel", "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!": "Vzhled a chování aplikace může být ve vašem aktuální prohlížeči nesprávné a některé nebo všechny funkce mohou být chybné. Chcete-li i přes to pokračovat, nebudeme vám bránit, ale se všemi problémy, na které narazíte, si musíte poradit sami!", "Checking for an update...": "Kontrola aktualizací...", - "There are advanced notifications which are not shown here": "Jsou k dispozici pokročilá upozornění, která zde nejsou zobrazena" + "There are advanced notifications which are not shown here": "Jsou k dispozici pokročilá upozornění, která zde nejsou zobrazena", + "The platform you're on": "Platforma na které jsi", + "The version of Riot.im": "Verze Riot.im", + "Whether or not you're logged in (we don't record your user name)": "Jestli jsi, nebo nejsi přihlášen (tvou přezdívku neukládáme)", + "Your language of choice": "Tvá jazyková volba" } diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 9b1c5acb8d..71b7ffe1d1 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -947,7 +947,7 @@ "Your identity server's URL": "Die URL deines Identitätsservers", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "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.": "Du wirst nicht in der Lage sein, die Änderung zurückzusetzen, da du dich degradierst. Wenn du der letze Nutzer mit Berechtigungen bist, wird es unmöglich sein die Privilegien zurückzubekommen.", - "Community IDs cannot not be empty.": "Community-IDs können nicht leer sein.", + "Community IDs cannot be empty.": "Community-IDs können nicht leer sein.", "Show devices, send anyway or cancel.": "Geräte anzeigen, trotzdem senden oder abbrechen.", "Learn more about how we use analytics.": "Lerne mehr darüber, wie wir die Analysedaten nutzen.", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Wenn diese Seite identifizierbare Informationen sowie Raum, Nutzer oder Gruppen-ID enthalten, werden diese Daten entfernt bevor sie an den Server gesendet werden.", @@ -1195,5 +1195,16 @@ "Share Message": "Teile Nachricht", "No Audio Outputs detected": "Keine Ton-Ausgabe erkannt", "Audio Output": "Ton-Ausgabe", - "Try the app first": "App erst ausprobieren" + "Try the app first": "App erst ausprobieren", + "Jitsi Conference Calling": "Jitsi-Konferenz Anruf", + "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 verschlüsselten Räumen, wie diesem, ist die Link-Vorschau standardmäßig deaktiviert damit dein Heimserver (auf dem die Vorschau erzeugt wird) keine Informationen über Links in diesem Raum bekommt.", + "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.": "Wenn jemand eine Nachricht mit einem Link schickt, kann die Link-Vorschau mehr Informationen, wie Titel, Beschreibung und Bild der Webseite, über den Link anzeigen.", + "The email field must not be blank.": "Das E-Mail-Feld darf nicht leer sein.", + "The user name field must not be blank.": "Das Benutzername-Feld darf nicht leer sein.", + "The phone number field must not be blank.": "Das Telefonnummern-Feld darf nicht leer sein.", + "The password field must not be blank.": "Das Passwort-Feld darf nicht leer sein.", + "Call in Progress": "Gespräch läuft", + "A call is already in progress!": "Ein Gespräch läuft bereits!", + "You have no historical rooms": "Du hast keine historischen Räume", + "You can't send any messages until you review and agree to our terms and conditions.": "Du kannst keine Nachrichten senden bis du die unsere Geschläftsbedingungen gelesen und akzeptiert hast." } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2cab44fafb..d90c0a81fa 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -205,8 +205,6 @@ "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?", - "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", "Failed to join room": "Failed to join room", "Message Pinning": "Message Pinning", "Jitsi Conference Calling": "Jitsi Conference Calling", @@ -237,6 +235,7 @@ "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", + "Show empty room list headings": "Show empty room list headings", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", @@ -324,6 +323,7 @@ " (unsupported)": " (unsupported)", "Join as voice or video.": "Join as voice or video.", "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", + "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", @@ -692,6 +692,8 @@ "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", "Revoke widget access": "Revoke widget access", "Minimize apps": "Minimize apps", "Reload widget": "Reload widget", @@ -801,7 +803,7 @@ "Start Chatting": "Start Chatting", "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.", - "Community IDs cannot not be empty.": "Community IDs cannot not be empty.", + "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", diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 1abaec65c7..309e6078a8 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -952,7 +952,7 @@ "Your identity server's URL": "Zure identitate zerbitzariaren URL-a", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(fullYear)s(e)ko %(monthName)sk %(day)sa", "This room is not public. You will not be able to rejoin without an invite.": "Gela hau ez da publikoa. Ezin izango zara berriro elkartu gonbidapenik gabe.", - "Community IDs cannot not be empty.": "Komunitate ID-ak ezin dira hutsik egon.", + "Community IDs cannot be empty.": "Komunitate ID-ak ezin dira hutsik egon.", "Show devices, send anyway or cancel.": "Erakutsi gailuak, bidali hala ere edo ezeztatu.", "In reply to ": "honi erantzunez: ", "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s erabiltzaileak bere pantaila izena aldatu du %(displayName)s izatera.", @@ -1195,5 +1195,16 @@ "COPY": "KOPIATU", "Share Message": "Partekatu mezua", "No Audio Outputs detected": "Ez da audio irteerarik antzeman", - "Audio Output": "Audio irteera" + "Audio Output": "Audio irteera", + "Jitsi Conference Calling": "Jitsi konferentzia deia", + "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.": "Zifratutako gelatan, honetan esaterako, URL-en aurrebistak lehenetsita desgaituta daude zure hasiera-zerbitzariak gela honetan ikusten dituzun estekei buruzko informaziorik jaso ez dezan, hasiera-zerbitzarian sortzen baitira aurrebistak.", + "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.": "Norbaitek mezu batean URL bat jartzen duenean, URL aurrebista bat erakutsi daiteke estekaren informazio gehiago erakusteko, adibidez webgunearen izenburua, deskripzioa eta irudi bat.", + "The email field must not be blank.": "E-mail eremua ezin da hutsik laga.", + "The user name field must not be blank.": "Erabiltzaile-izen eremua ezin da hutsik laga.", + "The phone number field must not be blank.": "Telefono zenbakia eremua ezin da hutsik laga.", + "The password field must not be blank.": "Pasahitza eremua ezin da hutsik laga.", + "Call in Progress": "Deia abian", + "A call is already in progress!": "Badago dei bat abian!", + "You have no historical rooms": "Ez duzu gelen historialik", + "You can't send any messages until you review and agree to our terms and conditions.": "Ezin duzu mezurik bidali gure termino eta baldintzak irakurri eta onartu arte." } diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 8843ad89ca..721cf1d9f1 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -952,7 +952,7 @@ "Your identity server's URL": "L'URL de votre serveur d'identité", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s %(day)s %(monthName)s %(fullYear)s", "This room is not public. You will not be able to rejoin without an invite.": "Ce salon n'est pas public. Vous ne pourrez pas y revenir sans invitation.", - "Community IDs cannot not be empty.": "Les identifiants de communauté ne peuvent pas être vides.", + "Community IDs cannot be empty.": "Les identifiants de communauté ne peuvent pas être vides.", "Show devices, send anyway or cancel.": "Afficher les appareils, envoyer quand même ou annuler.", "In reply to ": "En réponse à ", "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s a changé son nom affiché en %(displayName)s.", @@ -1195,5 +1195,18 @@ "Share Room Message": "Partager le message du salon", "Link to selected message": "Lien vers le message sélectionné", "COPY": "COPIER", - "Share Message": "Partager le message" + "Share Message": "Partager le message", + "Jitsi Conference Calling": "Appel en téléconférence Jitsi", + "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.": "Dans les salons chiffrés, comme celui-ci, l'aperçu des liens est désactivé par défaut pour s'assurer que le serveur d'accueil (où sont générés les aperçus) ne puisse pas collecter d'informations sur les liens qui apparaissent dans ce salon.", + "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.": "Quand quelqu'un met un lien dans son message, un aperçu du lien peut être affiché afin de fournir plus d'informations sur ce lien comme le titre, la description et une image du site.", + "The email field must not be blank.": "Le champ de l'adresse e-mail ne doit pas être vide.", + "The user name field must not be blank.": "Le champ du nom d'utilisateur ne doit pas être vide.", + "The phone number field must not be blank.": "Le champ du numéro de téléphone ne doit pas être vide.", + "The password field must not be blank.": "Le champ du mot de passe ne doit pas être vide.", + "Call in Progress": "Appel en cours", + "A call is already in progress!": "Un appel est déjà en cours !", + "You have no historical rooms": "Vous n'avez aucun salon historique", + "You can't send any messages until you review and agree to our terms and conditions.": "Vous ne pouvez voir aucun message tant que vous ne lisez et n'acceptez pas nos conditions générales.", + "Demote yourself?": "Vous rétrograder ?", + "Demote": "Rétrograder" } diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 57d8301df6..b1d64a156d 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -633,7 +633,7 @@ "Confirm Removal": "Confirme a retirada", "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.": "Está certa de que quere quitar (eliminar) este evento? Saiba que si elimina un nome de sala ou cambia o asunto, podería desfacer o cambio.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Os ID de comunidade só poden conter caracteres a-z, 0-9, or '=_-./'", - "Community IDs cannot not be empty.": "O ID de comunidade non pode quedar baldeiro.", + "Community IDs cannot be empty.": "O ID de comunidade non pode quedar baldeiro.", "Something went wrong whilst creating your community": "Algo fallou mentres se creaba a súa comunidade", "Create Community": "Crear comunidade", "Community Name": "Nome da comunidade", @@ -1195,5 +1195,6 @@ "Review terms and conditions": "Revise os termos e condicións", "No Audio Outputs detected": "Non se detectou unha saída de audio", "Audio Output": "Saída de audio", - "Try the app first": "Probe a aplicación primeiro" + "Try the app first": "Probe a aplicación primeiro", + "Jitsi Conference Calling": "Chamada para conferencia con Jitsi" } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 2aea205a15..2cdd8219ad 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -953,7 +953,7 @@ "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(fullYear)s. %(monthName)s %(day)s, %(weekDayName)s", "This room is not public. You will not be able to rejoin without an invite.": "Ez a szoba nem nyilvános. Kilépés után csak újabb meghívóval tudsz újra belépni a szobába.", "Show devices, send anyway or cancel.": "Eszközök listája, mindenképpen küld vagy szakítsd meg.", - "Community IDs cannot not be empty.": "A közösségi azonosító nem lehet üres.", + "Community IDs cannot be empty.": "A közösségi azonosító nem lehet üres.", "In reply to ": "Válaszolva neki ", "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s megváltoztatta a nevét erre: %(displayName)s.", "Failed to set direct chat tag": "Nem sikerült a közvetlen beszélgetés jelzést beállítani", @@ -1195,5 +1195,18 @@ "Share Room Message": "Szoba üzenet megosztás", "Link to selected message": "Hivatkozás a kijelölt üzenetre", "COPY": "Másol", - "Share Message": "Üzenet megosztása" + "Share Message": "Üzenet megosztása", + "Jitsi Conference Calling": "Jitsi konferencia hívás", + "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.": "Az olyan titkosított szobákban, mint ez is, az URL előnézet alapértelmezetten ki van kapcsolva, hogy biztosított legyen, hogy a matrix szerver (ahol az előnézet készül) ne tudjon információt gyűjteni arról, hogy milyen linkeket látsz ebben a szobában.", + "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.": "Ha valaki URL linket helyez az üzenetébe, lehetőség van egy előnézet megjelenítésére amivel további információt kaphatunk a linkről, mint cím, leírás és a weboldal képe.", + "The email field must not be blank.": "Az e-mail mező nem lehet üres.", + "The user name field must not be blank.": "A felhasználói név mező nem lehet üres.", + "The phone number field must not be blank.": "A telefonszám mező nem lehet üres.", + "The password field must not be blank.": "A jelszó mező nem lehet üres.", + "Call in Progress": "Hívás folyamatban", + "A call is already in progress!": "A hívás már folyamatban van!", + "You have no historical rooms": "Nincsenek archív szobáid", + "You can't send any messages until you review and agree to our terms and conditions.": "Nem tudsz üzenetet küldeni amíg nem olvasod el és nem fogadod el a felhasználási feltételeket.", + "Demote yourself?": "Lefokozod magad?", + "Demote": "Lefokozás" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 0957bc156d..07e030c061 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -674,7 +674,7 @@ "Start Chatting": "Inizia a chattare", "Confirm Removal": "Conferma la rimozione", "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.": "Sei sicuro di volere rimuovere (eliminare) questo evento? Nota che se elimini il nome di una stanza o la modifica di un argomento, potrebbe annullare la modifica.", - "Community IDs cannot not be empty.": "Gli ID della comunità non possono essere vuoti.", + "Community IDs cannot be empty.": "Gli ID della comunità non possono essere vuoti.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Gli ID della comunità devono contenere solo caratteri a-z, 0-9, or '=_-./'", "Something went wrong whilst creating your community": "Qualcosa è andato storto nella creazione della tua comunità", "Create Community": "Crea una comunità", @@ -1180,5 +1180,20 @@ "Replying": "Rispondere", "Popout widget": "Oggetto a comparsa", "Failed to indicate account erasure": "Impossibile indicare la cancellazione dell'account", - "Bulk Options": "Opzioni applicate in massa" + "Bulk Options": "Opzioni applicate in massa", + "Encrypting": "Cifratura...", + "Encrypted, not sent": "Cifrato, non inviato", + "Share Link to User": "Condividi link con utente", + "Share room": "Condividi stanza", + "Share Room": "Condividi stanza", + "Link to most recent message": "Link al messaggio più recente", + "Share User": "Condividi utente", + "Share Community": "Condividi comunità", + "Share Room Message": "Condividi messaggio stanza", + "Link to selected message": "Link al messaggio selezionato", + "COPY": "COPIA", + "Share Message": "Condividi messaggio", + "No Audio Outputs detected": "Nessuna uscita audio rilevata", + "Audio Output": "Uscita audio", + "Try the app first": "Prova prima l'app" } diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json index 0c6bcb0977..09182ed776 100644 --- a/src/i18n/strings/lv.json +++ b/src/i18n/strings/lv.json @@ -862,7 +862,7 @@ "email address": "e-pasta adrese", "Try using one of the following valid address types: %(validTypesList)s.": "Mēģiniet izmantot vienu no sekojošiem pieļautajiem adrešu tipiem: %(validTypesList)s.", "You have entered an invalid address.": "Ievadīta nederīga adrese.", - "Community IDs cannot not be empty.": "Kopienu IDs nevar būt tukši.", + "Community IDs cannot be empty.": "Kopienu IDs nevar būt tukši.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Kopienas ID var saturēt tikai simbolus a-z, 0-9, or '=_-./'", "Something went wrong whilst creating your community": "Radot Tavu kopienu kaut kas nogāja greizi", "Create Community": "Radīt kopienu", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 1ba068daa0..2c3771cafd 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -877,7 +877,7 @@ "Try using one of the following valid address types: %(validTypesList)s.": "Probeer één van de volgende geldige adrestypes: %(validTypesList)s.", "You have entered an invalid address.": "Je hebt een ongeldig adres ingevoerd.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Een gemeenschaps-ID mag alleen de karakters a-z, 0-9, of '=_-./' bevatten.", - "Community IDs cannot not be empty.": "Een gemeenschaps-ID kan niet leeg zijn.", + "Community IDs cannot be empty.": "Een gemeenschaps-ID kan niet leeg zijn.", "Something went wrong whilst creating your community": "Er is iets fout gegaan tijdens het aanmaken van je gemeenschap", "Create Community": "Gemeenschap Aanmaken", "Community Name": "Gemeenschapsnaam", diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index 0088028aed..c5dd13c011 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -650,7 +650,7 @@ "Automatically replace plain text Emoji": "Automatycznie zastępuj tekstowe emotikony", "Failed to upload image": "Przesyłanie obrazka nie powiodło się", "%(count)s new messages|one": "%(count)s nowa wiadomość", - "%(count)s new messages|other": "%(count)s nowe wiadomości", + "%(count)s new messages|other": "%(count)s nowych wiadomości", "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.": "Zalecamy Ci przejście przez proces weryfikacyjny dla każdego urządzenia aby potwierdzić, że należy ono do ich prawdziwego właściciela. Możesz jednak wysłać tę wiadomość bez potwierdzania.", "Unblacklist": "Usuń z czarnej listy", "Blacklist": "Dodaj do czarnej listy", @@ -752,9 +752,9 @@ "Unnamed room": "Pokój bez nazwy", "Guests can join": "Goście mogą dołączyć", "Remove avatar": "Usuń awatar", - "Drop here to favourite": "Upuść to aby dodać do ulubionych", - "Drop here to restore": "Upuść tu aby przywrócić", - "Drop here to demote": "Upuść tu aby zdegradować", + "Drop here to favourite": "Upuść tutaj aby dodać do ulubionych", + "Drop here to restore": "Upuść tutaj aby przywrócić", + "Drop here to demote": "Upuść tutaj aby zdegradować", "You have been kicked from this room by %(userName)s.": "Zostałeś usunięty z tego pokoju przez %(userName)s.", "You have been banned from this room by %(userName)s.": "Zostałeś zbanowany z tego pokoju przez %(userName)s.", "You are trying to access a room.": "Próbujesz uzyskać dostęp do pokoju.", @@ -937,8 +937,8 @@ "An email has been sent to %(emailAddress)s": "Email został wysłany do %(emailAddress)s", "A text message has been sent to %(msisdn)s": "Wysłano wiadomość tekstową do %(msisdn)s", "Code": "Kod", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Pomóż nam ulepszyć Riot.im wysyłając anonimowe dane analityczne. Spowoduje to użycie pliku cookie (zobacz naszą Politykę plików cookie).", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Pomóż nam ulepszyć Riot.im wysyłając anonimowe dane analityczne. Spowoduje to użycie pliku cookie.", + "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Pomóż nam ulepszyć Riot.im wysyłając anonimowe dane analityczne. Spowoduje to użycie pliku cookie (zobacz naszą Politykę plików cookie).", + "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Pomóż nam ulepszyć Riot.im wysyłając anonimowe dane analityczne. Spowoduje to użycie pliku cookie.", "Yes, I want to help!": "Tak, chcę pomóc!", "Warning: This widget might use cookies.": "Uwaga: Ten widżet może używać ciasteczek.", "Delete Widget": "Usuń widżet", @@ -955,5 +955,172 @@ "Advanced options": "Opcje zaawansowane", "To continue, please enter your password:": "Aby kontynuować, proszę wprowadzić swoje hasło:", "password": "hasło", - "Refresh": "Odśwież" + "Refresh": "Odśwież", + "Which officially provided instance you are using, if any": "Jakiej oficjalnej instancji używasz, jeżeli w ogóle", + "Every page you use in the app": "Każda strona, której używasz w aplikacji", + "e.g. ": "np. ", + "Your User Agent": "Identyfikator Twojej przeglądarki", + "Your device resolution": "Twoja rozdzielczość ekranu", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Dane identyfikujące, takie jak: pokój, identyfikator użytkownika lub grupy, są usuwane przed wysłaniem na serwer.", + "Who would you like to add to this community?": "Kogo chcesz dodać do tej społeczności?", + "Missing roomId.": "Brak identyfikatora pokoju (roomId).", + "Ignores a user, hiding their messages from you": "Ignoruje użytkownika ukrywając jego wiadomości przed Tobą", + "Stops ignoring a user, showing their messages going forward": "Przestaje ignorować użytkownika, zaczynaj pokazywać jego wiadomości od tego momentu", + "Opens the Developer Tools dialog": "Otwiera narzędzia deweloperskie", + "Encrypting": "Szyfrowanie", + "Encrypted, not sent": "Zaszyfrowane, nie wysłane", + "Disinvite this user?": "Anulować zaproszenie tego użytkownika?", + "Unignore": "Przestań ignorować", + "Jump to read receipt": "Przeskocz do potwierdzenia odczytu", + "Share Link to User": "Udostępnij link do użytkownika", + "At this time it is not possible to reply with a file so this will be sent without being a reply.": "W tej chwili nie można odpowiedzieć plikiem, więc zostanie wysłany nie będąc odpowiedzią.", + "Unable to reply": "Nie udało się odpowiedzieć", + "At this time it is not possible to reply with an emote.": "W tej chwili nie można odpowiedzieć emotikoną.", + "Replying": "Odpowiadanie", + "Share room": "Udostępnij pokój", + "Drop here to tag direct chat": "Upuść tutaj aby oznaczyć jako rozmowę bezpośrednią", + "Community Invites": "Zaproszenia do społeczności", + "To change the room's history visibility, you must be a": "Aby zmienić widoczność historii pokoju, musisz być", + "To change the permissions in the room, you must be a": "Aby zmienić uprawnienia pokoju, musisz być", + "To change the topic, you must be a": "Aby zmienić temat, musisz być", + "To modify widgets in the room, you must be a": "Aby modyfikować widżety w tym pokoju, musisz być", + "Banned by %(displayName)s": "Zbanowany przez %(displayName)s", + "To send messages, you must be a": "Aby wysyłać wiadomości, musisz być", + "To invite users into the room, you must be a": "Aby zapraszać użytkowników do pokoju, musisz być", + "To configure the room, you must be a": "Aby konfigurować pokój, musisz być", + "To kick users, you must be a": "Aby wyrzucać użytkowników, musisz być", + "To ban users, you must be a": "Aby blokować użytkowników, musisz być", + "To remove other users' messages, you must be a": "Aby usuwać wiadomości innych użytkowników, musisz być", + "To notify everyone in the room, you must be a": "Aby powiadamiać wszystkich w pokoju, musisz być", + "Muted Users": "Wyciszeni użytkownicy", + "To send events of type , you must be a": "Aby wysyłać zdarzenia typu , musisz być", + "Addresses": "Adresy", + "Invalid community ID": "Błędne ID społeczności", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' nie jest poprawnym ID społeczności", + "New community ID (e.g. +foo:%(localDomain)s)": "Nowe ID społeczności (np. +bla:%(localDomain)s)", + "URL previews are enabled by default for participants in this room.": "Podglądy linków są domyślnie włączone dla uczestników tego pokoju.", + "URL previews are disabled by default for participants in this room.": "Podglądy linków są domyślnie wyłączone dla uczestników tego pokoju.", + "Username on %(hs)s": "Nazwa użytkownika na %(hs)s", + "Remove from community": "Usuń ze społeczności", + "Disinvite this user from community?": "Anulować zaproszenie tego użytkownika ze społeczności?", + "Remove this user from community?": "Usunąć tego użytkownika ze społeczności?", + "Failed to withdraw invitation": "Nie udało się wycofać zaproszenia", + "Failed to remove user from community": "Nie udało się usunąć użytkownika ze społeczności", + "Filter community members": "Filtruj członków społeczności", + "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Czy na pewno chcesz usunąć '%(roomName)s' z %(groupId)s?", + "Removing a room from the community will also remove it from the community page.": "Usunięcie pokoju ze społeczności spowoduje także jego usunięcie ze strony społeczności.", + "Failed to remove room from community": "Nie udało się usunąć pokoju ze społeczności", + "Failed to remove '%(roomName)s' from %(groupId)s": "Nie udało się usunąć '%(roomName)s' z %(groupId)s", + "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "Widoczność '%(roomName)s' w %(groupId)s nie może być zaktualizowana.", + "Visibility in Room List": "Widoczność na liście pokojów", + "Visible to everyone": "Widoczny dla wszystkich", + "Only visible to community members": "Widoczny tylko dla członków społeczności", + "Filter community rooms": "Filtruj pokoje społeczności", + "Something went wrong when trying to get your communities.": "Coś poszło nie tak podczas pobierania Twoich społeczności.", + "You're not currently a member of any communities.": "Nie jesteś obecnie członkiem żadnej społeczności.", + "Minimize apps": "Zminimalizuj aplikacje", + "Reload widget": "Przeładuj widżet", + "Picture": "Zdjęcie", + "Matrix Room ID": "ID pokoju Matrix", + "You have entered an invalid address.": "Podałeś nieprawidłowy adres.", + "Try using one of the following valid address types: %(validTypesList)s.": "Spróbuj użyć jednego z następujących poprawnych typów adresów: %(validTypesList)s.", + "Riot bugs are tracked on GitHub: create a GitHub issue.": "Błędy Riot śledzone są na GitHubie: utwórz nowe zgłoszenie.", + "Community IDs cannot be empty.": "ID społeczności nie może być puste.", + "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "ID społeczności może zawierać tylko znaki a-z, 0-9 lub '=_-./'", + "Something went wrong whilst creating your community": "Coś poszło nie tak podczas tworzenia Twojej społeczności", + "Create Community": "Utwórz społeczność", + "Community Name": "Nazwa społeczności", + "Community ID": "ID społeczności", + "Block users on other matrix homeservers from joining this room": "Blokuj użytkowników z innych serwerów Matrix przed dołączaniem do tego pokoju", + "This setting cannot be changed later!": "Tego ustawienia nie można zmienić później!", + "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.": "To sprawi, że Twoje konto stanie się na stałe niezdatne do użytku. Nie będziesz mógł się zalogować i nikt nie będzie mógł ponownie zarejestrować tego samego identyfikatora użytkownika. Spowoduje to, że Twoje konto opuści wszystkie pokoje, w których uczestniczy, i usunie dane Twojego konta z serwera tożsamości. Ta czynność jest nieodwracalna.", + "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.": "Dezaktywacja konta domyślnie nie powoduje, że skasowania wysłanych wiadomości. Jeśli chcesz, abyśmy zapomnieli o Twoich wiadomościach, zaznacz pole poniżej.", + "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.": "Widoczność wiadomości w Matrix jest podobna do wiadomości e-mail. Nasze zapomnienie wiadomości oznacza, że wysłane wiadomości nie będą udostępniane żadnym nowym lub niezarejestrowanym użytkownikom, ale zarejestrowani użytkownicy, którzy już mają dostęp do tych wiadomości, nadal będą mieli dostęp do ich kopii.", + "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)": "Proszę zapomnieć o wszystkich wiadomościach, które wysłałem, gdy moje konto jest wyłączone (Ostrzeżenie: spowoduje to, że przyszli użytkownicy zobaczą niepełny obraz rozmów)", + "Log out and remove encryption keys?": "Wylogować i usunąć klucze szyfrujące?", + "Clear Storage and Sign Out": "Wyczyść pamięć i wyloguj się", + "Send Logs": "Wyślij dzienniki", + "We encountered an error trying to restore your previous session.": "Napotkaliśmy błąd podczas przywracania poprzedniej sesji.", + "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Wyczyszczenie pamięci przeglądarki może rozwiązać problem, ale wyloguje Cię i spowoduje, że jakakolwiek zaszyfrowana historia czatu stanie się nieczytelna.", + "Share Room": "Udostępnij pokój", + "Link to most recent message": "Link do najnowszej wiadomości", + "Share User": "Udostępnij użytkownika", + "Share Community": "Udostępnij Społeczność", + "Share Room Message": "Udostępnij wiadomość w pokoju", + "Link to selected message": "Link do zaznaczonej wiadomości", + "COPY": "KOPIUJ", + "Unable to reject invite": "Nie udało się odrzucić zaproszenia", + "Share Message": "Udostępnij wiadomość", + "Collapse Reply Thread": "Zwiń wątek odpowiedzi", + "

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": "

Strona HTML dla Twojej Społeczności

\n

\n Skorzystaj z długiego opisu aby wprowadzić nowych członków do Społeczności lub rozpowszechnić ważne linki.\n

\n

\n Możesz nawet używać tagów 'img'.\n

\n", + "Add rooms to the community summary": "Dodaj pokoje do podsumowania Społeczności", + "Which rooms would you like to add to this summary?": "Które pokoje chcesz dodać do tego podsumowania?", + "Add to summary": "Dodaj do podsumowania", + "Failed to add the following rooms to the summary of %(groupId)s:": "Nie udało się dodać następujących pokojów do podsumowania %(groupId)s:", + "Add a Room": "Dodaj pokój", + "Failed to remove the room from the summary of %(groupId)s": "Nie udało się usunąć pokoju z podsumowania %(groupId)s", + "The room '%(roomName)s' could not be removed from the summary.": "Pokój '%(roomName)s' nie mógł być usunięty z podsumowania.", + "Add users to the community summary": "Dodaj użytkowników do podsumowania Społeczności", + "Who would you like to add to this summary?": "Kogo chcesz dodać do tego podsumowania?", + "Failed to add the following users to the summary of %(groupId)s:": "Nie udało się dodać następujących użytkowników do podsumowania %(groupId)s:", + "Add a User": "Dodaj użytkownika", + "Failed to remove a user from the summary of %(groupId)s": "Nie udało się usunąć użytkownika z podsumowania %(groupId)s", + "The user '%(displayName)s' could not be removed from the summary.": "Użytkownik '%(displayName)s' nie mógł być usunięty z podsumowania.", + "Failed to update community": "Nie udało się zaktualizować Społeczności", + "Unable to accept invite": "Nie udało się zaakceptować zaproszenia", + "Unable to join community": "Nie udało się dołączyć do Społeczności", + "Leave Community": "Opuść Społeczność", + "Leave %(groupName)s?": "Opuścić %(groupName)s?", + "Unable to leave community": "Nie udało się opuścić Społeczności", + "Community Settings": "Ustawienia Społeczności", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Zmiany nazwy oraz awataru Twojej Społeczności mogą nie być widoczne przez innych użytkowników nawet przez 30 minut.", + "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Te pokoje są wyświetlane członkom społeczności na stronie społeczności. Członkowie społeczności mogą dołączyć do pokoi, klikając je.", + "%(inviter)s has invited you to join this community": "%(inviter)s zaprosił Cię do przyłączenia się do tej Społeczności", + "Join this community": "Dołącz do tej Społeczności", + "Leave this community": "Opuść tę Społeczność", + "You are an administrator of this community": "Jesteś administratorem tej Społeczności", + "You are a member of this community": "Jesteś członkiem tej społeczności", + "Who can join this community?": "Kto może dołączyć do tej Społeczności?", + "Everyone": "Każdy", + "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!": "Twoja Społeczność nie ma długiego opisu, strony HTML, która będzie wyświetlana członkom społeczności.
Kliknij tutaj, aby otworzyć ustawienia i nadać jej jakąś!", + "Long Description (HTML)": "Długi opis (HTML)", + "Description": "Opis", + "Community %(groupId)s not found": "Społeczność %(groupId)s nie znaleziona", + "This Home server does not support communities": "Ten serwer domowy nie wspiera Społeczności", + "Failed to load %(groupId)s": "Nie udało się załadować %(groupId)s", + "This room is not public. You will not be able to rejoin without an invite.": "Ten pokój nie jest publiczny. Nie będziesz w stanie do niego dołączyć bez zaproszenia.", + "Can't leave Server Notices room": "Nie można opuścić pokoju powiadomień serwera", + "This room is used for important messages from the Homeserver, so you cannot leave it.": "Ten pokój jest używany do ważnych wiadomości z serwera domowego, więc nie możesz go opuścić.", + "Terms and Conditions": "Warunki użytkowania", + "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Aby kontynuować używanie serwera domowego %(homeserverDomain)s musisz przejrzeć i zaakceptować nasze warunki użytkowania.", + "Review terms and conditions": "Przejrzyj warunki użytkowania", + "Old cryptography data detected": "Wykryto stare dane kryptograficzne", + "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.": "Dane ze starszej wersji Riot zostały wykryte. Spowoduje to błędne działanie kryptografii typu end-to-end w starszej wersji. Wiadomości szyfrowane end-to-end wymieniane ostatnio podczas korzystania ze starszej wersji mogą być niemożliwe do odszyfrowywane w tej wersji. Może to również spowodować niepowodzenie wiadomości wymienianych z tą wersją. Jeśli wystąpią problemy, wyloguj się i zaloguj ponownie. Aby zachować historię wiadomości, wyeksportuj i ponownie zaimportuj klucze.", + "Your Communities": "Twoje Społeczności", + "Did you know: you can use communities to filter your Riot.im experience!": "Czy wiesz, że: możesz używać Społeczności do filtrowania swoich doświadczeń z Riot.im!", + "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.": "Aby ustawić filtr, przeciągnij awatar Społeczności do panelu filtra po lewej stronie ekranu. Możesz kliknąć awatar w panelu filtra w dowolnym momencie, aby zobaczyć tylko pokoje i osoby powiązane z tą społecznością.", + "Error whilst fetching joined communities": "Błąd podczas pobierania dołączonych społeczności", + "Create a new community": "Utwórz nową Społeczność", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Utwórz Społeczność, aby grupować użytkowników i pokoje! Zbuduj niestandardową stronę główną, aby zaznaczyć swoją przestrzeń we wszechświecie Matrix.", + "Show devices, send anyway or cancel.": "Pokaż urządzenia, wyślij mimo to lub anuluj.", + "%(count)s of your messages have not been sent.|one": "Twoja wiadomość nie została wysłana.", + "There's no one else here! Would you like to invite others or stop warning about the empty room?": "Nikogo tu nie ma! Czy chcesz zaprosić inne osoby lub przestać ostrzegać o pustym pokoju?", + "Clear filter": "Wyczyść filtr", + "Light theme": "Jasny motyw", + "Dark theme": "Ciemny motyw", + "Status.im theme": "Motyw Status.im", + "Ignored Users": "Ignorowani użytkownicy", + "Debug Logs Submission": "Wysyłanie dzienników błędów", + "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.": "Jeśli zgłosiłeś błąd za pośrednictwem GitHuba, dzienniki błędów mogą nam pomóc wyśledzić problem. Dzienniki błędów zawierają dane o użytkowaniu aplikacji, w tym nazwę użytkownika, identyfikatory lub aliasy odwiedzonych pomieszczeń lub grup oraz nazwy użytkowników innych użytkowników. Nie zawierają wiadomości.", + "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Prywatność jest dla nas ważna, dlatego nie gromadzimy żadnych danych osobowych ani danych identyfikujących w naszych analizach.", + "Learn more about how we use analytics.": "Dowiedz się więcej co analizujemy.", + "No Audio Outputs detected": "Nie wykryto wyjść audio", + "Audio Output": "Wyjście audio", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "E-mail został wysłany na adres %(emailAddress)s. Gdy otworzysz link, który zawiera, kliknij poniżej.", + "Please note you are logging into the %(hs)s server, not matrix.org.": "Zauważ proszę, że logujesz się na serwer %(hs)s, nie matrix.org.", + "This homeserver doesn't offer any login flows which are supported by this client.": "Ten serwer domowy nie oferuje żadnych trybów logowania wspieranych przez Twojego klienta.", + "Try the app first": "Najpierw wypróbuj aplikację", + "Sign in to get started": "Zaloguj się, aby rozpocząć", + "Notify the whole room": "Powiadom cały pokój", + "Room Notification": "Powiadomienia pokoju" } diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index c583e795cb..08ad8a4bd5 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -873,7 +873,7 @@ "email address": "endereço de e-mail", "Try using one of the following valid address types: %(validTypesList)s.": "Tente usar um dos seguintes tipos de endereço válidos: %(validTypesList)s.", "You have entered an invalid address.": "Você entrou com um endereço inválido.", - "Community IDs cannot not be empty.": "IDs de comunidades não podem estar em branco.", + "Community IDs cannot be empty.": "IDs de comunidades não podem estar em branco.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "IDs de comunidade podem apenas ter os seguintes caracteres: a-z, 0-9, ou '=_-./'", "Something went wrong whilst creating your community": "Algo deu errado ao criar sua comunidade", "Create Community": "Criar comunidade", diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index ae889c5677..b0f6bd3d52 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -953,7 +953,7 @@ "Whether or not you're using the Richtext mode of the Rich Text Editor": "Используете ли вы режим Richtext в редакторе Rich Text Editor", "This room is not public. You will not be able to rejoin without an invite.": "Эта комната не является публичной. Вы не сможете войти без приглашения.", "Show devices, send anyway or cancel.": "Показать устройства, отправить в любом случае или отменить.", - "Community IDs cannot not be empty.": "ID сообществ не могут быть пустыми.", + "Community IDs cannot be empty.": "ID сообществ не могут быть пустыми.", "In reply to ": "В ответ на ", "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s изменил(а) отображаемое имя на %(displayName)s.", "Failed to set direct chat tag": "Не удалось установить тег прямого чата", @@ -1191,5 +1191,20 @@ "Share User": "Поделиться пользователем", "Share Community": "Поделиться сообществом", "Link to selected message": "Ссылка на выбранное сообщение", - "COPY": "КОПИРОВАТЬ" + "COPY": "КОПИРОВАТЬ", + "Jitsi Conference Calling": "Конференц-связь Jitsi", + "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.": "В зашифрованных комнатах, подобных этой, предварительный просмотр URL-адресов отключен по умолчанию, чтобы гарантировать, что ваш сервер (где создаются предварительные просмотры) не может собирать информацию о ссылках, которые вы видите в этой комнате.", + "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.": "Когда кто-то вставляет URL-адрес в свое сообщение, может быть отображен предварительный просмотр URL-адреса, чтобы предоставить дополнительную информацию об этой ссылке, такую как название, описание и изображение с веб-сайта.", + "The email field must not be blank.": "Поле email не должно быть пустым.", + "The user name field must not be blank.": "Поле имени пользователя не должно быть пустым.", + "The phone number field must not be blank.": "Поле номера телефона не должно быть пустым.", + "The password field must not be blank.": "Поле пароля не должно быть пустым.", + "Call in Progress": "Выполнение вызова", + "A call is already in progress!": "Вызов выполняется!", + "You have no historical rooms": "У вас нет архивных комнат", + "Share Room Message": "Обмен сообщениями в комнате", + "Share Message": "Обмен сообщениями", + "You can't send any messages until you review and agree to our terms and conditions.": "Вы не можете отправлять сообщения до тех пор, пока вы не примете наши правила и положения.", + "Demote": "Понижение", + "Demote yourself?": "Понизить самого себя?" } diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index b1f3f4260d..bf5d365dfe 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -60,7 +60,7 @@ "Riot was not given permission to send notifications - please try again": "Aplikácii Riot neboli udelené oprávnenia potrebné pre posielanie oznámení - prosím, skúste to znovu", "Unable to enable Notifications": "Nie je možné povoliť oznámenia", "This email address was not found": "Túto emailovú adresu sa nepodarilo nájsť", - "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Zdá sa, že vaša emailová adresa nie je priradená k žiadnemu Matrix ID na tomto domovskom servery.", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Zdá sa, že vaša emailová adresa nie je priradená k žiadnemu Matrix ID na tomto domovskom serveri.", "Default": "Predvolené", "Moderator": "Moderátor", "Admin": "Správca", @@ -118,7 +118,7 @@ "%(targetName)s rejected the invitation.": "%(targetName)s odmietol pozvanie.", "%(targetName)s left the room.": "%(targetName)s opustil miestnosť.", "%(senderName)s unbanned %(targetName)s.": "%(senderName)s povolil vstup %(targetName)s.", - "%(senderName)s kicked %(targetName)s.": "%(senderName)s vykopol %(targetName)s.", + "%(senderName)s kicked %(targetName)s.": "%(senderName)s vykázal %(targetName)s.", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s stiahol pozvanie %(targetName)s.", "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s zmenil tému na \"%(topic)s\".", "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s odstránil názov miestnosti.", @@ -219,10 +219,10 @@ "Unverified": "Neoverené", "device id: ": "ID zariadenia: ", "Disinvite": "Stiahnuť pozvanie", - "Kick": "Vykopnúť", + "Kick": "Vykázať", "Disinvite this user?": "Stiahnuť pozvanie tohoto používateľa?", - "Kick this user?": "Vykopnúť tohoto používateľa?", - "Failed to kick": "Nepodarilo sa vykopnúť", + "Kick this user?": "Vykázať tohoto používateľa?", + "Failed to kick": "Nepodarilo sa vykázať", "Unban": "Povoliť vstup", "Ban": "Zakázať vstup", "Unban this user?": "Povoliť vstúpiť tomuto používateľovi?", @@ -326,8 +326,8 @@ "Would you like to accept or decline this invitation?": "Chcete prijať alebo odmietnuť toto pozvanie?", "Reason: %(reasonText)s": "Dôvod: %(reasonText)s", "Rejoin": "Vstúpiť znovu", - "You have been kicked from %(roomName)s by %(userName)s.": "Používateľ %(userName)s vás vykopol z miestnosti %(roomName)s.", - "You have been kicked from this room by %(userName)s.": "Používateľ %(userName)s vás vykopol z tejto miestnosti.", + "You have been kicked from %(roomName)s by %(userName)s.": "Používateľ %(userName)s vás vykázal z miestnosti %(roomName)s.", + "You have been kicked from this room by %(userName)s.": "Používateľ %(userName)s vás vykázal z tejto miestnosti.", "You have been banned from %(roomName)s by %(userName)s.": "Používateľ %(userName)s vám zakázal vstúpiť do miestnosti %(roomName)s.", "You have been banned from this room by %(userName)s.": "Používateľ %(userName)s vám zakázal vstúpiť do tejto miestnosti.", "This room": "Táto miestnosť", @@ -364,7 +364,7 @@ "Privileged Users": "Poverení používatelia", "No users have specific privileges in this room": "Žiadny používatelia nemajú v tejto miestnosti pridelené konkrétne poverenia", "Banned users": "Používatelia, ktorým bol zakázaný vstup", - "This room is not accessible by remote Matrix servers": "Táto miestnosť nie je prístupná cez vzdialené Matrix servery", + "This room is not accessible by remote Matrix servers": "Táto miestnosť nie je prístupná zo vzdialených Matrix serverov", "Leave room": "Opustiť miestnosť", "Favourite": "Obľúbená", "Tagged as: ": "Označená ako: ", @@ -375,7 +375,7 @@ "Only people who have been invited": "Len pozvaní ľudia", "Anyone who knows the room's link, apart from guests": "Ktokoľvek, kto pozná odkaz do miestnosti (okrem hostí)", "Anyone who knows the room's link, including guests": "Ktokoľvek, kto pozná odkaz do miestnosti (vrátane hostí)", - "Publish this room to the public in %(domain)s's room directory?": "Uverejniť túto miestnosť v adresáry miestností na servery %(domain)s?", + "Publish this room to the public in %(domain)s's room directory?": "Uverejniť túto miestnosť v adresári miestností na serveri %(domain)s?", "Who can read history?": "Kto môže čítať históriu?", "Anyone": "Ktokoľvek", "Members only (since the point in time of selecting this option)": "Len členovia (odkedy je aktívna táto voľba)", @@ -387,7 +387,7 @@ "To send messages, you must be a": "Aby ste mohli posielať správy, musíte byť", "To invite users into the room, you must be a": "Aby ste mohli pozývať používateľov do miestnosti, musíte byť", "To configure the room, you must be a": "Aby ste mohli nastavovať miestnosť, musíte byť", - "To kick users, you must be a": "Aby ste mohli vykopávať používateľov, musíte byť", + "To kick users, you must be a": "Aby ste mohli vykazovať používateľov, musíte byť", "To ban users, you must be a": "Aby ste používateľom mohli zakazovať vstup, musíte byť", "To remove other users' messages, you must be a": "Aby ste mohli odstraňovať správy, ktoré poslali iní používatelia, musíte byť", "To send events of type , you must be a": "Aby ste mohli posielať udalosti typu , musíte byť", @@ -437,7 +437,7 @@ "Sign in with CAS": "Prihlásiť sa s použitím CAS", "Custom Server Options": "Vlastné možnosti servera", "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Vlastné nastavenia servera môžete použiť na pripojenie k iným serverom Matrix a to zadaním URL adresy domovského servera.", - "This allows you to use this app with an existing Matrix account on a different home server.": "Umožní vám to použiť túto aplikáciu s už existujúcim Matrix účtom na akomkoľvek domovskom servery.", + "This allows you to use this app with an existing Matrix account on a different home server.": "Umožní vám to použiť túto aplikáciu s už existujúcim Matrix účtom na akomkoľvek domovskom serveri.", "You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Môžete tiež zadať vlastnú adresu servera totožností, čo však za štandardných okolností znemožní interakcie medzi používateľmi založené emailovou adresou.", "Dismiss": "Zamietnuť", "To continue, please enter your password.": "Aby ste mohli pokračovať, prosím zadajte svoje heslo.", @@ -452,7 +452,7 @@ "User name": "Meno používateľa", "Mobile phone number": "Číslo mobilného telefónu", "Forgot your password?": "Zabudli ste heslo?", - "%(serverName)s Matrix ID": "Matrix ID na servery %(serverName)s", + "%(serverName)s Matrix ID": "Matrix ID na serveri %(serverName)s", "Sign in with": "Na prihlásenie sa použije", "Email address": "Emailová adresa", "Sign in": "Prihlásiť sa", @@ -539,10 +539,10 @@ "were unbanned %(count)s times|one": "mali povolený vstup", "was unbanned %(count)s times|other": "mal %(count)s krát povolený vstup", "was unbanned %(count)s times|one": "mal povolený vstup", - "were kicked %(count)s times|other": "boli %(count)s krát vykopnutí", - "were kicked %(count)s times|one": "boli vykopnutí", - "was kicked %(count)s times|other": "bol %(count)s krát vykopnutý", - "was kicked %(count)s times|one": "bol vykopnutý", + "were kicked %(count)s times|other": "boli %(count)s krát vykázaní", + "were kicked %(count)s times|one": "boli vykázaní", + "was kicked %(count)s times|other": "bol %(count)s krát vykázaný", + "was kicked %(count)s times|one": "bol vykázaný", "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)ssi %(count)s krát zmenili meno", "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)ssi zmenili meno", "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)ssi %(count)s krát zmenil meno", @@ -621,7 +621,7 @@ "An error occurred: %(error_string)s": "Vyskytla sa chyba: %(error_string)s", "Username available": "Používateľské meno je k dispozícii", "To get started, please pick a username!": "Začnite tým, že si zvolíte používateľské meno!", - "This will be your account name on the homeserver, or you can pick a different server.": "Toto bude názov vašeho účtu na domovskom servery , alebo si môžete zvoliť iný server.", + "This will be your account name on the homeserver, or you can pick a different server.": "Toto bude názov vašeho účtu na domovskom serveri , alebo si môžete zvoliť iný server.", "If you already have a Matrix account you can log in instead.": "Ak už máte Matrix účet, môžete sa hneď Prihlásiť.", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Momentálne sa ku všetkym neovereným zariadeniam správate ako by boli na čiernej listine; aby ste na tieto zariadenia mohli posielať správy, mali by ste ich overiť.", "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.": "Odporúčame vám prejsť procesom overenia pre všetky tieto zariadenia aby ste si potvrdili, že skutočne patria ich pravým vlastníkom, ak si to však želáte, môžete tiež znovu poslať správu bez overovania.", @@ -725,7 +725,7 @@ "Don't send typing notifications": "Neposielať oznámenia keď píšete", "Always show message timestamps": "Vždy zobrazovať časovú značku správ", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Pri zobrazovaní časových značiek používať 12 hodinový formát (napr. 2:30pm)", - "Hide join/leave messages (invites/kicks/bans unaffected)": "Skryť správy o vstupe a opustení miestnosti (netýka sa pozvaní/vykopnutí/zákazov vstupu)", + "Hide join/leave messages (invites/kicks/bans unaffected)": "Skryť správy o vstupe a opustení miestnosti (netýka sa pozvaní/vykázaní/zákazov vstupu)", "Use compact timeline layout": "Použiť kompaktné rozloženie časovej osy", "Hide removed messages": "Skryť odstránené správy", "Enable automatic language detection for syntax highlighting": "Povoliť automatickú detegciu jazyka pre zvýrazňovanie syntaxe", @@ -775,8 +775,8 @@ "No media permissions": "Žiadne oprávnenia k médiám", "You may need to manually permit Riot to access your microphone/webcam": "Mali by ste aplikácii Riot ručne udeliť právo pristupovať k mikrofónu a kamere", "Missing Media Permissions, click here to request.": "Kliknutím sem vyžiadate chýbajúce oprávnenia na prístup k mediálnym zariadeniam.", - "No Microphones detected": "Neboli nájdené žiadne mikrofóny", - "No Webcams detected": "Neboli nájdené žiadne kamery", + "No Microphones detected": "Neboli rozpoznané žiadne mikrofóny", + "No Webcams detected": "Neboli rozpoznané žiadne kamery", "Default Device": "Predvolené zariadenie", "Microphone": "Mikrofón", "Camera": "Kamera", @@ -813,7 +813,7 @@ "Create an account": "Vytvoriť účet", "This Home Server does not support login using email address.": "Tento domovský server nepodporuje prihlasovanie sa emailom.", "Incorrect username and/or password.": "Nesprávne meno používateľa a / alebo heslo.", - "Guest access is disabled on this Home Server.": "Na tomto domovskom servery je zakázaný prístup pre hostí.", + "Guest access is disabled on this Home Server.": "Na tomto domovskom serveri je zakázaný prístup pre hostí.", "The phone number entered looks invalid": "Zdá sa, že zadané telefónne číslo je neplatné", "Error: Problem communicating with the given homeserver.": "Chyba: Nie je možné komunikovať so zadaným domovským serverom.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "K domovskému serveru nie je možné pripojiť sa použitím protokolu HTTP keďže v adresnom riadku prehliadača máte HTTPS adresu. Použite protokol HTTPS alebo povolte nezabezpečené skripty.", @@ -838,7 +838,7 @@ "Invites user with given id to current room": "Pošle používateľovi so zadaným ID pozvanie do tejto miestnosti", "Joins room with given alias": "Vstúpi do miestnosti so zadaným aliasom", "Sets the room topic": "Nastaví tému miestnosti", - "Kicks user with given id": "Vykopne používateľa so zadaným ID", + "Kicks user with given id": "Vykáže používateľa so zadaným ID", "Changes your display nickname": "Zmení vaše zobrazované meno", "Searches DuckDuckGo for results": "Vyhľadá výsledky na DuckDuckGo", "Changes colour scheme of current room": "Zmení farebnú schému aktuálnej miestnosti", @@ -884,7 +884,7 @@ "Sign in to get started": "Začnite prihlásením sa", "Status.im theme": "Téma status.im", "Please note you are logging into the %(hs)s server, not matrix.org.": "Všimnite si: Práve sa prihlasujete na server %(hs)s, nie na server matrix.org.", - "Username on %(hs)s": "Meno používateľa na servery %(hs)s", + "Username on %(hs)s": "Meno používateľa na serveri %(hs)s", "Restricted": "Obmedzené", "Hide avatar changes": "Skryť zmeny obrázka v profile", "Hide display name changes": "Skryť zmeny zobrazovaného mena", @@ -957,7 +957,7 @@ "Failed to remove tag %(tagName)s from room": "Z miestnosti sa nepodarilo odstrániť značku %(tagName)s", "Failed to add tag %(tagName)s to room": "Miestnosti sa nepodarilo pridať značku %(tagName)s", "In reply to ": "Odpoveď na ", - "Community IDs cannot not be empty.": "ID komunity nemôže ostať prázdne.", + "Community IDs cannot be empty.": "ID komunity nemôže ostať prázdne.", "Show devices, send anyway or cancel.": "Zobraziť zariadenia, napriek tomu odoslať alebo zrušiť.", "Disable Community Filter Panel": "Zakázať panel Filter komunity", "Your key share request has been sent - please check your other devices for key share requests.": "Žiadosť o zdieľanie kľúčov bola odoslaná - Overte si zobrazenie žiadosti o zdieľanie kľúčov na vašich ostatných zariadeniach.", @@ -1095,7 +1095,7 @@ "When I'm invited to a room": "Pozvania vstúpiť do miestnosti", "Can't update user notification settings": "Nie je možné aktualizovať používateľské nastavenia oznamovania", "Notify for all other messages/rooms": "oznamovať všetky ostatné správy / miestnosti", - "Unable to look up room ID from server": "Nie je možné vyhľadať ID miestnosti na servery", + "Unable to look up room ID from server": "Nie je možné vyhľadať ID miestnosti na serveri", "Couldn't find a matching Matrix room": "Nie je možné nájsť zodpovedajúcu Matrix miestnosť", "Invite to this room": "Pozvať do tejto miestnosti", "You cannot delete this message. (%(code)s)": "Nemôžete vymazať túto správu. (%(code)s)", @@ -1180,5 +1180,30 @@ "Terms and Conditions": "Zmluvné podmienky", "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Ak chcete aj naďalej používať domovský server %(homeserverDomain)s, mali by ste si prečítať a odsúhlasiť naše zmluvné podmienky.", "Review terms and conditions": "Prečítať zmluvné podmienky", - "To notify everyone in the room, you must be a": "Aby ste mohli upozorňovať všetkých členov v miestnosti, musíte byť" + "To notify everyone in the room, you must be a": "Aby ste mohli upozorňovať všetkých členov v miestnosti, musíte byť", + "Encrypting": "Šifrovanie", + "Encrypted, not sent": "Zašifrované, ale neodoslané", + "Share Link to User": "Zdieľať odkaz na používateľa", + "Share room": "Zdieľaj miestnosť", + "Share Room": "Zdieľať miestnosť", + "Link to most recent message": "Odkaz na najnovšiu správu", + "Share User": "Zdieľať používateľa", + "Share Community": "Zdieľať komunitu", + "Link to selected message": "Odkaz na vybratú správu", + "COPY": "Kopírovať", + "Share Message": "Zdieľaj správu", + "No Audio Outputs detected": "Neboli rozpoznané žiadne zvukové výstupy", + "Audio Output": "Výstup zvuku", + "Try the app first": "Vyskúšať si aplikáciu", + "Share Room Message": "Zdieľať správu z miestnosti", + "The email field must not be blank.": "Email nemôže ostať prázdny.", + "The user name field must not be blank.": "Používateľské meno nemôže ostať prázdne.", + "The phone number field must not be blank.": "Telefónne číslo nemôže ostať prázdne.", + "The password field must not be blank.": "Heslo nemôže ostať prázdne.", + "Jitsi Conference Calling": "Konferenčné hovory Jitsi", + "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.": "Náhľady URL adries sú v šifrovaných miestnostiach ako je táto predvolene zakázané, aby ste si mohli byť istí, že obsah odkazov z vašej konverzácii nebude zaznamenaný na vašom domovskom serveri počas ich generovania.", + "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.": "Ak niekto vo svojej správe pošle URL adresu, môže byť zobrazený jej náhľad obsahujúci názov, popis a obrázok z cieľovej web stránky.", + "Call in Progress": "Prebiehajúci hovor", + "A call is already in progress!": "Jeden hovor už prebieha!", + "You have no historical rooms": "Nemáte žiadne historické miestnosti" } diff --git a/src/i18n/strings/sr.json b/src/i18n/strings/sr.json index 6d217d5349..b9ccbe2089 100644 --- a/src/i18n/strings/sr.json +++ b/src/i18n/strings/sr.json @@ -641,7 +641,7 @@ "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.": "Да ли сте сигурни да желите уклонити (обрисати) овај догађај? Знајте да брисање назива собе или мењање теме може опозвати измену.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "ИБ-јеви заједнице могу садржати само знакове a-z, 0-9, или '=_-./'", - "Community IDs cannot not be empty.": "ИБ-јеви заједнице не могу бити празни.", + "Community IDs cannot be empty.": "ИБ-јеви заједнице не могу бити празни.", "Something went wrong whilst creating your community": "Нешто је пошло наопако приликом стварања ваше заједнице", "Create Community": "Направи заједницу", "Community Name": "Назив заједнице", @@ -1177,5 +1177,24 @@ "Terms and Conditions": "Услови коришћења", "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Да бисте наставили са коришћењем Кућног сервера %(homeserverDomain)s морате погледати и пристати на наше услове коришћења.", "Review terms and conditions": "Погледај услове коришћења", - "Try the app first": "Пробајте прво апликацију" + "Try the app first": "Пробајте прво апликацију", + "Jitsi Conference Calling": "Jitsi конференцијско позивање", + "Encrypting": "Шифрујем", + "Encrypted, not sent": "Шифровано, непослато", + "Share Link to User": "Подели везу са корисником", + "Share room": "Подели собу", + "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.": "Деактивирањем вашег налога се ваше поруке неће заборавити. Ако желите да заборавимо ваше поруке, штиклирајте кућицу испод.", + "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)": "Заборавите све моје поруке које сам послао када се мој налог деактивира (Упозорење: овим ће будући корисници видети непотпуне разговоре)", + "Share Room": "Подели собу", + "Link to most recent message": "Веза ка најновијој поруци", + "Share User": "Подели корисника", + "Share Community": "Подели заједницу", + "Share Room Message": "Подели поруку у соби", + "Link to selected message": "Веза ка изабраној поруци", + "COPY": "КОПИРАЈ", + "Share Message": "Подели поруку", + "No Audio Outputs detected": "Нема уочених излаза звука", + "Audio Output": "Излаз звука" } diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 0e26125c30..597c68c474 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -1034,7 +1034,7 @@ "Failed to remove room from community": "Det gick inte att ta bort rum från community", "Only visible to community members": "Endast synlig för community-medlemmar", "Filter community rooms": "Filtrera community-rum", - "Community IDs cannot not be empty.": "Community-ID kan inte vara tomt.", + "Community IDs cannot be empty.": "Community-ID kan inte vara tomt.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community-ID får endast innehålla tecknen a-z, 0-9 och '=_-./'", "Something went wrong whilst creating your community": "Något gick fel när din community skapades", "Create Community": "Skapa community", @@ -1180,5 +1180,21 @@ "This room is not showing flair for any communities": "Detta rum visar inte emblem för några communityn", "Flair will appear if enabled in room settings": "Emblem kommer visas om det är aktiverat i rumsinställningarna", "Flair will not appear": "Emblem kommer inte att visas", - "Display your community flair in rooms configured to show it.": "Visa ditt community-emblem i rum som är konfigurerade för att visa det." + "Display your community flair in rooms configured to show it.": "Visa ditt community-emblem i rum som är konfigurerade för att visa det.", + "Jitsi Conference Calling": "Jitsi konferenssamtal", + "Encrypting": "Krypterar", + "Encrypted, not sent": "Krypterat, inte skickat", + "Share Link to User": "Dela länk till användare", + "Share room": "Dela rum", + "Share Room": "Dela rum", + "Link to most recent message": "Länk till senaste meddelandet", + "Share User": "Dela användare", + "Share Community": "Dela community", + "Share Room Message": "Dela rumsmeddelande", + "Link to selected message": "Länk till valt meddelande", + "COPY": "KOPIERA", + "Share Message": "Dela meddelande", + "No Audio Outputs detected": "Inga ljudutgångar hittades", + "Audio Output": "Ljudutgång", + "Try the app first": "Testa appen först" } diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 5e7cc75f8a..b36642691c 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -279,5 +279,55 @@ "Your User Agent": "Ваш користувацький агент", "Your device resolution": "Роздільність вашого пристрою", "Analytics": "Аналітика", - "The information being sent to us to help make Riot.im better includes:": "Надсилана інформація, що допомагає нам покращити Riot.im, вміщує:" + "The information being sent to us to help make Riot.im better includes:": "Надсилана інформація, що допомагає нам покращити Riot.im, вміщує:", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Введіть пароль для захисту експортованого файлу. Щоб розшифрувати файл потрібно буде ввести цей пароль.", + "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.": "У цій кімнаті є невідомі пристрої: якщо ви продовжите без їхньої перевірки, зважайте на те, що вас можна буде прослуховувати.", + "Review Devices": "Перевірити пристрої", + "Call Anyway": "Подзвонити все одно", + "Answer Anyway": "Відповісти все одно", + "Call": "Подзвонити", + "Answer": "Відповісти", + "The remote side failed to pick up": "На ваш дзвінок не змогли відповісти", + "Unable to capture screen": "Не вдалось захопити екран", + "Existing Call": "Наявний виклик", + "You are already in a call.": "Ви вже розмовляєте.", + "VoIP is unsupported": "VoIP не підтримується", + "You cannot place VoIP calls in this browser.": "Цей оглядач не підтримує VoIP дзвінки.", + "You cannot place a call with yourself.": "Ви не можете подзвонити самим собі.", + "Conference calls are not supported in encrypted rooms": "Режим конференції не підтримується у зашифрованих кімнатах", + "Conference calls are not supported in this client": "Режим конференції не підтримується у цьому клієнті", + "Warning!": "Увага!", + "Conference calling is in development and may not be reliable.": "Режим конференції ще знаходиться в стані розробки та може бути ненадійним.", + "Failed to set up conference call": "Не вдалось встановити конференцію", + "Conference call failed.": "Конференц-виклик зазнав невдачі.", + "The file '%(fileName)s' failed to upload": "Не вдалось відвантажити файл '%(fileName)s'", + "The file '%(fileName)s' exceeds this home server's size limit for uploads": "Файл '%(fileName)s' перевищує максимальні розміри, дозволені на цьому сервері", + "Upload Failed": "Помилка відвантаження", + "Sun": "Нд", + "Mon": "Пн", + "Tue": "Вт", + "Wed": "Ср", + "Thu": "Чт", + "Fri": "Пт", + "Sat": "Сб", + "Jan": "Січ", + "Feb": "Лют", + "Mar": "Бер", + "Apr": "Квіт", + "May": "Трав", + "Jun": "Чер", + "Jul": "Лип", + "Aug": "Сер", + "Sep": "Вер", + "Oct": "Жов", + "Nov": "Лис", + "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", + "Who would you like to add to this community?": "Кого ви хочете додати до цієї спільноти?" } diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index 56d5c1e4dc..b83873d207 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -35,7 +35,7 @@ "Event information": "事件信息", "Existing Call": "当前通话", "Export E2E room keys": "导出聊天室的端到端加密密钥", - "Failed to ban user": "封禁用户失败", + "Failed to ban user": "封禁失败", "Failed to change password. Is your password correct?": "修改密码失败。确认原密码输入正确吗?", "Failed to forget room %(errCode)s": "忘记聊天室失败,错误代码: %(errCode)s", "Failed to join room": "无法加入聊天室", @@ -103,7 +103,7 @@ "Server may be unavailable or overloaded": "服务器可能不可用或者超载", "Server may be unavailable, overloaded, or search timed out :(": "服务器可能不可用、超载,或者搜索超时 :(", "Server may be unavailable, overloaded, or the file too big": "服务器可能不可用、超载,或者文件过大", - "Server may be unavailable, overloaded, or you hit a bug.": "服务器可能不可用、超载,或者你遇到了一个 bug。", + "Server may be unavailable, overloaded, or you hit a bug.": "当前服务器可能处于不可用或过载状态,或者您遇到了一个 bug。", "Server unavailable, overloaded, or something else went wrong.": "服务器可能不可用、超载,或者其他东西出错了.", "Session ID": "会话 ID", "%(senderName)s set a profile picture.": "%(senderName)s 设置了头像。.", @@ -179,7 +179,7 @@ "Are you sure you want to reject the invitation?": "你确定要拒绝邀请吗?", "Are you sure you want to upload the following files?": "你确定要上传这些文件吗?", "Bans user with given id": "按照 ID 封禁指定的用户", - "Blacklisted": "已列入黑名单", + "Blacklisted": "已拉黑", "Bulk Options": "批量操作", "Call Timeout": "通话超时", "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.": "无法连接主服务器 - 请检查网络连接,确保你的主服务器 SSL 证书被信任,且没有浏览器插件拦截请求。", @@ -192,7 +192,7 @@ "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s 将话题修改为 “%(topic)s”。", "Changes to who can read history will only apply to future messages in this room": "修改阅读历史的权限仅对此聊天室以后的消息有效", "Changes your display nickname": "修改昵称", - "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.": "目前,修改密码会导致所有设备上的端到端密钥被重置,使得加密的聊天记录不再可读。除非事先导出你的密钥,并在密码修改后导入回去。此问题将会在未来得到改善。", "Clear Cache and Reload": "清除缓存并刷新", "Clear Cache": "清除缓存", "Click here to join the discussion!": "点此 加入讨论!", @@ -222,8 +222,8 @@ "Drop File Here": "把文件拖拽到这里", "Email address (optional)": "邮箱地址 (可选)", "Enable Notifications": "启用消息通知", - "Encrypted by a verified device": "由一个已验证的设备加密", - "Encrypted by an unverified device": "由一个未经验证的设备加密", + "Encrypted by a verified device": "由已验证设备加密", + "Encrypted by an unverified device": "由未验证设备加密", "Encryption is enabled in this room": "此聊天室启用了加密", "Encryption is not enabled in this room": "此聊天室未启用加密", "Enter passphrase": "输入密码", @@ -254,7 +254,7 @@ "Moderator": "协管员", "Mute": "静音", "Name": "姓名", - "Never send encrypted messages to unverified devices from this device": "在此设备上不向未经验证的设备发送消息", + "Never send encrypted messages to unverified devices from this device": "在此设备上,从不对未经验证的设备发送消息", "New passwords don't match": "两次输入的新密码不符", "none": "无", "not set": "未设置", @@ -320,7 +320,7 @@ "VoIP conference started.": "VoIP 会议开始。", "VoIP is unsupported": "不支持 VoIP", "Warning!": "警告!", - "You must register to use this functionality": "你必须注册以使用这个功能", + "You must register to use this functionality": "你必须 注册 以使用此功能", "You need to be logged in.": "你需要登录。", "You need to enter a user name.": "你需要输入一个用户名。", "Your password has been reset": "你的密码已被重置", @@ -394,29 +394,29 @@ "Kicks user with given id": "按照 ID 移除特定的用户", "Last seen": "最近一次上线", "Level:": "级别:", - "Local addresses for this room:": "这个聊天室的本地地址:", + "Local addresses for this room:": "此聊天室的本地地址:", "New passwords must match each other.": "新密码必须互相匹配。", "Power level must be positive integer.": "权限级别必须是正整数。", "Reason: %(reasonText)s": "理由: %(reasonText)s", "Revoke Moderator": "撤销主持人", "Revoke widget access": "撤销小部件的访问", - "Remote addresses for this room:": "这个聊天室的远程地址:", + "Remote addresses for this room:": "此聊天室的远程地址:", "Remove Contact Information?": "移除联系人信息?", "Remove %(threePid)s?": "移除 %(threePid)s?", "Results from DuckDuckGo": "来自 DuckDuckGo 的结果", "Room contains unknown devices": "聊天室包含未知设备", "%(roomName)s does not exist.": "%(roomName)s 不存在。", "Save": "保存", - "Send anyway": "无论任何都发送", + "Send anyway": "仍然发送", "Sets the room topic": "设置聊天室主题", "Show Text Formatting Toolbar": "显示文字格式工具栏", - "This room has no local addresses": "这个聊天室没有本地地址", + "This room has no local addresses": "此聊天室没有本地地址", "This doesn't appear to be a valid email address": "这看起来不是一个合法的邮箱地址", - "This is a preview of this room. Room interactions have been disabled": "这是这个聊天室的一个预览。聊天室交互已禁用", + "This is a preview of this room. Room interactions have been disabled": "这是此聊天室的预览。交互操作已被禁用", "This phone number is already in use": "此手机号码已被使用", - "This room": "这个聊天室", - "This room is not accessible by remote Matrix servers": "这个聊天室无法被远程 Matrix 服务器访问", - "This room's internal ID is": "这个聊天室的内部 ID 是", + "This room": "此聊天室", + "This room is not accessible by remote Matrix servers": "此聊天室无法被远程 Matrix 服务器访问", + "This room's internal ID is": "此聊天室的内部 ID 为", "Turn Markdown off": "禁用 Markdown", "Turn Markdown on": "启用 Markdown", "Unable to create widget.": "无法创建小部件。", @@ -427,7 +427,7 @@ "Undecryptable": "无法解密的", "Unencrypted room": "未加密的聊天室", "unencrypted": "未加密的", - "Unencrypted message": "未加密的消息", + "Unencrypted message": "未加密消息", "unknown caller": "未知呼叫者", "unknown device": "未知设备", "Unnamed Room": "未命名的聊天室", @@ -438,7 +438,7 @@ "Upload file": "上传文件", "Usage": "用法", "Who can read history?": "谁可以阅读历史消息?", - "You are not in this room.": "你不在这个聊天室。", + "You are not in this room.": "您不在此聊天室中。", "You have no visible notifications": "你没有可见的通知", "Missing password.": "缺少密码。", "Passwords don't match.": "密码不匹配。", @@ -449,9 +449,9 @@ "Do you want to load widget from URL:": "你想从此 URL 加载小组件吗:", "Hide join/leave messages (invites/kicks/bans unaffected)": "隐藏加入/退出消息(邀请/踢出/封禁不受影响)", "Integrations Error": "集成错误", - "Publish this room to the public in %(domain)s's room directory?": "把这个聊天室发布到 %(domain)s 的聊天室目录吗?", + "Publish this room to the public in %(domain)s's room directory?": "是否将此聊天室发布至 %(domain)s 的聊天室目录中?", "Manage Integrations": "管理集成", - "No users have specific privileges in this room": "没有用户在这个聊天室有特殊权限", + "No users have specific privileges in this room": "此聊天室中没有用户有特殊权限", "%(senderName)s placed a %(callType)s call.": "%(senderName)s 发起了一个 %(callType)s 通话。", "Please check your email and click on the link it contains. Once this is done, click continue.": "请检查你的电子邮箱并点击里面包含的链接。完成时请点击继续。", "Press to start a chat with someone": "按下 来开始和某个人聊天", @@ -480,7 +480,7 @@ "Refer a friend to Riot:": "介绍朋友加入Riot:", "%(roomName)s is not accessible at this time.": "%(roomName)s 此时无法访问。", "Start authentication": "开始认证", - "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.": "此聊天室可拥有的小部件数量已达到上限。", "The phone number entered looks invalid": "输入的手机号码看起来无效", "The remote side failed to pick up": "对方未能接听", "This Home Server does not support login using email address.": "HS不支持使用邮箱地址登陆。", @@ -501,15 +501,15 @@ "(no answer)": "(没有回答)", "(warning: cannot be disabled again!)": "(警告:无法再被禁用!)", "WARNING: Device already verified, but keys do NOT MATCH!": "警告:设备已经验证,但密钥不匹配!", - "Who can access this room?": "谁可以访问这个聊天室?", - "Who would you like to add to this room?": "你想把谁加入这个聊天室?", + "Who can access this room?": "谁有权访问此聊天室?", + "Who would you like to add to this room?": "你想把谁添加到此聊天室?", "Who would you like to communicate with?": "你想和谁交流?", "You are already in a call.": "您正在通话。", - "You do not have permission to do that in this room.": "你没有权限在这个聊天室里面做那件事。", + "You do not have permission to do that in this room.": "您没有进行此操作的权限。", "You are trying to access %(roomName)s.": "你正在尝试访问 %(roomName)s.", - "You cannot place VoIP calls in this browser.": "你不能在这个浏览器中发起 VoIP 通话。", - "You do not have permission to post to this room": "你没有发送到这个聊天室的权限", - "You have been invited to join this room by %(inviterName)s": "您已被 %(inviterName)s 邀请加入这个聊天室", + "You cannot place VoIP calls in this browser.": "无法在此浏览器中发起 VoIP 通话。", + "You do not have permission to post to this room": "您没有在此聊天室发送消息的权限", + "You have been invited to join this room by %(inviterName)s": "您已被 %(inviterName)s 邀请加入此聊天室", "You seem to be in a call, are you sure you want to quit?": "您似乎正在进行通话,确定要退出吗?", "You seem to be uploading files, are you sure you want to quit?": "您似乎正在上传文件,确定要退出吗?", "You should not yet trust it to secure data": "你不应该相信它来保护你的数据", @@ -520,7 +520,7 @@ "An unknown error occurred.": "一个未知错误出现了。", "An error occurred: %(error_string)s": "一个错误出现了: %(error_string)s", "Encrypt room": "加密聊天室", - "There are no visible files in this room": "这个聊天室里面没有可见的文件", + "There are no visible files in this room": "此聊天室中没有可见的文件", "Active call": "当前通话", "Verify...": "验证...", "Error decrypting audio": "解密音频时出错", @@ -544,34 +544,34 @@ "Would you like to accept or decline this invitation?": "你想要 接受 还是 拒绝 这个邀请?", "You already have existing direct chats with this user:": "你已经有和此用户的直接聊天:", "You're not in any rooms yet! Press to make a room or to browse the directory": "你现在还不再任何聊天室!按下 来创建一个聊天室或者 来浏览目录", - "You cannot place a call with yourself.": "你不能和你自己发起一个通话。", + "You cannot place a call with yourself.": "你怎么寂寞到要和自己打电话,不支持的啦。", "You have been kicked from %(roomName)s by %(userName)s.": "您已被 %(userName)s 从聊天室 %(roomName)s 中移除。", "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 disabled URL previews by default.": "你已经默认 禁用 链接预览。", "You have enabled URL previews by default.": "你已经默认 启用 链接预览。", "Your home server does not support device management.": "你的 home server 不支持设备管理。", "Set a display name:": "设置一个昵称:", - "This server does not support authentication with a phone number.": "这个服务器不支持用手机号码认证。", + "This server does not support authentication with a phone number.": "此服务器不支持使用手机号码认证。", "Password too short (min %(MIN_PASSWORD_LENGTH)s).": "密码过短(最短为 %(MIN_PASSWORD_LENGTH)s)。", - "Make this room private": "使这个聊天室私密", + "Make this room private": "将此聊天室转为私密聊天室", "Share message history with new users": "和新用户共享消息历史", "Copied!": "已复制!", "Failed to copy": "复制失败", "Sent messages will be stored until your connection has returned.": "已发送的消息会被保存直到你的连接回来。", "(~%(count)s results)|one": "(~%(count)s 个结果)", "(~%(count)s results)|other": "(~%(count)s 个结果)", - "Please select the destination room for this message": "请选择这条消息的目标聊天室", + "Please select the destination room for this message": "请选择此消息的目标聊天室", "Start automatically after system login": "在系统登录后自动启动", "Analytics": "统计分析服务", "Reject all %(invitedRooms)s invites": "拒绝所有 %(invitedRooms)s 邀请", - "You may wish to login with a different account, or add this email to this account.": "你可能希望用另外一个账户登录,或者添加这个电子邮件到这个账户上。", - "Sun": "星期日", - "Mon": "星期一", - "Tue": "星期二", - "Wed": "星期三", - "Thu": "星期四", - "Fri": "星期五", - "Sat": "星期六", + "You may wish to login with a different account, or add this email to this account.": "您可能是想要用另一个账户登录,或是将此电子邮件关联至当前账户。", + "Sun": "周日", + "Mon": "周一", + "Tue": "周二", + "Wed": "周三", + "Thu": "周四", + "Fri": "周五", + "Sat": "周六", "Jan": "一月", "Feb": "二月", "Mar": "三月", @@ -597,10 +597,10 @@ "The visibility of existing history will be unchanged": "现有历史记录的可见性不会改变", "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s 打开了端到端加密 (算法 %(algorithm)s).", "Unable to remove contact information": "无法移除联系人信息", - "Riot collects anonymous analytics to allow us to improve the application.": "Riot 收集匿名的分析数据来允许我们改善这个应用。", + "Riot collects anonymous analytics to allow us to improve the application.": "Riot 收集匿名的分析数据以允许我们改善它。", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" 包含你以前没见过的设备。", "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "你可以使用自定义的服务器选项来通过指定一个不同的主服务器 URL 来登录其他 Matrix 服务器。", - "This allows you to use this app with an existing Matrix account on a different home server.": "这允许你用一个已有在不同主服务器的 Matrix 账户使用这个应用。", + "This allows you to use this app with an existing Matrix account on a different home server.": "这允许你使用其他主服务器上的 Matrix 帐号。", "Please check your email to continue registration.": "请查看你的电子邮件以继续注册。", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "如果你不指定一个邮箱地址,你将不能重置你的密码。你确定吗?", "Home server URL": "主服务器 URL", @@ -650,9 +650,9 @@ "Hide avatar changes": "隐藏头像修改", "Hide display name changes": "隐藏昵称修改", "Disable big emoji in chat": "禁用聊天中的大Emoji", - "Never send encrypted messages to unverified devices in this room from this device": "在此设备上,在此聊天室中不向未经验证的设备发送加密的消息", - "Enable URL previews for this room (only affects you)": "在此聊天室启用链接预览(只影响你)", - "Enable URL previews by default for participants in this room": "对这个聊天室的参与者默认启用 链接预览", + "Never send encrypted messages to unverified devices in this room from this device": "在此设备上、此聊天室中,从不对未经验证的设备发送加密的消息", + "Enable URL previews for this room (only affects you)": "在此聊天室中启用链接预览(仅影响你)", + "Enable URL previews by default for participants in this room": "对此聊天室的所有成员默认启用链接预览", "Delete %(count)s devices|other": "删除了 %(count)s 个设备", "Delete %(count)s devices|one": "删除设备", "Select devices": "选择设备", @@ -729,24 +729,24 @@ "Your homeserver's URL": "您的主服务器的链接", "Your identity server's URL": "您的身份认证服务器的链接", "The information being sent to us to help make Riot.im better includes:": "将要为帮助 Riot.im 发展而发送的信息包含:", - "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "这个页面中含有可能能用于识别您身份的信息,比如聊天室、用户或群组 ID,在它们发送到服务器上之前,这些数据会被移除。", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "此页面中含有可用于识别您身份的信息,比如聊天室、用户或群组 ID,这些数据会在发送到服务器前被移除。", "%(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", - "Who would you like to add to this community?": "您想把谁添加到这个社区内?", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(monthName)s %(day)s %(time)s, %(weekDayName)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(fullYear)s %(monthName)s %(day)s, %(weekDayName)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(fullYear)s %(monthName)s %(day)s %(time)s, %(weekDayName)s", + "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": "警告:您添加的一切用户都将会对一切知道此社区的 ID 的人公开", "Name or matrix ID": "名称或 Matrix ID", - "Which rooms would you like to add to this community?": "您想把哪个聊天室添加到这个社区中?", + "Which rooms would you like to add to this community?": "您想把哪个聊天室添加到此社区中?", "Add rooms to the community": "添加聊天室到社区", "Add to community": "添加到社区", "Failed to invite users to community": "邀请用户到社区失败", "Disable Peer-to-Peer for 1:1 calls": "在一对一通话中禁用 P2P 对等网络", - "Enable inline URL previews by default": "默认启用网址预览", - "Disinvite this user?": "取消邀请此用户?", - "Kick this user?": "移除此用户?", - "Unban this user?": "解除此用户的封禁?", - "Ban this user?": "封紧此用户?", + "Enable inline URL previews by default": "默认启用链接预览", + "Disinvite this user?": "是否不再邀请此用户?", + "Kick this user?": "是否移除此用户?", + "Unban this user?": "是否解封此用户?", + "Ban this user?": "是否封禁此用户?", "Send an encrypted reply…": "发送加密的回复…", "Send a reply (unencrypted)…": "发送回复(未加密)…", "Send an encrypted message…": "发送加密消息…", @@ -766,7 +766,7 @@ "%(user)s is a %(userRole)s": "%(user)s 是一个 %(userRole)s", "To link to a room it must have an address.": "要链接一个聊天室,它必须有一个地址。", "To send events of type , you must be a": "要发送类型为 的事件,你必须是", - "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 joined)": "只有成员(从他们加入开始)", "Invalid community ID": "无效的社区 ID", @@ -803,8 +803,8 @@ "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.": "密钥共享请求将会自动发送到您的其他设备上。如果您在其他设备上拒绝了请求,请点击此处以再次请求此会话的密钥。", "If your other devices do not have the key for this message you will not be able to decrypt them.": "如果您的其他设备上没有此消息的密钥,您将依然无法解密。", - "Key request sent.": "已请求共享密钥。", - "Re-request encryption keys from your other devices.": "在您的其他设备上 重新请求加密密钥。", + "Key request sent.": "已发送密钥共享请求。", + "Re-request encryption keys from your other devices.": "从其他设备上 重新请求密钥。", "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "如果您是房间中最后一位有权限的用户,在您降低自己的权限等级后将无法撤回此修改,因为你将无法重新获得权限。", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "你将无法撤回此修改,因为您正在将此用户的权限提升至和你相同的级别。", "No devices with registered encryption keys": "没有设备有已注册的加密密钥", @@ -877,7 +877,7 @@ "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)s 撤回了他们的邀请", "Custom of %(powerLevel)s": "", "In reply to ": "回复给 ", - "Community IDs cannot not be empty.": "社区 ID 不能为空。", + "Community IDs cannot be empty.": "社区 ID 不能为空。", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "社区 ID 只能包含 a-z、0-9 或 “=_-./” 等字符", "Something went wrong whilst creating your community": "创建社区时出现问题", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "您目前默认将未经验证的设备列入黑名单;在发送消息到这些设备上之前,您必须先验证它们。", @@ -901,7 +901,7 @@ "Block users on other matrix homeservers from joining this room": "禁止其他 Matrix 主服务器上的用户加入此聊天室", "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:": "为验证此设备是否可信,请通过其他方式(例如面对面交换或拨打电话)与其拥有者联系,并询问他们该设备的用户设置中的密钥是否与以下密钥匹配:", "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.": "如果匹配,请点击下面的验证按钮。 如果不匹配,那么这可能说明其他人正在盗用此设备,而您应当点击黑名单按钮。", - "In future this verification process will be more sophisticated.": "未来,这个验证过程将会变得更加精致、巧妙一些。", + "In future this verification process will be more sophisticated.": "未来,此验证过程将更为精致、巧妙一些。", "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.": "我们建议您对每台设备进行验证以保证它们属于其合法所有者,但是您可以在不验证它们的情况下重新发送消息。", "

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 代码

\n

\n 你可以给社区的新成员们写些长长的社区简介来引导他们,或者放置\n 一些重要的链接\n

\n

\n 你甚至可以使用 标签\n

\n", "Add rooms to the community summary": "将聊天室添加到社区简介", @@ -946,13 +946,13 @@ "Opens the Developer Tools dialog": "打开开发者工具窗口", "Notify the whole room": "通知聊天室全体成员", "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.": "此操作允许您将加密聊天室中收到的消息的密钥导出为本地文件。您可以将文件导入其他 Matrix 客户端,以便让别的客户端在未收到密钥的情况下解密这些消息。", - "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.": "导出的文件将允许任何可以读取它的人解密任何他们可以看到的加密消息,因此您应该小心以确保其安全。为解决此问题,您应该在下面输入密码以加密导出的数据。只有输入相同的密码才能导入数据。", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "导出文件有密码保护。你需要在此输入密码以解密此文件。", "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.": "此操作允许您导入之前从另一个 Matrix 客户端中导出的加密密钥文件。导入完成后,您将能够解密那个客户端可以解密的加密消息。", "Ignores a user, hiding their messages from you": "忽略用户,隐藏他们的消息", "Stops ignoring a user, showing their messages going forward": "解除忽略用户,显示他们的消息", "To return to your account in future you need to set a password": "如果你想再次使用账号,您得为它设置一个密码", - "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.": "如果你在 GitHub 提交了一个 bug,调试日志可以帮助我们追踪这个问题。 调试日志包含应用程序使用数据,这包括您的用户名、您访问的房间或社区的 ID 或名称以及其他用户的用户名,不包扩聊天记录。", + "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.": "如果你在 GitHub 提交了一个 bug,调试日志可以帮助我们追踪这个问题。 调试日志包含应用程序使用数据,也就包括您的用户名、您访问的房间或社区的 ID 或名称,以及其他用户的用户名,但不包括聊天记录。", "Debug Logs Submission": "发送调试日志", "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "密码修改成功。在您在其他设备上重新登录之前,其他设备不会收到推送通知", "Tried to load a specific point in this room's timeline, but was unable to find it.": "尝试加载此房间的时间线的特定时间点,但是无法找到。", @@ -960,7 +960,7 @@ "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "現在 重新发送消息取消发送 。你也可以单独选择消息以重新发送或取消。", "Visibility in Room List": "是否在聊天室目录中可见", "Something went wrong when trying to get your communities.": "获取你加入的社区时发生错误。", - "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?": "删除小部件时将为聊天室中的所有成员删除。您确定要删除此小部件吗?", "Fetching third party location failed": "获取第三方位置失败", "A new version of Riot is available.": "Riot 有更新可用。", "Couldn't load home page": "不能加载首页", @@ -990,7 +990,7 @@ "Forget": "忘记", "#example": "#例子", "Hide panel": "隐藏面板", - "You cannot delete this image. (%(code)s)": "您不能删除这个图片。(%(code)s)", + "You cannot delete this image. (%(code)s)": "无法删除此图片。(%(code)s)", "Cancel Sending": "取消发送", "This Room": "此聊天室", "The Home Server may be too old to support third party networks": "主服务器可能太老旧无法支持第三方网络", @@ -1135,5 +1135,15 @@ "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "无法加载被回复的事件,它可能不存在,也可能是您没有权限查看它。", "And %(count)s more...|other": "和 %(count)s 个其他…", "Try using one of the following valid address types: %(validTypesList)s.": "请尝试使用以下的有效邮箱地址格式中的一种:%(validTypesList)s", - "Riot bugs are tracked on GitHub: create a GitHub issue.": "Riot 使用 GitHub 追踪 bug:在 GitHub 上创建新 Issue" + "Riot bugs are tracked on GitHub: create a GitHub issue.": "Riot 使用 GitHub 追踪 bug:在 GitHub 上创建新 Issue", + "e.g. %(exampleValue)s": "例如:%(exampleValue)s", + "Call in Progress": "正在通话", + "A call is already in progress!": "您已在通话中!", + "Jitsi Conference Calling": "Jitsi 电话会议", + "Send analytics data": "发送统计数据", + "Enable widget screenshots on supported widgets": "对支持的小部件启用小部件截图", + "Encrypting": "正在加密", + "Encrypted, not sent": "已加密,未发送", + "Demote yourself?": "是否降低您自己的权限?", + "Demote": "降权" } diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index ebf329b45b..0a0ba8e9af 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -952,7 +952,7 @@ "Your identity server's URL": "您的驗證伺服器 URL", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "This room is not public. You will not be able to rejoin without an invite.": "這個聊天室並未公開。您在沒有邀請的情況下將無法重新加入。", - "Community IDs cannot not be empty.": "社群 ID 不能為空。", + "Community IDs cannot be empty.": "社群 ID 不能為空。", "Show devices, send anyway or cancel.": "顯示裝置無論如何都要傳送取消。", "In reply to ": "回覆給 ", "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s 變更了他的顯示名稱為 %(displayName)s 。", @@ -1195,5 +1195,16 @@ "Share Room Message": "分享聊天室訊息", "Link to selected message": "連結到選定的訊息", "COPY": "複製", - "Share Message": "分享訊息" + "Share Message": "分享訊息", + "Jitsi Conference Calling": "Jitsi 會議通話", + "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.": "在加密的聊天室中(這個就是),URL 預覽會預設停用以確保您的家伺服器(預覽生成的地方)無法在這個聊天室中收集關於您看到的連結的資訊。", + "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.": "當某人在他們的訊息中放置 URL 時,URL 預覽可以顯示如標題、描述與網頁上的圖片等等來給您更多關於該連結的資訊。", + "The email field must not be blank.": "電子郵件欄不能留空。", + "The user name field must not be blank.": "使用者名稱欄不能留空。", + "The phone number field must not be blank.": "電話號碼欄不能留空。", + "The password field must not be blank.": "密碼欄不能留空。", + "Call in Progress": "進行中的通話", + "A call is already in progress!": "已有一通電話進行中!", + "You have no historical rooms": "您沒有過去的聊天室", + "You can't send any messages until you review and agree to our terms and conditions.": "您在審閱並同意我們的條款與條件前無法傳送訊息。" } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 84178e2673..022fabc739 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -284,4 +284,9 @@ export const SETTINGS = { supportedLevels: ['room-device'], default: false, }, + "RoomSubList.showEmpty": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Show empty room list headings'), + default: true, + }, }; diff --git a/src/stores/ActiveWidgetStore.js b/src/stores/ActiveWidgetStore.js new file mode 100644 index 0000000000..7c179aef84 --- /dev/null +++ b/src/stores/ActiveWidgetStore.js @@ -0,0 +1,84 @@ +/* +Copyright 2018 New Vector Ltd + +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. +*/ + +/** + * Stores information about the widgets active in the app right now: + * * What widget is set to remain always-on-screen, if any + * Only one widget may be 'always on screen' at any one time. + * * Negotiated capabilities for active apps + */ +class ActiveWidgetStore { + constructor() { + this._persistentWidgetId = null; + + // A list of negotiated capabilities for each widget, by ID + // { + // widgetId: [caps...], + // } + this._capsByWidgetId = {}; + + // A WidgetMessaging instance for each widget ID + this._widgetMessagingByWidgetId = {}; + } + + setWidgetPersistence(widgetId, val) { + if (this._persistentWidgetId === widgetId && !val) { + this._persistentWidgetId = null; + } else if (this._persistentWidgetId !== widgetId && val) { + this._persistentWidgetId = widgetId; + } + } + + getWidgetPersistence(widgetId) { + return this._persistentWidgetId === widgetId; + } + + setWidgetCapabilities(widgetId, caps) { + this._capsByWidgetId[widgetId] = caps; + } + + widgetHasCapability(widgetId, cap) { + return this._capsByWidgetId[widgetId] && this._capsByWidgetId[widgetId].includes(cap); + } + + delWidgetCapabilities(widgetId) { + delete this._capsByWidgetId[widgetId]; + } + + setWidgetMessaging(widgetId, wm) { + this._widgetMessagingByWidgetId[widgetId] = wm; + } + + getWidgetMessaging(widgetId) { + return this._widgetMessagingByWidgetId[widgetId]; + } + + delWidgetMessaging(widgetId) { + if (this._widgetMessagingByWidgetId[widgetId]) { + try { + this._widgetMessagingByWidgetId[widgetId].stop(); + } catch (e) { + console.error('Failed to stop listening for widgetMessaging events', e.message); + } + delete this._widgetMessagingByWidgetId[widgetId]; + } + } +} + +if (global.singletonActiveWidgetStore === undefined) { + global.singletonActiveWidgetStore = new ActiveWidgetStore(); +} +export default global.singletonActiveWidgetStore; diff --git a/test/DecryptionFailureTracker-test.js b/test/DecryptionFailureTracker-test.js index 4979fb9bb4..617c9d5d68 100644 --- a/test/DecryptionFailureTracker-test.js +++ b/test/DecryptionFailureTracker-test.js @@ -16,10 +16,18 @@ limitations under the License. import expect from 'expect'; -import DecryptionFailureTracker from '../src/DecryptionFailureTracker'; +import { DecryptionFailure, DecryptionFailureTracker } from '../src/DecryptionFailureTracker'; import { MatrixEvent } from 'matrix-js-sdk'; +class MockDecryptionError extends Error { + constructor(code) { + super(); + + this.code = code || 'MOCK_DECRYPTION_ERROR'; + } +} + function createFailedDecryptionEvent() { const event = new MatrixEvent({ event_id: "event-id-" + Math.random().toString(16).slice(2), @@ -37,13 +45,14 @@ describe('DecryptionFailureTracker', function() { let count = 0; const tracker = new DecryptionFailureTracker((total) => count += total); - tracker.eventDecrypted(failedDecryptionEvent); + const err = new MockDecryptionError(); + tracker.eventDecrypted(failedDecryptionEvent, err); // Pretend "now" is Infinity tracker.checkFailures(Infinity); - // Immediately track the newest failure, if there is one - tracker.trackFailure(); + // Immediately track the newest failures + tracker.trackFailures(); expect(count).toNotBe(0, 'should track a failure for an event that failed decryption'); @@ -56,17 +65,18 @@ describe('DecryptionFailureTracker', function() { expect(true).toBe(false, 'should not track an event that has since been decrypted correctly'); }); - tracker.eventDecrypted(decryptedEvent); + const err = new MockDecryptionError(); + tracker.eventDecrypted(decryptedEvent, err); // Indicate successful decryption: clear data can be anything where the msgtype is not m.bad.encrypted decryptedEvent._setClearData({}); - tracker.eventDecrypted(decryptedEvent); + tracker.eventDecrypted(decryptedEvent, null); // Pretend "now" is Infinity tracker.checkFailures(Infinity); - // Immediately track the newest failure, if there is one - tracker.trackFailure(); + // Immediately track the newest failures + tracker.trackFailures(); done(); }); @@ -78,23 +88,24 @@ describe('DecryptionFailureTracker', function() { const tracker = new DecryptionFailureTracker((total) => count += total); // Arbitrary number of failed decryptions for both events - tracker.eventDecrypted(decryptedEvent); - tracker.eventDecrypted(decryptedEvent); - tracker.eventDecrypted(decryptedEvent); - tracker.eventDecrypted(decryptedEvent); - tracker.eventDecrypted(decryptedEvent); - tracker.eventDecrypted(decryptedEvent2); - tracker.eventDecrypted(decryptedEvent2); - tracker.eventDecrypted(decryptedEvent2); + const err = new MockDecryptionError(); + tracker.eventDecrypted(decryptedEvent, err); + tracker.eventDecrypted(decryptedEvent, err); + tracker.eventDecrypted(decryptedEvent, err); + tracker.eventDecrypted(decryptedEvent, err); + tracker.eventDecrypted(decryptedEvent, err); + tracker.eventDecrypted(decryptedEvent2, err); + tracker.eventDecrypted(decryptedEvent2, err); + tracker.eventDecrypted(decryptedEvent2, err); // Pretend "now" is Infinity tracker.checkFailures(Infinity); - // Simulated polling of `trackFailure`, an arbitrary number ( > 2 ) times - tracker.trackFailure(); - tracker.trackFailure(); - tracker.trackFailure(); - tracker.trackFailure(); + // Simulated polling of `trackFailures`, an arbitrary number ( > 2 ) times + tracker.trackFailures(); + tracker.trackFailures(); + tracker.trackFailures(); + tracker.trackFailures(); expect(count).toBe(2, count + ' failures tracked, should only track a single failure per event'); @@ -108,17 +119,18 @@ describe('DecryptionFailureTracker', function() { const tracker = new DecryptionFailureTracker((total) => count += total); // Indicate decryption - tracker.eventDecrypted(decryptedEvent); + const err = new MockDecryptionError(); + tracker.eventDecrypted(decryptedEvent, err); // Pretend "now" is Infinity tracker.checkFailures(Infinity); - tracker.trackFailure(); + tracker.trackFailures(); // Indicate a second decryption, after having tracked the failure - tracker.eventDecrypted(decryptedEvent); + tracker.eventDecrypted(decryptedEvent, err); - tracker.trackFailure(); + tracker.trackFailures(); expect(count).toBe(1, 'should only track a single failure per event'); @@ -135,25 +147,68 @@ describe('DecryptionFailureTracker', function() { const tracker = new DecryptionFailureTracker((total) => count += total); // Indicate decryption - tracker.eventDecrypted(decryptedEvent); + const err = new MockDecryptionError(); + tracker.eventDecrypted(decryptedEvent, err); // Pretend "now" is Infinity // NB: This saves to localStorage specific to DFT tracker.checkFailures(Infinity); - tracker.trackFailure(); + tracker.trackFailures(); // Simulate the browser refreshing by destroying tracker and creating a new tracker const secondTracker = new DecryptionFailureTracker((total) => count += total); //secondTracker.loadTrackedEventHashMap(); - secondTracker.eventDecrypted(decryptedEvent); + secondTracker.eventDecrypted(decryptedEvent, err); secondTracker.checkFailures(Infinity); - secondTracker.trackFailure(); + secondTracker.trackFailures(); expect(count).toBe(1, count + ' failures tracked, should only track a single failure per event'); done(); }); + + it('should count different error codes separately for multiple failures with different error codes', () => { + const counts = {}; + const tracker = new DecryptionFailureTracker( + (total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total, + ); + + // One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2 + tracker.addDecryptionFailure(new DecryptionFailure('$event_id1', 'ERROR_CODE_1')); + tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'ERROR_CODE_2')); + tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'ERROR_CODE_2')); + tracker.addDecryptionFailure(new DecryptionFailure('$event_id3', 'ERROR_CODE_2')); + + // Pretend "now" is Infinity + tracker.checkFailures(Infinity); + + tracker.trackFailures(); + + expect(counts['ERROR_CODE_1']).toBe(1, 'should track one ERROR_CODE_1'); + expect(counts['ERROR_CODE_2']).toBe(2, 'should track two ERROR_CODE_2'); + }); + + it('should map error codes correctly', () => { + const counts = {}; + const tracker = new DecryptionFailureTracker( + (total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total, + (errorCode) => 'MY_NEW_ERROR_CODE', + ); + + // One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2 + tracker.addDecryptionFailure(new DecryptionFailure('$event_id1', 'ERROR_CODE_1')); + tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'ERROR_CODE_2')); + tracker.addDecryptionFailure(new DecryptionFailure('$event_id3', 'ERROR_CODE_3')); + + // Pretend "now" is Infinity + tracker.checkFailures(Infinity); + + tracker.trackFailures(); + + expect(counts['MY_NEW_ERROR_CODE']) + .toBe(3, 'should track three MY_NEW_ERROR_CODE, got ' + counts['MY_NEW_ERROR_CODE']); + }); });