mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into t3chguy/remove_bluebird
commit
6121420113
|
@ -22,7 +22,7 @@ number throgh from the original code to the final application.
|
|||
General Style
|
||||
-------------
|
||||
- 4 spaces to indent, for consistency with Matrix Python.
|
||||
- 120 columns per line, but try to keep JavaScript code around the 80 column mark.
|
||||
- 120 columns per line, but try to keep JavaScript code around the 90 column mark.
|
||||
Inline JSX in particular can be nicer with more columns per line.
|
||||
- No trailing whitespace at end of lines.
|
||||
- Don't indent empty lines.
|
||||
|
|
|
@ -550,6 +550,22 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
color: $username-variant8-color;
|
||||
}
|
||||
|
||||
@define-mixin mx_Tooltip_dark {
|
||||
box-shadow: none;
|
||||
background-color: $tooltip-timeline-bg-color;
|
||||
color: $tooltip-timeline-fg-color;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
// This is a workaround for our mixins not supporting child selectors
|
||||
.mx_Tooltip_dark {
|
||||
.mx_Tooltip_chevron::after {
|
||||
border-right-color: $tooltip-timeline-bg-color;
|
||||
}
|
||||
}
|
||||
|
||||
@define-mixin mx_Settings_fullWidthField {
|
||||
margin-right: 100px;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ limitations under the License.
|
|||
width: 12px;
|
||||
height: 16px;
|
||||
content: "";
|
||||
mask: url("$(res)/img/e2e/normal.svg");
|
||||
mask-image: url("$(res)/img/e2e/normal.svg");
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100%;
|
||||
margin-top: 4px;
|
||||
|
@ -33,7 +33,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
&.mx_KeyVerification_icon_verified::after {
|
||||
mask: url("$(res)/img/e2e/verified.svg");
|
||||
mask-image: url("$(res)/img/e2e/verified.svg");
|
||||
background-color: $accent-color;
|
||||
}
|
||||
|
||||
|
|
|
@ -195,6 +195,8 @@ limitations under the License.
|
|||
.mx_UserInfo_devices {
|
||||
.mx_UserInfo_device {
|
||||
display: flex;
|
||||
margin: 8px 0;
|
||||
|
||||
|
||||
&.mx_UserInfo_device_verified {
|
||||
.mx_UserInfo_device_trusted {
|
||||
|
@ -210,6 +212,7 @@ limitations under the License.
|
|||
.mx_UserInfo_device_name {
|
||||
flex: 1;
|
||||
margin-right: 5px;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -294,49 +294,61 @@ form.mx_Custom_Widget_Form div {
|
|||
|
||||
.mx_AppPermissionWarning {
|
||||
text-align: center;
|
||||
background-color: $primary-bg-color;
|
||||
background-color: $widget-menu-bar-bg-color;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mx_AppPermissionWarningImage {
|
||||
margin: 10px 0;
|
||||
.mx_AppPermissionWarning_row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mx_AppPermissionWarningImage img {
|
||||
width: 100px;
|
||||
.mx_AppPermissionWarning_smallText {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mx_AppPermissionWarningText {
|
||||
max-width: 90%;
|
||||
margin: 10px auto 10px auto;
|
||||
color: $primary-fg-color;
|
||||
.mx_AppPermissionWarning_bolder {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mx_AppPermissionWarningTextLabel {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
.mx_AppPermissionWarning h4 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mx_AppPermissionWarningTextURL {
|
||||
.mx_AppPermissionWarning_helpIcon {
|
||||
margin-top: 1px;
|
||||
margin-right: 2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
color: $accent-color;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mx_AppPermissionButton {
|
||||
border: none;
|
||||
padding: 5px 20px;
|
||||
border-radius: 5px;
|
||||
background-color: $button-bg-color;
|
||||
color: $button-fg-color;
|
||||
cursor: pointer;
|
||||
.mx_AppPermissionWarning_helpIcon::before {
|
||||
display: inline-block;
|
||||
background-color: $accent-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
vertical-align: middle;
|
||||
mask-image: url('$(res)/img/feather-customised/help-circle.svg');
|
||||
}
|
||||
|
||||
.mx_AppPermissionWarning_tooltip {
|
||||
@mixin mx_Tooltip_dark;
|
||||
|
||||
ul {
|
||||
list-style-position: inside;
|
||||
padding-left: 2px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AppLoading {
|
||||
|
|
|
@ -22,21 +22,6 @@ limitations under the License.
|
|||
display: block;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_verified::before, .mx_E2EIcon_warning::before {
|
||||
content: "";
|
||||
display: block;
|
||||
/* the symbols in the shield icons are cut out to make it themeable with css masking.
|
||||
if they appear on a different background than white, the symbol wouldn't be white though, so we
|
||||
add a rectangle here below the masked element to shine through the symbol cut-out.
|
||||
hardcoding white and not using a theme variable as this would probably be white for any theme. */
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_verified::after, .mx_E2EIcon_warning::after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
@ -49,23 +34,11 @@ limitations under the License.
|
|||
mask-size: contain;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_verified::before {
|
||||
/* white rectangle below checkmark of shield */
|
||||
margin: 25% 28% 38% 25%;
|
||||
}
|
||||
|
||||
|
||||
.mx_E2EIcon_verified::after {
|
||||
mask-image: url('$(res)/img/e2e/verified.svg');
|
||||
background-color: $accent-color;
|
||||
}
|
||||
|
||||
|
||||
.mx_E2EIcon_warning::before {
|
||||
/* white rectangle below "!" of shield */
|
||||
margin: 18% 40% 25% 40%;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_warning::after {
|
||||
mask-image: url('$(res)/img/e2e/warning.svg');
|
||||
background-color: $warning-color;
|
||||
|
|
|
@ -25,6 +25,7 @@ limitations under the License.
|
|||
width: 12px;
|
||||
height: 12px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100%;
|
||||
}
|
||||
.mx_MemberDeviceInfo_icon_blacklisted {
|
||||
mask-image: url('$(res)/img/e2e/blacklisted.svg');
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import dis from './dispatcher';
|
||||
import BaseEventIndexManager from './indexing/BaseEventIndexManager';
|
||||
|
||||
/**
|
||||
* Base class for classes that provide platform-specific functionality
|
||||
|
@ -151,4 +152,14 @@ export default class BasePlatform {
|
|||
async setMinimizeToTrayEnabled(enabled: boolean): void {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our platform specific EventIndexManager.
|
||||
*
|
||||
* @return {BaseEventIndexManager} The EventIndex manager for our platform,
|
||||
* can be null if the platform doesn't support event indexing.
|
||||
*/
|
||||
getEventIndexingManager(): BaseEventIndexManager | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
import Matrix from 'matrix-js-sdk';
|
||||
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import EventIndexPeg from './indexing/EventIndexPeg';
|
||||
import createMatrixClient from './utils/createMatrixClient';
|
||||
import Analytics from './Analytics';
|
||||
import Notifier from './Notifier';
|
||||
|
@ -588,6 +589,7 @@ async function startMatrixClient(startSyncing=true) {
|
|||
|
||||
if (startSyncing) {
|
||||
await MatrixClientPeg.start();
|
||||
await EventIndexPeg.init();
|
||||
} else {
|
||||
console.warn("Caller requested only auxiliary services be started");
|
||||
await MatrixClientPeg.assign();
|
||||
|
@ -606,20 +608,20 @@ async function startMatrixClient(startSyncing=true) {
|
|||
* Stops a running client and all related services, and clears persistent
|
||||
* storage. Used after a session has been logged out.
|
||||
*/
|
||||
export function onLoggedOut() {
|
||||
export async function onLoggedOut() {
|
||||
_isLoggingOut = false;
|
||||
// Ensure that we dispatch a view change **before** stopping the client so
|
||||
// so that React components unmount first. This avoids React soft crashes
|
||||
// that can occur when components try to use a null client.
|
||||
dis.dispatch({action: 'on_logged_out'}, true);
|
||||
stopMatrixClient();
|
||||
_clearStorage();
|
||||
await _clearStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise} promise which resolves once the stores have been cleared
|
||||
*/
|
||||
function _clearStorage() {
|
||||
async function _clearStorage() {
|
||||
Analytics.logout();
|
||||
|
||||
if (window.localStorage) {
|
||||
|
@ -631,7 +633,9 @@ function _clearStorage() {
|
|||
// we'll never make any requests, so can pass a bogus HS URL
|
||||
baseUrl: "",
|
||||
});
|
||||
return cli.clearStores();
|
||||
|
||||
await EventIndexPeg.deleteEventIndex();
|
||||
await cli.clearStores();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -648,6 +652,7 @@ export function stopMatrixClient(unsetClient=true) {
|
|||
IntegrationManagers.sharedInstance().stopWatching();
|
||||
Mjolnir.sharedInstance().stop();
|
||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||
EventIndexPeg.stop();
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.stopClient();
|
||||
|
@ -655,6 +660,7 @@ export function stopMatrixClient(unsetClient=true) {
|
|||
|
||||
if (unsetClient) {
|
||||
MatrixClientPeg.unset();
|
||||
EventIndexPeg.unset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,6 +220,16 @@ class MatrixClientPeg {
|
|||
identityServer: new IdentityAuthClient(),
|
||||
};
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
// TODO: Cross-signing keys are temporarily in memory only. A
|
||||
// separate task in the cross-signing project will build from here.
|
||||
const keys = [];
|
||||
opts.cryptoCallbacks = {
|
||||
getCrossSigningKey: k => keys[k],
|
||||
saveCrossSigningKeys: newKeys => Object.assign(keys, newKeys),
|
||||
};
|
||||
}
|
||||
|
||||
this.matrixClient = createMatrixClient(opts);
|
||||
|
||||
// we're going to add eventlisteners for each matrix event tile, so the
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import EventIndexPeg from "./indexing/EventIndexPeg";
|
||||
import MatrixClientPeg from "./MatrixClientPeg";
|
||||
|
||||
function serverSideSearch(term, roomId = undefined) {
|
||||
let filter;
|
||||
if (roomId !== undefined) {
|
||||
// XXX: it's unintuitive that the filter for searching doesn't have
|
||||
// the same shape as the v2 filter API :(
|
||||
filter = {
|
||||
rooms: [roomId],
|
||||
};
|
||||
}
|
||||
|
||||
const searchPromise = MatrixClientPeg.get().searchRoomEvents({
|
||||
filter,
|
||||
term,
|
||||
});
|
||||
|
||||
return searchPromise;
|
||||
}
|
||||
|
||||
async function combinedSearch(searchTerm) {
|
||||
// Create two promises, one for the local search, one for the
|
||||
// server-side search.
|
||||
const serverSidePromise = serverSideSearch(searchTerm);
|
||||
const localPromise = localSearch(searchTerm);
|
||||
|
||||
// Wait for both promises to resolve.
|
||||
await Promise.all([serverSidePromise, localPromise]);
|
||||
|
||||
// Get both search results.
|
||||
const localResult = await localPromise;
|
||||
const serverSideResult = await serverSidePromise;
|
||||
|
||||
// Combine the search results into one result.
|
||||
const result = {};
|
||||
|
||||
// Our localResult and serverSideResult are both ordered by
|
||||
// recency separately, when we combine them the order might not
|
||||
// be the right one so we need to sort them.
|
||||
const compare = (a, b) => {
|
||||
const aEvent = a.context.getEvent().event;
|
||||
const bEvent = b.context.getEvent().event;
|
||||
|
||||
if (aEvent.origin_server_ts >
|
||||
bEvent.origin_server_ts) return -1;
|
||||
if (aEvent.origin_server_ts <
|
||||
bEvent.origin_server_ts) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
result.count = localResult.count + serverSideResult.count;
|
||||
result.results = localResult.results.concat(
|
||||
serverSideResult.results).sort(compare);
|
||||
result.highlights = localResult.highlights.concat(
|
||||
serverSideResult.highlights);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function localSearch(searchTerm, roomId = undefined) {
|
||||
const searchArgs = {
|
||||
search_term: searchTerm,
|
||||
before_limit: 1,
|
||||
after_limit: 1,
|
||||
order_by_recency: true,
|
||||
room_id: undefined,
|
||||
};
|
||||
|
||||
if (roomId !== undefined) {
|
||||
searchArgs.room_id = roomId;
|
||||
}
|
||||
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
||||
const localResult = await eventIndex.search(searchArgs);
|
||||
|
||||
const response = {
|
||||
search_categories: {
|
||||
room_events: localResult,
|
||||
},
|
||||
};
|
||||
|
||||
const emptyResult = {
|
||||
results: [],
|
||||
highlights: [],
|
||||
};
|
||||
|
||||
const result = MatrixClientPeg.get()._processRoomEventsSearch(
|
||||
emptyResult, response);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function eventIndexSearch(term, roomId = undefined) {
|
||||
let searchPromise;
|
||||
|
||||
if (roomId !== undefined) {
|
||||
if (MatrixClientPeg.get().isRoomEncrypted(roomId)) {
|
||||
// The search is for a single encrypted room, use our local
|
||||
// search method.
|
||||
searchPromise = localSearch(term, roomId);
|
||||
} else {
|
||||
// The search is for a single non-encrypted room, use the
|
||||
// server-side search.
|
||||
searchPromise = serverSideSearch(term, roomId);
|
||||
}
|
||||
} else {
|
||||
// Search across all rooms, combine a server side search and a
|
||||
// local search.
|
||||
searchPromise = combinedSearch(term);
|
||||
}
|
||||
|
||||
return searchPromise;
|
||||
}
|
||||
|
||||
export default function eventSearch(term, roomId = undefined) {
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
||||
if (eventIndex === null) return serverSideSearch(term, roomId);
|
||||
else return eventIndexSearch(term, roomId);
|
||||
}
|
|
@ -268,6 +268,7 @@ export default createReactClass({
|
|||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onThemeChanged);
|
||||
|
||||
this.focusComposer = false;
|
||||
|
||||
|
@ -354,6 +355,7 @@ export default createReactClass({
|
|||
componentWillUnmount: function() {
|
||||
Lifecycle.stopMatrixClient();
|
||||
dis.unregister(this.dispatcherRef);
|
||||
SettingsStore.unwatchSetting(this._themeWatchRef);
|
||||
window.removeEventListener("focus", this.onFocus);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
|
||||
|
@ -376,6 +378,13 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
_onThemeChanged: function(settingName, roomId, atLevel, newValue) {
|
||||
dis.dispatch({
|
||||
action: 'set_theme',
|
||||
value: newValue,
|
||||
});
|
||||
},
|
||||
|
||||
startPageChangeTimer() {
|
||||
// Tor doesn't support performance
|
||||
if (!performance || !performance.mark) return null;
|
||||
|
@ -1370,17 +1379,6 @@ export default createReactClass({
|
|||
}, null, true);
|
||||
});
|
||||
|
||||
cli.on("accountData", function(ev) {
|
||||
if (ev.getType() === 'im.vector.web.settings') {
|
||||
if (ev.getContent() && ev.getContent().theme) {
|
||||
dis.dispatch({
|
||||
action: 'set_theme',
|
||||
value: ev.getContent().theme,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const dft = new DecryptionFailureTracker((total, errorCode) => {
|
||||
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, total);
|
||||
}, (errorCode) => {
|
||||
|
|
|
@ -185,7 +185,7 @@ export default class RightPanel extends React.Component {
|
|||
} else if (this.state.phase === RightPanel.Phase.GroupRoomList) {
|
||||
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
|
||||
} else if (this.state.phase === RightPanel.Phase.RoomMemberInfo) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_user_info_panel")) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
||||
const onClose = () => {
|
||||
dis.dispatch({
|
||||
action: "view_user",
|
||||
|
@ -204,7 +204,7 @@ export default class RightPanel extends React.Component {
|
|||
} else if (this.state.phase === RightPanel.Phase.Room3pidMemberInfo) {
|
||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
|
||||
} else if (this.state.phase === RightPanel.Phase.GroupMemberInfo) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_user_info_panel")) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
||||
const onClose = () => {
|
||||
dis.dispatch({
|
||||
action: "view_user",
|
||||
|
|
|
@ -289,7 +289,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
return <div className="mx_RoomStatusBar_connectionLostBar">
|
||||
<img src={require("../../../res/img/e2e/warning.svg")} width="24" height="24" title={_t("Warning")} alt="" />
|
||||
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24" height="24" title={_t("Warning")} alt="" />
|
||||
<div>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||
{ title }
|
||||
|
@ -306,7 +306,7 @@ module.exports = createReactClass({
|
|||
if (this._shouldShowConnectionError()) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_connectionLostBar">
|
||||
<img src={require("../../../res/img/e2e/warning.svg")} width="24" height="24" title="/!\ " alt="/!\ " />
|
||||
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24" height="24" title="/!\ " alt="/!\ " />
|
||||
<div>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||
{ _t('Connectivity to the server has been lost.') }
|
||||
|
|
|
@ -42,6 +42,7 @@ import Tinter from '../../Tinter';
|
|||
import rate_limited_func from '../../ratelimitedfunc';
|
||||
import ObjectUtils from '../../ObjectUtils';
|
||||
import * as Rooms from '../../Rooms';
|
||||
import eventSearch from '../../Searching';
|
||||
|
||||
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
|
||||
|
||||
|
@ -1128,22 +1129,11 @@ module.exports = createReactClass({
|
|||
// todo: should cancel any previous search requests.
|
||||
this.searchId = new Date().getTime();
|
||||
|
||||
let filter;
|
||||
if (scope === "Room") {
|
||||
filter = {
|
||||
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
|
||||
rooms: [
|
||||
this.state.room.roomId,
|
||||
],
|
||||
};
|
||||
}
|
||||
let roomId;
|
||||
if (scope === "Room") roomId = this.state.room.roomId;
|
||||
|
||||
debuglog("sending search request");
|
||||
|
||||
const searchPromise = MatrixClientPeg.get().searchRoomEvents({
|
||||
filter: filter,
|
||||
term: term,
|
||||
});
|
||||
const searchPromise = eventSearch(term, roomId);
|
||||
this._handleSearchResult(searchPromise);
|
||||
},
|
||||
|
||||
|
|
|
@ -1075,6 +1075,7 @@ const TimelinePanel = createReactClass({
|
|||
if (timeline) {
|
||||
// This is a hot-path optimization by skipping a promise tick
|
||||
// by repeating a no-op sync branch in TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline
|
||||
this._timelineWindow.load(eventId, INITIAL_SIZE); // in this branch this method will happen in sync time
|
||||
onLoaded();
|
||||
} else {
|
||||
const prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
|
||||
|
|
|
@ -378,8 +378,19 @@ module.exports = createReactClass({
|
|||
|
||||
// Do a quick liveliness check on the URLs
|
||||
try {
|
||||
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
|
||||
this.setState({serverIsAlive: true, errorText: ""});
|
||||
const { warning } =
|
||||
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
|
||||
if (warning) {
|
||||
this.setState({
|
||||
...AutoDiscoveryUtils.authComponentStateForError(warning),
|
||||
errorText: "",
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
serverIsAlive: true,
|
||||
errorText: "",
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
busy: false,
|
||||
|
|
|
@ -19,79 +19,123 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import url from 'url';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
|
||||
export default class AppPermission extends React.Component {
|
||||
static propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
creatorUserId: PropTypes.string.isRequired,
|
||||
roomId: PropTypes.string.isRequired,
|
||||
onPermissionGranted: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onPermissionGranted: () => {},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const curlBase = this.getCurlBase();
|
||||
this.state = { curlBase: curlBase};
|
||||
// The first step is to pick apart the widget so we can render information about it
|
||||
const urlInfo = this.parseWidgetUrl();
|
||||
|
||||
// The second step is to find the user's profile so we can show it on the prompt
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
let roomMember;
|
||||
if (room) roomMember = room.getMember(this.props.creatorUserId);
|
||||
|
||||
// Set all this into the initial state
|
||||
this.state = {
|
||||
...urlInfo,
|
||||
roomMember,
|
||||
};
|
||||
}
|
||||
|
||||
// Return string representation of content URL without query parameters
|
||||
getCurlBase() {
|
||||
const wurl = url.parse(this.props.url);
|
||||
let curl;
|
||||
let curlString;
|
||||
parseWidgetUrl() {
|
||||
const widgetUrl = url.parse(this.props.url);
|
||||
const params = new URLSearchParams(widgetUrl.search);
|
||||
|
||||
const searchParams = new URLSearchParams(wurl.search);
|
||||
|
||||
if (WidgetUtils.isScalarUrl(wurl) && searchParams && searchParams.get('url')) {
|
||||
curl = url.parse(searchParams.get('url'));
|
||||
if (curl) {
|
||||
curl.search = curl.query = "";
|
||||
curlString = curl.format();
|
||||
}
|
||||
// HACK: We're relying on the query params when we should be relying on the widget's `data`.
|
||||
// This is a workaround for Scalar.
|
||||
if (WidgetUtils.isScalarUrl(widgetUrl) && params && params.get('url')) {
|
||||
const unwrappedUrl = url.parse(params.get('url'));
|
||||
return {
|
||||
widgetDomain: unwrappedUrl.host || unwrappedUrl.hostname,
|
||||
isWrapped: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
widgetDomain: widgetUrl.host || widgetUrl.hostname,
|
||||
isWrapped: false,
|
||||
};
|
||||
}
|
||||
if (!curl && wurl) {
|
||||
wurl.search = wurl.query = "";
|
||||
curlString = wurl.format();
|
||||
}
|
||||
return curlString;
|
||||
}
|
||||
|
||||
render() {
|
||||
let e2eWarningText;
|
||||
if (this.props.isRoomEncrypted) {
|
||||
e2eWarningText =
|
||||
<span className='mx_AppPermissionWarningTextLabel'>{ _t('NOTE: Apps are not end-to-end encrypted') }</span>;
|
||||
}
|
||||
const cookieWarning =
|
||||
<span className='mx_AppPermissionWarningTextLabel'>
|
||||
{ _t('Warning: This widget might use cookies.') }
|
||||
</span>;
|
||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
||||
const TextWithTooltip = sdk.getComponent("views.elements.TextWithTooltip");
|
||||
|
||||
const displayName = this.state.roomMember ? this.state.roomMember.name : this.props.creatorUserId;
|
||||
const userId = displayName === this.props.creatorUserId ? null : this.props.creatorUserId;
|
||||
|
||||
const avatar = this.state.roomMember
|
||||
? <MemberAvatar member={this.state.roomMember} width={38} height={38} />
|
||||
: <BaseAvatar name={this.props.creatorUserId} width={38} height={38} />;
|
||||
|
||||
const warningTooltipText = (
|
||||
<div>
|
||||
{_t("Any of the following data may be shared:")}
|
||||
<ul>
|
||||
<li>{_t("Your display name")}</li>
|
||||
<li>{_t("Your avatar URL")}</li>
|
||||
<li>{_t("Your user ID")}</li>
|
||||
<li>{_t("Your theme")}</li>
|
||||
<li>{_t("Riot URL")}</li>
|
||||
<li>{_t("Room ID")}</li>
|
||||
<li>{_t("Widget ID")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
const warningTooltip = (
|
||||
<TextWithTooltip tooltip={warningTooltipText} tooltipClass='mx_AppPermissionWarning_tooltip mx_Tooltip_dark'>
|
||||
<span className='mx_AppPermissionWarning_helpIcon' />
|
||||
</TextWithTooltip>
|
||||
);
|
||||
|
||||
// Due to i18n limitations, we can't dedupe the code for variables in these two messages.
|
||||
const warning = this.state.isWrapped
|
||||
? _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.",
|
||||
{widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip})
|
||||
: _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
||||
{widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip});
|
||||
|
||||
return (
|
||||
<div className='mx_AppPermissionWarning'>
|
||||
<div className='mx_AppPermissionWarningImage'>
|
||||
<img src={require("../../../../res/img/feather-customised/warning-triangle.svg")} alt={_t('Warning!')} />
|
||||
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_bolder mx_AppPermissionWarning_smallText'>
|
||||
{_t("Widget added by")}
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarningText'>
|
||||
<span className='mx_AppPermissionWarningTextLabel'>{_t('Do you want to load widget from URL:')}</span>
|
||||
<span className='mx_AppPermissionWarningTextURL'
|
||||
title={this.state.curlBase}
|
||||
>{this.state.curlBase}</span>
|
||||
{ e2eWarningText }
|
||||
{ cookieWarning }
|
||||
<div className='mx_AppPermissionWarning_row'>
|
||||
{avatar}
|
||||
<h4 className='mx_AppPermissionWarning_bolder'>{displayName}</h4>
|
||||
<div className='mx_AppPermissionWarning_smallText'>{userId}</div>
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_smallText'>
|
||||
{warning}
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_smallText'>
|
||||
{_t("This widget may use cookies.")}
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarning_row'>
|
||||
<AccessibleButton kind='primary_sm' onClick={this.props.onPermissionGranted}>
|
||||
{_t("Continue")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<input
|
||||
className='mx_AppPermissionButton'
|
||||
type='button'
|
||||
value={_t('Allow')}
|
||||
onClick={this.props.onPermissionGranted}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AppPermission.propTypes = {
|
||||
isRoomEncrypted: PropTypes.bool,
|
||||
url: PropTypes.string.isRequired,
|
||||
onPermissionGranted: PropTypes.func.isRequired,
|
||||
};
|
||||
AppPermission.defaultProps = {
|
||||
isRoomEncrypted: false,
|
||||
onPermissionGranted: function() {},
|
||||
};
|
||||
|
|
|
@ -569,11 +569,11 @@ export default class AppTile extends React.Component {
|
|||
</div>
|
||||
);
|
||||
if (!this.state.hasPermissionToLoad) {
|
||||
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||
appTileBody = (
|
||||
<div className={appTileBodyClass}>
|
||||
<AppPermission
|
||||
isRoomEncrypted={isRoomEncrypted}
|
||||
roomId={this.props.room.roomId}
|
||||
creatorUserId={this.props.creatorUserId}
|
||||
url={this.state.widgetUrl}
|
||||
onPermissionGranted={this._grantWidgetPermission}
|
||||
/>
|
||||
|
|
|
@ -67,7 +67,7 @@ module.exports = createReactClass({
|
|||
return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId();
|
||||
});
|
||||
const app = WidgetUtils.makeAppConfig(
|
||||
appEvent.getStateKey(), appEvent.getContent(), appEvent.sender, persistentWidgetInRoomId,
|
||||
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(), persistentWidgetInRoomId,
|
||||
);
|
||||
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, persistentWidgetInRoomId);
|
||||
const AppTile = sdk.getComponent('elements.AppTile');
|
||||
|
|
|
@ -21,7 +21,8 @@ import sdk from '../../../index';
|
|||
export default class TextWithTooltip extends React.Component {
|
||||
static propTypes = {
|
||||
class: PropTypes.string,
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
tooltipClass: PropTypes.string,
|
||||
tooltip: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
@ -49,6 +50,7 @@ export default class TextWithTooltip extends React.Component {
|
|||
<Tooltip
|
||||
label={this.props.tooltip}
|
||||
visible={this.state.hover}
|
||||
tooltipClassName={this.props.tooltipClass}
|
||||
className={"mx_TextWithTooltip_tooltip"} />
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -107,7 +107,7 @@ module.exports = createReactClass({
|
|||
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
|
||||
);
|
||||
return widgets.map((ev) => {
|
||||
return WidgetUtils.makeAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
|
||||
return WidgetUtils.makeAppConfig(ev.getStateKey(), ev.getContent(), ev.getSender());
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -173,8 +173,17 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
const newTheme = e.target.value;
|
||||
if (this.state.theme === newTheme) return;
|
||||
|
||||
SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme);
|
||||
// doing getValue in the .catch will still return the value we failed to set,
|
||||
// so remember what the value was before we tried to set it so we can revert
|
||||
const oldTheme = SettingsStore.getValue('theme');
|
||||
SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => {
|
||||
dis.dispatch({action: 'set_theme', value: oldTheme});
|
||||
this.setState({theme: oldTheme});
|
||||
});
|
||||
this.setState({theme: newTheme});
|
||||
// The settings watcher doesn't fire until the echo comes back from the
|
||||
// server, so to make the theme change immediately we need to manually
|
||||
// do the dispatch now
|
||||
dis.dispatch({action: 'set_theme', value: newTheme});
|
||||
};
|
||||
|
||||
|
|
|
@ -340,9 +340,10 @@
|
|||
"Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)",
|
||||
"Render simple counters in room header": "Render simple counters in room header",
|
||||
"Multiple integration managers": "Multiple integration managers",
|
||||
"Use the new, consistent UserInfo panel for Room Members and Group Members": "Use the new, consistent UserInfo panel for Room Members and Group Members",
|
||||
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
|
||||
"Send verification requests in direct message": "Send verification requests in direct message",
|
||||
"Send verification requests in direct message, including a new verification UX in the member panel.": "Send verification requests in direct message, including a new verification UX in the member panel.",
|
||||
"Enable cross-signing to verify per-user instead of per-device": "Enable cross-signing to verify per-user instead of per-device",
|
||||
"Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)",
|
||||
"Use the new, faster, composer for writing messages": "Use the new, faster, composer for writing messages",
|
||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||
"Use compact timeline layout": "Use compact timeline layout",
|
||||
|
@ -1183,10 +1184,18 @@
|
|||
"Quick Reactions": "Quick Reactions",
|
||||
"Cancel search": "Cancel search",
|
||||
"Unknown Address": "Unknown Address",
|
||||
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
|
||||
"Warning: This widget might use cookies.": "Warning: This widget might use cookies.",
|
||||
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
|
||||
"Allow": "Allow",
|
||||
"Any of the following data may be shared:": "Any of the following data may be shared:",
|
||||
"Your display name": "Your display name",
|
||||
"Your avatar URL": "Your avatar URL",
|
||||
"Your user ID": "Your user ID",
|
||||
"Your theme": "Your theme",
|
||||
"Riot URL": "Riot URL",
|
||||
"Room ID": "Room ID",
|
||||
"Widget ID": "Widget ID",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
||||
"Widget added by": "Widget added by",
|
||||
"This widget may use cookies.": "This widget may use cookies.",
|
||||
"Delete Widget": "Delete Widget",
|
||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?",
|
||||
"Delete widget": "Delete widget",
|
||||
|
@ -1494,6 +1503,7 @@
|
|||
"A widget would like to verify your identity": "A widget would like to verify your identity",
|
||||
"A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.",
|
||||
"Remember my selection for this widget": "Remember my selection for this widget",
|
||||
"Allow": "Allow",
|
||||
"Deny": "Deny",
|
||||
"Unable to load backup status": "Unable to load backup status",
|
||||
"Recovery Key Mismatch": "Recovery Key Mismatch",
|
||||
|
|
|
@ -1995,7 +1995,7 @@
|
|||
"Preview": "Antaŭrigardo",
|
||||
"View": "Rigardo",
|
||||
"Find a room…": "Trovi ĉambron…",
|
||||
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "Se vi ne povas travi la serĉatan ĉambron, petu inviton aŭ <a>kreu novan ĉambron</a>.",
|
||||
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "Se vi ne povas trovi la serĉatan ĉambron, petu inviton aŭ <a>kreu novan ĉambron</a>.",
|
||||
"Explore rooms": "Esplori ĉambrojn",
|
||||
"Add Email Address": "Aldoni retpoŝtadreson",
|
||||
"Add Phone Number": "Aldoni telefonnumeron",
|
||||
|
|
|
@ -1533,7 +1533,7 @@
|
|||
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Lisää ”¯\\_(ツ)_/¯” viestin alkuun",
|
||||
"User %(userId)s is already in the room": "Käyttäjä %(userId)s on jo huoneessa",
|
||||
"The user must be unbanned before they can be invited.": "Käyttäjän porttikielto täytyy poistaa ennen kutsumista.",
|
||||
"<a>Upgrade</a> to your own domain": "<a>Päivitä</a> omaan verkkotunnukseesi",
|
||||
"<a>Upgrade</a> to your own domain": "<a>Päivitä</a> omaan verkkotunnukseen",
|
||||
"Accept all %(invitedRooms)s invites": "Hyväksy kaikki %(invitedRooms)s kutsut",
|
||||
"Change room avatar": "Vaihda huoneen kuva",
|
||||
"Change room name": "Vaihda huoneen nimi",
|
||||
|
@ -2121,5 +2121,21 @@
|
|||
"Emoji Autocomplete": "Emojien automaattinen täydennys",
|
||||
"Notification Autocomplete": "Ilmoitusten automaattinen täydennys",
|
||||
"Room Autocomplete": "Huoneiden automaattinen täydennys",
|
||||
"User Autocomplete": "Käyttäjien automaattinen täydennys"
|
||||
"User Autocomplete": "Käyttäjien automaattinen täydennys",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Tämä toiminto vaatii oletusidentiteettipalvelimen <server /> käyttämistä sähköpostiosoitteen tai puhelinnumeron validointiin, mutta palvelimella ei ole käyttöehtoja.",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"My Ban List": "Tekemäni estot",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "Tämä on luettelo käyttäjistä ja palvelimista, jotka olet estänyt - älä poistu huoneesta!",
|
||||
"Something went wrong. Please try again or view your console for hints.": "Jotain meni vikaan. Yritä uudelleen tai katso vihjeitä konsolista.",
|
||||
"Please verify the room ID or alias and try again.": "Varmista huoneen tunnus tai alias ja yritä uudelleen.",
|
||||
"Please try again or view your console for hints.": "Yritä uudelleen tai katso vihjeitä konsolista.",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ Nämä asetukset on tarkoitettu edistyneille käyttäjille.",
|
||||
"eg: @bot:* or example.org": "esim. @bot:* tai esimerkki.org",
|
||||
"Show tray icon and minimize window to it on close": "Näytä ilmaisinalueen kuvake ja pienennä ikkuna siihen suljettaessa",
|
||||
"Your email address hasn't been verified yet": "Sähköpostiosoitettasi ei ole vielä varmistettu",
|
||||
"Verify the link in your inbox": "Varmista sähköpostiisi saapunut linkki",
|
||||
"%(count)s unread messages including mentions.|one": "Yksi lukematon maininta.",
|
||||
"%(count)s unread messages.|one": "Yksi lukematon viesti.",
|
||||
"Unread messages.": "Lukemattomat viestit.",
|
||||
"Message Actions": "Viestitoiminnot"
|
||||
}
|
||||
|
|
|
@ -2291,5 +2291,65 @@
|
|||
"You cancelled": "Vous avez annulé",
|
||||
"%(name)s cancelled": "%(name)s a annulé",
|
||||
"%(name)s wants to verify": "%(name)s veut vérifier",
|
||||
"You sent a verification request": "Vous avez envoyé une demande de vérification"
|
||||
"You sent a verification request": "Vous avez envoyé une demande de vérification",
|
||||
"Try out new ways to ignore people (experimental)": "Essayez de nouvelles façons d’ignorer les gens (expérimental)",
|
||||
"My Ban List": "Ma liste de bannissement",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "C’est la liste des utilisateurs/serveurs que vous avez bloqués − ne quittez pas le salon !",
|
||||
"Ignored/Blocked": "Ignoré/bloqué",
|
||||
"Error adding ignored user/server": "Erreur lors de l’ajout de l’utilisateur/du serveur ignoré",
|
||||
"Something went wrong. Please try again or view your console for hints.": "Une erreur est survenue. Réessayez ou consultez votre console pour des indices.",
|
||||
"Error subscribing to list": "Erreur lors de l’inscription à la liste",
|
||||
"Please verify the room ID or alias and try again.": "Vérifiez l’identifiant ou l’alias du salon et réessayez.",
|
||||
"Error removing ignored user/server": "Erreur lors de la suppression de l’utilisateur/du serveur ignoré",
|
||||
"Error unsubscribing from list": "Erreur lors de la désinscription de la liste",
|
||||
"Please try again or view your console for hints.": "Réessayez ou consultez votre console pour des indices.",
|
||||
"None": "Aucun",
|
||||
"Ban list rules - %(roomName)s": "Règles de la liste de bannissement − %(roomName)s",
|
||||
"Server rules": "Règles de serveur",
|
||||
"User rules": "Règles d’utilisateur",
|
||||
"You have not ignored anyone.": "Vous n’avez ignoré personne.",
|
||||
"You are currently ignoring:": "Vous ignorez actuellement :",
|
||||
"You are not subscribed to any lists": "Vous n’êtes inscrit(e) à aucune liste",
|
||||
"Unsubscribe": "Se désinscrire",
|
||||
"View rules": "Voir les règles",
|
||||
"You are currently subscribed to:": "Vous êtes actuellement inscrit(e) à :",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ Ces paramètres sont prévus pour les utilisateurs avancés.",
|
||||
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "Ajoutez les utilisateurs et les serveurs que vous voulez ignorer ici. Utilisez des astérisques pour remplacer n’importe quel caractère. Par exemple, <code>@bot:*</code> ignorerait tous les utilisateurs qui ont le nom « bot » sur n’importe quel serveur.",
|
||||
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignorer les gens est possible grâce à des listes de bannissement qui contiennent des règles sur les personnes à bannir. L’inscription à une liste de bannissement signifie que les utilisateurs/serveurs bloqués par cette liste seront cachés pour vous.",
|
||||
"Personal ban list": "Liste de bannissement personnelle",
|
||||
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Votre liste de bannissement personnelle contient tous les utilisateurs/serveurs dont vous ne voulez pas voir les messages personnellement. Quand vous aurez ignoré votre premier utilisateur/serveur, un nouveau salon nommé « Ma liste de bannissement » apparaîtra dans la liste de vos salons − restez dans ce salon pour que la liste de bannissement soit effective.",
|
||||
"Server or user ID to ignore": "Serveur ou identifiant d’utilisateur à ignorer",
|
||||
"eg: @bot:* or example.org": "par ex. : @bot:* ou exemple.org",
|
||||
"Subscribed lists": "Listes souscrites",
|
||||
"Subscribing to a ban list will cause you to join it!": "En vous inscrivant à une liste de bannissement, vous la rejoindrez !",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "Si ce n’est pas ce que vous voulez, utilisez un autre outil pour ignorer les utilisateurs.",
|
||||
"Room ID or alias of ban list": "Identifiant ou alias du salon de la liste de bannissement",
|
||||
"Subscribe": "S’inscrire",
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>": "Vous avez ignoré cet utilisateur, donc ses messages sont cachés. <a>Les montrer quand même.</a>",
|
||||
"Custom (%(level)s)": "Personnalisé (%(level)s)",
|
||||
"Trusted": "Fiable",
|
||||
"Not trusted": "Non vérifié",
|
||||
"Hide verified Sign-In's": "Masquer les connexions vérifiées",
|
||||
"%(count)s verified Sign-In's|other": "%(count)s connexions vérifiées",
|
||||
"%(count)s verified Sign-In's|one": "1 connexion vérifiée",
|
||||
"Direct message": "Message direct",
|
||||
"Unverify user": "Ne plus marquer l’utilisateur comme vérifié",
|
||||
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> dans %(roomName)s",
|
||||
"Messages in this room are end-to-end encrypted.": "Les messages dans ce salon sont chiffrés de bout en bout.",
|
||||
"Security": "Sécurité",
|
||||
"Verify": "Vérifier",
|
||||
"Enable cross-signing to verify per-user instead of per-device": "Activer la signature croisée pour vérifier par utilisateur et non par appareil",
|
||||
"Any of the following data may be shared:": "Les données suivants peuvent être partagées :",
|
||||
"Your display name": "Votre nom d’affichage",
|
||||
"Your avatar URL": "L’URL de votre avatar",
|
||||
"Your user ID": "Votre identifiant utilisateur",
|
||||
"Your theme": "Votre thème",
|
||||
"Riot URL": "URL de Riot",
|
||||
"Room ID": "Identifiant du salon",
|
||||
"Widget ID": "Identifiant du widget",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "L’utilisation de ce widget pourrait partager des données <helpIcon /> avec %(widgetDomain)s et votre gestionnaire d’intégrations.",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "L’utilisation de ce widget pourrait partager des données <helpIcon /> avec %(widgetDomain)s.",
|
||||
"Widget added by": "Widget ajouté par",
|
||||
"This widget may use cookies.": "Ce widget pourrait utiliser des cookies.",
|
||||
"Send verification requests in direct message, including a new verification UX in the member panel.": "Envoyer les demandes de vérification en message direct, en incluant une nouvelle expérience de vérification dans le tableau des membres."
|
||||
}
|
||||
|
|
|
@ -2278,5 +2278,65 @@
|
|||
"You cancelled": "Megszakítottad",
|
||||
"%(name)s cancelled": "%(name)s megszakította",
|
||||
"%(name)s wants to verify": "%(name)s ellenőrizni szeretné",
|
||||
"You sent a verification request": "Ellenőrzési kérést küldtél"
|
||||
"You sent a verification request": "Ellenőrzési kérést küldtél",
|
||||
"Try out new ways to ignore people (experimental)": "Emberek figyelmen kívül hagyásához próbálj ki új utakat (kísérleti)",
|
||||
"My Ban List": "Tiltólistám",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "Ez az általad tiltott felhasználók/szerverek listája - ne hagyd el ezt a szobát!",
|
||||
"Ignored/Blocked": "Figyelmen kívül hagyott/Tiltott",
|
||||
"Error adding ignored user/server": "Hiba a felhasználó/szerver hozzáadásánál a figyelmen kívül hagyandók listájához",
|
||||
"Something went wrong. Please try again or view your console for hints.": "Valami nem sikerült. Kérjük próbáld újra vagy nézd meg a konzolt a hiba okának felderítéséhez.",
|
||||
"Error subscribing to list": "A listára való feliratkozásnál hiba történt",
|
||||
"Please verify the room ID or alias and try again.": "Kérünk ellenőrizd a szoba azonosítóját vagy alternatív nevét és próbáld újra.",
|
||||
"Error removing ignored user/server": "Hiba a felhasználó/szerver törlésénél a figyelmen kívül hagyandók listájából",
|
||||
"Error unsubscribing from list": "A listáról való leiratkozásnál hiba történt",
|
||||
"Please try again or view your console for hints.": "Kérjük próbáld újra vagy nézd meg a konzolt a hiba okának felderítéséhez.",
|
||||
"None": "Semmi",
|
||||
"Ban list rules - %(roomName)s": "Tiltási lista szabályok - %(roomName)s",
|
||||
"Server rules": "Szerver szabályok",
|
||||
"User rules": "Felhasználói szabályok",
|
||||
"You have not ignored anyone.": "Senkit nem hagysz figyelmen kívül.",
|
||||
"You are currently ignoring:": "Jelenleg őket hagyod figyelmen kívül:",
|
||||
"You are not subscribed to any lists": "Nem iratkoztál fel egyetlen listára sem",
|
||||
"Unsubscribe": "Leiratkozás",
|
||||
"View rules": "Szabályok megtekintése",
|
||||
"You are currently subscribed to:": "Jelenleg ezekre iratkoztál fel:",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ Ezek a beállítások haladó felhasználók számára vannak.",
|
||||
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "Adj hozzá olyan felhasználókat és szervereket akiket figyelmen kívül kívánsz hagyni. Használj csillagot ahol bármilyen karakter állhat. Például: <code>@bot:*</code> minden „bot” nevű felhasználót figyelmen kívül fog hagyni akármelyik szerverről.",
|
||||
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Emberek figyelmen kívül hagyása tiltólistán keresztül történik ami arról tartalmaz szabályokat, hogy kiket kell kitiltani. A feliratkozás a tiltólistára azt jelenti, hogy azok a felhasználók/szerverek amik tiltva vannak a lista által, azoknak az üzenetei rejtve maradnak számodra.",
|
||||
"Personal ban list": "Személyes tiltó lista",
|
||||
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "A személyes tiltólistád tartalmazza azokat a személyeket/szervereket akiktől nem szeretnél üzeneteket látni. Az első felhasználó/szerver figyelmen kívül hagyása után egy új szoba jelenik meg a szobák listájában „Tiltólistám” névvel - ahhoz, hogy a lista érvényben maradjon maradj a szobában.",
|
||||
"Server or user ID to ignore": "Figyelmen kívül hagyandó szerver vagy felhasználói azonosító",
|
||||
"eg: @bot:* or example.org": "pl.: @bot:* vagy example.org",
|
||||
"Subscribed lists": "Feliratkozott listák",
|
||||
"Subscribing to a ban list will cause you to join it!": "A feliratkozás egy tiltó listára azzal jár, hogy csatlakozol hozzá!",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "Ha nem ez az amit szeretnél, kérlek használj más eszközt a felhasználók figyelmen kívül hagyásához.",
|
||||
"Room ID or alias of ban list": "Tiltó lista szoba azonosítója vagy alternatív neve",
|
||||
"Subscribe": "Feliratkozás",
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>": "Ezt a felhasználót figyelmen kívül hagyod, így az üzenetei el lesznek rejtve. <a>Mindenképpen megmutat.</a>",
|
||||
"Custom (%(level)s)": "Egyedi (%(level)s)",
|
||||
"Trusted": "Megbízható",
|
||||
"Not trusted": "Megbízhatatlan",
|
||||
"Hide verified Sign-In's": "Ellenőrzött belépések elrejtése",
|
||||
"%(count)s verified Sign-In's|other": "%(count)s ellenőrzött belépés",
|
||||
"%(count)s verified Sign-In's|one": "1 ellenőrzött belépés",
|
||||
"Direct message": "Közvetlen beszélgetés",
|
||||
"Unverify user": "Ellenőrizetlen felhasználó",
|
||||
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> a szobában: %(roomName)s",
|
||||
"Messages in this room are end-to-end encrypted.": "Az üzenetek a szobában végponttól végpontig titkosítottak.",
|
||||
"Security": "Biztonság",
|
||||
"Verify": "Ellenőriz",
|
||||
"Enable cross-signing to verify per-user instead of per-device": "Kereszt-aláírás engedélyezése eszköz alapú ellenőrzés helyett felhasználó alapú ellenőrzéshez",
|
||||
"Any of the following data may be shared:": "Az alábbi adatok közül bármelyik megosztásra kerülhet:",
|
||||
"Your display name": "Megjelenítési neved",
|
||||
"Your avatar URL": "Profilképed URL-je",
|
||||
"Your user ID": "Felhasználói azonosítód",
|
||||
"Your theme": "Témád",
|
||||
"Riot URL": "Riot URL",
|
||||
"Room ID": "Szoba azonosító",
|
||||
"Widget ID": "Kisalkalmazás azon.",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Ennek a kisalkalmazásnak a használata adatot oszthat meg <helpIcon /> %(widgetDomain)s domain-nel és az Integrációs Menedzserrel.",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Ennek a kisalkalmazásnak a használata adatot oszthat meg <helpIcon /> %(widgetDomain)s domain-nel.",
|
||||
"Widget added by": "A kisalkalmazást hozzáadta",
|
||||
"This widget may use cookies.": "Ez a kisalkalmazás sütiket használhat.",
|
||||
"Send verification requests in direct message, including a new verification UX in the member panel.": "Ellenőrzés küldése közvetlen üzenetben, beleértve az új ellenőrzési felhasználói élményt a résztvevői panelen."
|
||||
}
|
||||
|
|
|
@ -2224,5 +2224,76 @@
|
|||
"%(count)s unread messages including mentions.|one": "1 citazione non letta.",
|
||||
"%(count)s unread messages.|one": "1 messaggio non letto.",
|
||||
"Unread messages.": "Messaggi non letti.",
|
||||
"Show tray icon and minimize window to it on close": "Mostra icona in tray e usala alla chiusura della finestra"
|
||||
"Show tray icon and minimize window to it on close": "Mostra icona in tray e usala alla chiusura della finestra",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Questa azione richiede l'accesso al server di identità predefinito <server /> per verificare un indirizzo email o numero di telefono, ma il server non ha termini di servizio.",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"Try out new ways to ignore people (experimental)": "Prova nuovi metodi per ignorare persone (sperimentale)",
|
||||
"Send verification requests in direct message": "Invia richieste di verifica in un messaggio diretto",
|
||||
"My Ban List": "Mia lista ban",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "Questa è la lista degli utenti/server che hai bloccato - non lasciare la stanza!",
|
||||
"Error adding ignored user/server": "Errore di aggiunta utente/server ignorato",
|
||||
"Something went wrong. Please try again or view your console for hints.": "Qualcosa è andato storto. Riprova o controlla la console per suggerimenti.",
|
||||
"Error subscribing to list": "Errore di iscrizione alla lista",
|
||||
"Please verify the room ID or alias and try again.": "Verifica l'ID della stanza o l'alias e riprova.",
|
||||
"Error removing ignored user/server": "Errore di rimozione utente/server ignorato",
|
||||
"Error unsubscribing from list": "Errore di disiscrizione dalla lista",
|
||||
"Please try again or view your console for hints.": "Riprova o controlla la console per suggerimenti.",
|
||||
"None": "Nessuno",
|
||||
"Ban list rules - %(roomName)s": "Regole lista banditi - %(roomName)s",
|
||||
"Server rules": "Regole server",
|
||||
"User rules": "Regole utente",
|
||||
"You have not ignored anyone.": "Non hai ignorato nessuno.",
|
||||
"You are currently ignoring:": "Attualmente stai ignorando:",
|
||||
"You are not subscribed to any lists": "Non sei iscritto ad alcuna lista",
|
||||
"Unsubscribe": "Disiscriviti",
|
||||
"View rules": "Vedi regole",
|
||||
"You are currently subscribed to:": "Attualmente sei iscritto a:",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ Queste opzioni sono pensate per utenti esperti.",
|
||||
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "Aggiungi qui gli utenti e i server che vuoi ignorare. Usa l'asterisco perchè Riot consideri qualsiasi carattere. Ad esempio, <code>@bot:*</code> ignorerà tutti gli utenti che hanno il nome 'bot' su qualsiasi server.",
|
||||
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Si possono ignorare persone attraverso liste di ban contenenti regole per chi bandire. Iscriversi ad una lista di ban significa che gli utenti/server bloccati da quella lista ti verranno nascosti.",
|
||||
"Personal ban list": "Lista di ban personale",
|
||||
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "La tua lista personale di ban contiene tutti gli utenti/server da cui non vuoi vedere messaggi. Dopo aver ignorato il tuo primo utente/server, apparirà una nuova stanza nel tuo elenco stanze chiamata 'Mia lista ban' - resta in questa stanza per mantenere effettiva la lista ban.",
|
||||
"Server or user ID to ignore": "Server o ID utente da ignorare",
|
||||
"eg: @bot:* or example.org": "es: @bot:* o esempio.org",
|
||||
"Subscribed lists": "Liste sottoscritte",
|
||||
"Subscribing to a ban list will cause you to join it!": "Iscriversi ad una lista di ban implica di unirsi ad essa!",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "Se non è ciò che vuoi, usa uno strumento diverso per ignorare utenti.",
|
||||
"Room ID or alias of ban list": "ID stanza o alias della lista di ban",
|
||||
"Subscribe": "Iscriviti",
|
||||
"Message Actions": "Azioni messaggio",
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>": "Hai ignorato questo utente, perciò il suo messaggio è nascosto. <a>Mostra comunque.</a>",
|
||||
"You verified %(name)s": "Hai verificato %(name)s",
|
||||
"You cancelled verifying %(name)s": "Hai annullato la verifica di %(name)s",
|
||||
"%(name)s cancelled verifying": "%(name)s ha annullato la verifica",
|
||||
"You accepted": "Hai accettato",
|
||||
"%(name)s accepted": "%(name)s ha accettato",
|
||||
"You cancelled": "Hai annullato",
|
||||
"%(name)s cancelled": "%(name)s ha annullato",
|
||||
"%(name)s wants to verify": "%(name)s vuole verificare",
|
||||
"You sent a verification request": "Hai inviato una richiesta di verifica",
|
||||
"Custom (%(level)s)": "Personalizzato (%(level)s)",
|
||||
"Trusted": "Fidato",
|
||||
"Not trusted": "Non fidato",
|
||||
"Hide verified Sign-In's": "Nascondi accessi verificati",
|
||||
"%(count)s verified Sign-In's|other": "%(count)s accessi verificati",
|
||||
"%(count)s verified Sign-In's|one": "1 accesso verificato",
|
||||
"Direct message": "Messaggio diretto",
|
||||
"Unverify user": "Revoca verifica utente",
|
||||
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> in %(roomName)s",
|
||||
"Messages in this room are end-to-end encrypted.": "I messaggi in questa stanza sono cifrati end-to-end.",
|
||||
"Security": "Sicurezza",
|
||||
"Verify": "Verifica",
|
||||
"Enable cross-signing to verify per-user instead of per-device": "Attiva la firma incrociata per verificare per-utente invece che per-dispositivo",
|
||||
"Any of the following data may be shared:": "Possono essere condivisi tutti i seguenti dati:",
|
||||
"Your display name": "Il tuo nome visualizzato",
|
||||
"Your avatar URL": "L'URL del tuo avatar",
|
||||
"Your user ID": "Il tuo ID utente",
|
||||
"Your theme": "Il tuo tema",
|
||||
"Riot URL": "URL di Riot",
|
||||
"Room ID": "ID stanza",
|
||||
"Widget ID": "ID widget",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Usando questo widget i dati possono essere condivisi <helpIcon /> con %(widgetDomain)s e il tuo Gestore di Integrazione.",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Usando questo widget i dati possono essere condivisi <helpIcon /> con %(widgetDomain)s.",
|
||||
"Widget added by": "Widget aggiunto da",
|
||||
"This widget may use cookies.": "Questo widget può usare cookie."
|
||||
}
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
"Hide read receipts": "既読を表示しない",
|
||||
"Invited": "招待中",
|
||||
"%(displayName)s is typing": "%(displayName)s 文字入力中",
|
||||
"%(targetName)s joined the room.": "%(targetName)s 部屋に参加しました。",
|
||||
"%(targetName)s joined the room.": "%(targetName)s が部屋に参加しました。",
|
||||
"Low priority": "低優先度",
|
||||
"Mute": "通知しない",
|
||||
"New password": "新しいパスワード",
|
||||
"Notifications": "通知",
|
||||
"Cancel": "取消",
|
||||
"Cancel": "キャンセル",
|
||||
"Create new room": "新しい部屋を作成",
|
||||
"Room directory": "公開部屋一覧",
|
||||
"Search": "検索",
|
||||
|
@ -389,8 +389,8 @@
|
|||
"%(senderName)s kicked %(targetName)s.": "%(senderName)s は %(targetName)s を追放しました。",
|
||||
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s は %(targetName)s の招待を撤回しました。",
|
||||
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s はトピックを \"%(topic)s\" に変更しました。",
|
||||
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s はルーム名を削除しました。",
|
||||
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s はルーム名を %(roomName)s に変更しました。",
|
||||
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s は部屋名を削除しました。",
|
||||
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s は部屋名を %(roomName)s に変更しました。",
|
||||
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s はイメージを送信しました。",
|
||||
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s はこの部屋のアドレスとして %(addedAddresses)s を追加しました。",
|
||||
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s はこの部屋のアドレスとして %(addedAddresses)s を追加しました。",
|
||||
|
@ -490,7 +490,7 @@
|
|||
"Delete %(count)s devices|one": "端末を削除する",
|
||||
"Device ID": "端末ID",
|
||||
"Device Name": "端末名",
|
||||
"Last seen": "最後のシーン",
|
||||
"Last seen": "最後に表示した時刻",
|
||||
"Select devices": "端末の選択",
|
||||
"Failed to set display name": "表示名の設定に失敗しました",
|
||||
"Disable Notifications": "通知を無効にする",
|
||||
|
@ -1162,8 +1162,8 @@
|
|||
"No media permissions": "メディア権限がありません",
|
||||
"You may need to manually permit Riot to access your microphone/webcam": "自分のマイク/ウェブカメラにアクセスするために手動でRiotを許可する必要があるかもしれません",
|
||||
"Missing Media Permissions, click here to request.": "メディアアクセス権がありません。ここをクリックしてリクエストしてください。",
|
||||
"No Audio Outputs detected": "オーディオ出力が検出されなかった",
|
||||
"Default Device": "標準端末",
|
||||
"No Audio Outputs detected": "オーディオ出力が検出されませんでした",
|
||||
"Default Device": "既定のデバイス",
|
||||
"Audio Output": "音声出力",
|
||||
"VoIP": "VoIP",
|
||||
"Email": "Eメール",
|
||||
|
@ -1380,5 +1380,47 @@
|
|||
"Enable Community Filter Panel": "コミュニティーフィルターパネルを有効にする",
|
||||
"Show recently visited rooms above the room list": "最近訪問した部屋をリストの上位に表示する",
|
||||
"Low bandwidth mode": "低帯域通信モード",
|
||||
"Trust & Devices": "信頼と端末"
|
||||
"Trust & Devices": "信頼と端末",
|
||||
"Public Name": "パブリック名",
|
||||
"Upload profile picture": "プロフィール画像をアップロード",
|
||||
"<a>Upgrade</a> to your own domain": "あなた自身のドメインに<a>アップグレード</a>",
|
||||
"Phone numbers": "電話番号",
|
||||
"Set a new account password...": "アカウントの新しいパスワードを設定...",
|
||||
"Language and region": "言語と地域",
|
||||
"Theme": "テーマ",
|
||||
"General": "一般",
|
||||
"Preferences": "環境設定",
|
||||
"Security & Privacy": "セキュリティとプライバシー",
|
||||
"A device's public name is visible to people you communicate with": "デバイスのパブリック名はあなたと会話するすべての人が閲覧できます",
|
||||
"Room information": "部屋の情報",
|
||||
"Internal room ID:": "内部 部屋ID:",
|
||||
"Room version": "部屋のバージョン",
|
||||
"Room version:": "部屋のバージョン:",
|
||||
"Developer options": "開発者オプション",
|
||||
"Room Addresses": "部屋のアドレス",
|
||||
"Sounds": "音",
|
||||
"Notification sound": "通知音",
|
||||
"Reset": "リセット",
|
||||
"Set a new custom sound": "カスタム音を設定",
|
||||
"Browse": "参照",
|
||||
"Roles & Permissions": "役割と権限",
|
||||
"Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "誰が履歴を読み取れるかに関する変更は、今後送信されるメッセージにのみ適用されます。既に存在する履歴の表示は変更されません。",
|
||||
"Encryption": "暗号化",
|
||||
"Once enabled, encryption cannot be disabled.": "もし有効化された場合、二度と無効化できません。",
|
||||
"Encrypted": "暗号化",
|
||||
"Email Address": "メールアドレス",
|
||||
"Main address": "メインアドレス",
|
||||
"Join": "参加",
|
||||
"This room is private, and can only be joined by invitation.": "この部屋はプライベートです。招待によってのみ参加できます。",
|
||||
"Create a private room": "プライベートな部屋を作成",
|
||||
"Topic (optional)": "トピック (オプション)",
|
||||
"Hide advanced": "高度な設定を非表示",
|
||||
"Show advanced": "高度な設定を表示",
|
||||
"Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "他の Matrix ホームサーバーからの参加を禁止する (この設定はあとから変更できません!)",
|
||||
"Room Settings - %(roomName)s": "部屋の設定 - %(roomName)s",
|
||||
"Explore": "探索",
|
||||
"Filter": "フィルター",
|
||||
"Find a room… (e.g. %(exampleRoom)s)": "部屋を探す… (例: %(exampleRoom)s)",
|
||||
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "もしお探しの部屋が見つからない場合、招待してもらうか<a>部屋を作成</a>しましょう。",
|
||||
"Enable room encryption": "部屋の暗号化を有効にする"
|
||||
}
|
||||
|
|
|
@ -128,8 +128,8 @@
|
|||
"Deactivate Account": "계정 비활성화",
|
||||
"Deactivate my account": "계정 정지하기",
|
||||
"Decline": "거절",
|
||||
"Decrypt %(text)s": "%(text)s 해독",
|
||||
"Decryption error": "암호 해독 오류",
|
||||
"Decrypt %(text)s": "%(text)s 복호화",
|
||||
"Decryption error": "암호 복호화 오류",
|
||||
"Delete": "지우기",
|
||||
"Deops user with given id": "받은 ID로 사용자의 등급을 낮추기",
|
||||
"Device ID:": "기기 ID:",
|
||||
|
@ -156,7 +156,7 @@
|
|||
"End-to-end encryption is in beta and may not be reliable": "종단 간 암호화는 베타 테스트 중이며 신뢰하기 힘들 수 있습니다.",
|
||||
"Enter Code": "코드를 입력하세요",
|
||||
"Enter passphrase": "암호 입력",
|
||||
"Error decrypting attachment": "첨부 파일 해독 중 오류",
|
||||
"Error decrypting attachment": "첨부 파일 복호화 중 오류",
|
||||
"Error: Problem communicating with the given homeserver.": "오류: 지정한 홈서버와 통신에 문제가 있습니다.",
|
||||
"Event information": "이벤트 정보",
|
||||
"Existing Call": "기존 전화",
|
||||
|
@ -394,7 +394,7 @@
|
|||
"Unable to capture screen": "화면을 찍을 수 없음",
|
||||
"Unable to enable Notifications": "알림을 사용할 수 없음",
|
||||
"Unable to load device list": "기기 목록을 불러올 수 없음",
|
||||
"Undecryptable": "해독할 수 없음",
|
||||
"Undecryptable": "복호화할 수 없음",
|
||||
"Unencrypted room": "암호화하지 않은 방",
|
||||
"unencrypted": "암호화하지 않음",
|
||||
"Unencrypted message": "암호화하지 않은 메시지",
|
||||
|
@ -549,10 +549,10 @@
|
|||
"Unknown error": "알 수 없는 오류",
|
||||
"Incorrect password": "맞지 않는 비밀번호",
|
||||
"To continue, please enter your password.": "계속하려면, 비밀번호를 입력해주세요.",
|
||||
"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.": "내보낸 파일이 있으면 누구든 암호화한 메시지를 해독해서 읽을 수 있으므로, 보안에 신경을 써야 합니다. 이런 이유로 내보낸 파일을 암호화하도록 아래에 암호를 입력하는 것을 추천합니다. 같은 암호를 사용해야 데이터를 불러올 수 있을 것입니다.",
|
||||
"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 클라이언트에서 내보낸 암호화 키를 가져올 수 있습니다. 그런 다음 이전 클라이언트에서 해독할 수 있는 모든 메시지를 해독할 수 있습니다.",
|
||||
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "내보낸 파일이 암호로 보호되어 있습니다. 파일을 해독하려면, 여기에 암호를 입력해야 합니다.",
|
||||
"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.": "내보낸 파일이 있으면 누구든 암호화한 메시지를 복호화해서 읽을 수 있으므로, 보안에 신경을 써야 합니다. 이런 이유로 내보낸 파일을 암호화하도록 아래에 암호를 입력하는 것을 추천합니다. 같은 암호를 사용해야 데이터를 불러올 수 있을 것입니다.",
|
||||
"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 클라이언트에서 내보낸 암호화 키를 가져올 수 있습니다. 그런 다음 이전 클라이언트에서 복호화할 수 있는 모든 메시지를 복호화할 수 있습니다.",
|
||||
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "내보낸 파일이 암호로 보호되어 있습니다. 파일을 복호화하려면, 여기에 암호를 입력해야 합니다.",
|
||||
"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.": "이 이벤트를 감추길(삭제하길) 원하세요? 방 이름을 삭제하거나 주제를 바꾸면, 다시 생길 수도 있습니다.",
|
||||
"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:": "이 기기를 신뢰할 수 있는 지 인증하려면, 다른 방법(예를 들자면 직접 만나거나 전화를 걸어서)으로 소유자 분에게 연락해, 사용자 설정에 있는 키가 아래 키와 같은지 물어보세요:",
|
||||
"Device name": "기기 이름",
|
||||
|
@ -590,9 +590,9 @@
|
|||
"Custom server": "사용자 지정 서버",
|
||||
"Home server URL": "홈 서버 URL",
|
||||
"Identity server URL": "ID 서버 URL",
|
||||
"Error decrypting audio": "음성 해독 오류",
|
||||
"Error decrypting image": "사진 해독 중 오류",
|
||||
"Error decrypting video": "영상 해독 중 오류",
|
||||
"Error decrypting audio": "음성 복호화 오류",
|
||||
"Error decrypting image": "사진 복호화 중 오류",
|
||||
"Error decrypting video": "영상 복호화 중 오류",
|
||||
"Add an Integration": "통합 추가",
|
||||
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "%(integrationsUrl)s에서 쓸 수 있도록 계정을 인증하려고 다른 사이트로 이동하고 있습니다. 계속하겠습니까?",
|
||||
"Removed or unknown message type": "감췄거나 알 수 없는 메시지 유형",
|
||||
|
@ -670,7 +670,7 @@
|
|||
"Messages containing my display name": "내 표시 이름이 포함된 메시지",
|
||||
"Messages in one-to-one chats": "1:1 대화 메시지",
|
||||
"Unavailable": "이용할 수 없음",
|
||||
"View Decrypted Source": "해독된 소스 보기",
|
||||
"View Decrypted Source": "복호화된 소스 보기",
|
||||
"Send": "보내기",
|
||||
"remove %(name)s from the directory.": "목록에서 %(name)s 방을 제거했습니다.",
|
||||
"Notifications on the following keywords follow rules which can’t be displayed here:": "여기에 표시할 수 없는 규칙에 따르는 다음 키워드에 대한 알림:",
|
||||
|
@ -779,7 +779,7 @@
|
|||
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s님이 아바타를 %(count)s번 바꿨습니다",
|
||||
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s님이 아바타를 바꿨습니다",
|
||||
"This setting cannot be changed later!": "이 설정은 나중에 바꿀 수 없습니다!",
|
||||
"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.": "이전 버전 Riot의 데이터가 감지됬습니다. 이 때문에 이전 버전에서 종단간 암호화가 작동하지 않을 수 있습니다. 이전 버전을 사용하면서 최근에 교환한 종단간 암호화 메시지를 이 버전에서는 해독할 수 없습니다. 이 버전에서 메시지를 교환할 수 없을 수도 있습니다. 문제가 발생하면 로그아웃한 후 다시 로그인하세요. 메시지 기록을 유지하려면 키를 내보낸 후 다시 가져오세요.",
|
||||
"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.": "이전 버전 Riot의 데이터가 감지됬습니다. 이 때문에 이전 버전에서 종단간 암호화가 작동하지 않을 수 있습니다. 이전 버전을 사용하면서 최근에 교환한 종단간 암호화 메시지를 이 버전에서는 복호화할 수 없습니다. 이 버전에서 메시지를 교환할 수 없을 수도 있습니다. 문제가 발생하면 로그아웃한 후 다시 로그인하세요. 메시지 기록을 유지하려면 키를 내보낸 후 다시 가져오세요.",
|
||||
"Hide display name changes": "별명 변경 내역 숨기기",
|
||||
"This event could not be displayed": "이 이벤트를 표시할 수 없음",
|
||||
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "%(displayName)s(%(userName)s)님이 %(dateTime)s에 확인함",
|
||||
|
@ -902,7 +902,7 @@
|
|||
"%(senderName)s sent a video": "%(senderName)s님이 영상을 보냄",
|
||||
"%(senderName)s uploaded a file": "%(senderName)s님이 파일을 업로드함",
|
||||
"Key request sent.": "키 요청을 보냈습니다.",
|
||||
"If your other devices do not have the key for this message you will not be able to decrypt them.": "당신의 다른 기기에 이 메시지를 읽기 위한 키가 없다면 메시지를 해독할 수 없습니다.",
|
||||
"If your other devices do not have the key for this message you will not be able to decrypt them.": "당신의 다른 기기에 이 메시지를 읽기 위한 키가 없다면 메시지를 복호화할 수 없습니다.",
|
||||
"Encrypting": "암호화 중",
|
||||
"Encrypted, not sent": "암호화 됨, 보내지지 않음",
|
||||
"Disinvite this user?": "이 사용자에 대한 초대를 취소할까요?",
|
||||
|
@ -1811,13 +1811,13 @@
|
|||
"Deny": "거부",
|
||||
"Unable to load backup status": "백업 상태 불러올 수 없음",
|
||||
"Recovery Key Mismatch": "복구 키가 맞지 않음",
|
||||
"Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "이 키로 백업을 해독할 수 없음: 맞는 복구 키를 입력해서 인증해주세요.",
|
||||
"Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "이 키로 백업을 복호화할 수 없음: 맞는 복구 키를 입력해서 인증해주세요.",
|
||||
"Incorrect Recovery Passphrase": "맞지 않은 복구 암호",
|
||||
"Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "이 암호로 백업을 해독할 수 없음: 맞는 암호를 입력해서 입증해주세요.",
|
||||
"Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "이 암호로 백업을 복호화할 수 없음: 맞는 암호를 입력해서 입증해주세요.",
|
||||
"Unable to restore backup": "백업을 복구할 수 없음",
|
||||
"No backup found!": "백업을 찾을 수 없습니다!",
|
||||
"Backup Restored": "백업 복구됨",
|
||||
"Failed to decrypt %(failedCount)s sessions!": "%(failedCount)s개의 세션 해독에 실패했습니다!",
|
||||
"Failed to decrypt %(failedCount)s sessions!": "%(failedCount)s개의 세션 복호화에 실패했습니다!",
|
||||
"Restored %(sessionCount)s session keys": "%(sessionCount)s개의 세션 키 복구됨",
|
||||
"Enter Recovery Passphrase": "복구 암호 입력",
|
||||
"<b>Warning</b>: you should only set up key backup from a trusted computer.": "<b>경고</b>: 신뢰할 수 있는 컴퓨터에서만 키 백업을 설정해야 합니다.",
|
||||
|
@ -2124,5 +2124,76 @@
|
|||
"Show tray icon and minimize window to it on close": "닫을 때 창을 최소화하고 트레이 아이콘으로 표시하기",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "이 작업에는 이메일 주소 또는 전화번호를 확인하기 위해 기본 ID 서버 <server />에 접근해야 합니다. 하지만 서버가 서비스 약관을 갖고 있지 않습니다.",
|
||||
"Trust": "신뢰함",
|
||||
"Message Actions": "메시지 동작"
|
||||
"Message Actions": "메시지 동작",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"Send verification requests in direct message": "다이렉트 메시지에서 확인 요청 보내기",
|
||||
"You verified %(name)s": "%(name)s님을 확인했습니다",
|
||||
"You cancelled verifying %(name)s": "%(name)s님의 확인을 취소했습니다",
|
||||
"%(name)s cancelled verifying": "%(name)s님이 확인을 취소했습니다",
|
||||
"You accepted": "당신이 수락했습니다",
|
||||
"%(name)s accepted": "%(name)s님이 수락했습니다",
|
||||
"You cancelled": "당신이 취소했습니다",
|
||||
"%(name)s cancelled": "%(name)s님이 취소했습니다",
|
||||
"%(name)s wants to verify": "%(name)s님이 확인을 요청합니다",
|
||||
"You sent a verification request": "확인 요청을 보냈습니다",
|
||||
"Try out new ways to ignore people (experimental)": "새 방식으로 사람들을 무시하기 (실험)",
|
||||
"My Ban List": "차단 목록",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "차단한 사용자/서버 목록입니다 - 방을 떠나지 마세요!",
|
||||
"Ignored/Blocked": "무시됨/차단됨",
|
||||
"Error adding ignored user/server": "무시한 사용자/서버 추가 중 오류",
|
||||
"Something went wrong. Please try again or view your console for hints.": "무언가 잘못되었습니다. 다시 시도하거나 콘솔을 통해 원인을 알아봐주세요.",
|
||||
"Error subscribing to list": "목록으로 구독하는 중 오류",
|
||||
"Please verify the room ID or alias and try again.": "방 ID나 별칭을 확인한 후 다시 시도해주세요.",
|
||||
"Error removing ignored user/server": "무시한 사용자/서버를 지우는 중 오류",
|
||||
"Error unsubscribing from list": "목록에서 구독 해제 중 오류",
|
||||
"Please try again or view your console for hints.": "다시 시도하거나 콘솔을 통해 원인을 알아봐주세요.",
|
||||
"None": "없음",
|
||||
"Ban list rules - %(roomName)s": "차단 목록 규칙 - %(roomName)s",
|
||||
"Server rules": "서버 규칙",
|
||||
"User rules": "사용자 규칙",
|
||||
"You have not ignored anyone.": "아무도 무시하고 있지 않습니다.",
|
||||
"You are currently ignoring:": "현재 무시하고 있음:",
|
||||
"You are not subscribed to any lists": "어느 목록에도 구독하고 있지 않습니다",
|
||||
"Unsubscribe": "구독 해제",
|
||||
"View rules": "규칙 보기",
|
||||
"You are currently subscribed to:": "현재 구독 중임:",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ 이 설정은 고급 사용자를 위한 것입니다.",
|
||||
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "무시하고 싶은 사용자와 서버를 여기에 추가하세요. 별표(*)를 사용해서 Riot이 이름과 문자를 맞춰볼 수 있습니다. 예를 들어, <code>@bot:*</code>이라면 모든 서버에서 'bot'이라는 문자를 가진 이름의 모든 사용자를 무시합니다.",
|
||||
"Custom (%(level)s)": "맞춤 (%(level)s)",
|
||||
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "차단당하는 사람은 규칙에 따라 차단 목록을 통해 무시됩니다. 차단 목록을 구독하면 그 목록에서 차단당한 사용자/서버를 당신으로부터 감추게됩니다.",
|
||||
"Personal ban list": "개인 차단 목록",
|
||||
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "개인 차단 목록은 개인적으로 보고 싶지 않은 메시지를 보낸 모든 사용자/서버를 담고 있습니다. 처음 사용자/서버를 무시했다면, 방 목록에 '나의 차단 목록'이라는 이름의 새 방이 나타납니다 - 차단 목록의 효력을 유지하려면 이 방을 그대로 두세요.",
|
||||
"Server or user ID to ignore": "무시할 서버 또는 사용자 ID",
|
||||
"eg: @bot:* or example.org": "예: @bot:* 또는 example.org",
|
||||
"Subscribed lists": "구독 목록",
|
||||
"Subscribing to a ban list will cause you to join it!": "차단 목록을 구독하면 차단 목록에 참여하게 됩니다!",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "이것을 원한 것이 아니라면, 사용자를 무시하는 다른 도구를 사용해주세요.",
|
||||
"Room ID or alias of ban list": "방 ID 또는 차단 목록의 별칭",
|
||||
"Subscribe": "구독",
|
||||
"Trusted": "신뢰함",
|
||||
"Not trusted": "신뢰하지 않음",
|
||||
"Hide verified Sign-In's": "확인 로그인 숨기기",
|
||||
"%(count)s verified Sign-In's|other": "확인된 %(count)s개의 로그인",
|
||||
"%(count)s verified Sign-In's|one": "확인된 1개의 로그인",
|
||||
"Direct message": "다이렉트 메시지",
|
||||
"Unverify user": "사용자 확인 취소",
|
||||
"<strong>%(role)s</strong> in %(roomName)s": "%(roomName)s 방의 <strong>%(role)s</strong>",
|
||||
"Messages in this room are end-to-end encrypted.": "이 방의 메시지는 종단간 암호화되었습니다.",
|
||||
"Security": "보안",
|
||||
"Verify": "확인",
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>": "이 사용자를 무시했습니다. 사용자의 메시지는 숨겨집니다. <a>무시하고 보이기.</a>",
|
||||
"Send verification requests in direct message, including a new verification UX in the member panel.": "다이렉트 메시지에서 구성원 패널에 새 확인 UX가 적용된 확인 요청을 보냅니다.",
|
||||
"Enable cross-signing to verify per-user instead of per-device": "기기 당 확인이 아닌 사람 당 확인을 위한 교차 서명 켜기",
|
||||
"Any of the following data may be shared:": "다음 데이터가 공유됩니다:",
|
||||
"Your display name": "당신의 표시 이름",
|
||||
"Your avatar URL": "당신의 아바타 URL",
|
||||
"Your user ID": "당신의 사용자 ID",
|
||||
"Your theme": "당신의 테마",
|
||||
"Riot URL": "Riot URL",
|
||||
"Room ID": "방 ID",
|
||||
"Widget ID": "위젯 ID",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "이 위젯을 사용하면 <helpcon /> %(widgetDomain)s & 통합 관리자와 데이터를 공유합니다.",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "이 위젯을 사용하면 <helpIcon /> %(widgetDomain)s와(과) 데이터를 공유합니다.",
|
||||
"Widget added by": "위젯을 추가했습니다",
|
||||
"This widget may use cookies.": "이 위젯은 쿠키를 사용합니다."
|
||||
}
|
||||
|
|
|
@ -1117,5 +1117,6 @@
|
|||
"Custom user status messages": "Pasirinktinės vartotojo būsenos žinutės",
|
||||
"Group & filter rooms by custom tags (refresh to apply changes)": "Grupuoti ir filtruoti kambarius pagal pasirinktines žymas (atnaujinkite, kad pritaikytumėte pakeitimus)",
|
||||
"Render simple counters in room header": "Užkrauti paprastus skaitiklius kambario antraštėje",
|
||||
"Multiple integration managers": "Daugialypiai integracijų valdikliai"
|
||||
"Multiple integration managers": "Daugialypiai integracijų valdikliai",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)"
|
||||
}
|
||||
|
|
|
@ -320,7 +320,7 @@
|
|||
"Mobile phone number (optional)": "Numer telefonu komórkowego (opcjonalne)",
|
||||
"Moderator": "Moderator",
|
||||
"%(serverName)s Matrix ID": "%(serverName)s Matrix ID",
|
||||
"Name": "Imię",
|
||||
"Name": "Nazwa",
|
||||
"Never send encrypted messages to unverified devices from this device": "Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych urządzeń z tego urządzenia",
|
||||
"Never send encrypted messages to unverified devices in this room from this device": "Nigdy nie wysyłaj niezaszyfrowanych wiadomości do niezweryfikowanych urządzeń z tego urządzenia",
|
||||
"New address (e.g. #foo:%(localDomain)s)": "Nowy adres (np. #foo:%(localDomain)s)",
|
||||
|
@ -972,7 +972,7 @@
|
|||
"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",
|
||||
"Share Link to User": "Udostępnij odnośnik 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ą.",
|
||||
|
@ -1492,7 +1492,7 @@
|
|||
"You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Możesz się zarejestrować, lecz niektóre funkcje nie będą dostępne dopóki Serwer Tożsamości nie będzie znów online. Jeśli ciągle widzisz to ostrzeżenie, sprawdź swoją konfigurację lub skontaktuj się z administratorem serwera.",
|
||||
"You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Możesz zresetować hasło, lecz niektóre funkcje nie będą dostępne dopóki Serwer Tożsamości nie będzie znów online. Jeśli ciągle widzisz to ostrzeżenie, sprawdź swoją konfigurację lub skontaktuj się z administratorem serwera.",
|
||||
"You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Możesz się zalogować, lecz niektóre funkcje nie będą dostępne dopóki Serwer Tożsamości nie będzie znów online. Jeśli ciągle widzisz to ostrzeżenie, sprawdź swoją konfigurację lub skontaktuj się z administratorem serwera.",
|
||||
"No homeserver URL provided": "Nie podano URL serwera głównego.",
|
||||
"No homeserver URL provided": "Nie podano URL serwera głównego",
|
||||
"The server does not support the room version specified.": "Serwer nie wspiera tej wersji pokoju.",
|
||||
"Name or Matrix ID": "Imię lub identyfikator Matrix",
|
||||
"Email, name or Matrix ID": "E-mail, imię lub Matrix ID",
|
||||
|
@ -1528,7 +1528,7 @@
|
|||
"%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s dezaktywował Flair dla %(groups)s w tym pokoju.",
|
||||
"%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s aktywował Flair dla %(newGroups)s i dezaktywował Flair dla %(oldGroups)s w tym pokoju.",
|
||||
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s odwołał zaproszednie dla %(targetDisplayName)s aby dołączył do pokoju.",
|
||||
"%(names)s and %(count)s others are typing …|one": "%(names)s i jedna osoba pisze.",
|
||||
"%(names)s and %(count)s others are typing …|one": "%(names)s i jedna osoba pisze…",
|
||||
"Cannot reach homeserver": "Błąd połączenia z serwerem domowym",
|
||||
"Ensure you have a stable internet connection, or get in touch with the server admin": "Upewnij się, że posiadasz stabilne połączenie internetowe lub skontaktuj się z administratorem serwera",
|
||||
"Your Riot is misconfigured": "Twój Riot jest źle skonfigurowany",
|
||||
|
@ -1556,7 +1556,7 @@
|
|||
"Order rooms in the room list by most important first instead of most recent": "Kolejkuj pokoje na liście pokojów od najważniejszych niż od najnowszych",
|
||||
"Show hidden events in timeline": "Pokaż ukryte wydarzenia na linii czasowej",
|
||||
"Low bandwidth mode": "Tryb wolnej przepustowości",
|
||||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Powzól na awaryjny serwer wspomagania połączeń turn.matrix.org, gdy Twój serwer domowy takiego nie oferuje (Twój adres IP będzie udostępniony podczas połączenia)",
|
||||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Pozwól na awaryjny serwer wspomagania połączeń turn.matrix.org, gdy Twój serwer domowy takiego nie oferuje (Twój adres IP będzie udostępniony podczas połączenia)",
|
||||
"Messages containing my username": "Wiadomości zawierające moją nazwę użytkownika",
|
||||
"Encrypted messages in one-to-one chats": "Zaszyforwane wiadomości w rozmowach jeden-do-jednego",
|
||||
"Encrypted messages in group chats": "Zaszyfrowane wiadomości w rozmowach grupowych",
|
||||
|
@ -1619,7 +1619,7 @@
|
|||
"Disconnect Identity Server": "Odłącz Serwer Tożsamości",
|
||||
"Disconnect": "Odłącz",
|
||||
"Identity Server (%(server)s)": "Serwer tożsamości (%(server)s)",
|
||||
"You are currently using <server></server> to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Używasz <server></server> aby odkrywać i być odkrywanym przez isteniejące kontakty, które znasz. Możesz zmienić serwer tożsamości poniżej.",
|
||||
"You are currently using <server></server> to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Używasz <server></server>, aby odnajdywać i móc być odnajdywanym przez istniejące kontakty, które znasz. Możesz zmienić serwer tożsamości poniżej.",
|
||||
"Identity Server": "Serwer Tożsamości",
|
||||
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "Nie używasz serwera tożsamości. Aby odkrywać i być odkrywanym przez istniejące kontakty które znasz, dodaj jeden poniżej.",
|
||||
"Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Odłączenie się od serwera tożsamości oznacza, że nie będzie możliwości wykrycia przez innych użytkowników oraz nie będzie możliwości zaproszenia innych e-mailem lub za pomocą telefonu.",
|
||||
|
@ -1653,5 +1653,84 @@
|
|||
"You do not have the required permissions to use this command.": "Nie posiadasz wymaganych uprawnień do użycia tego polecenia.",
|
||||
"Changes the avatar of the current room": "Zmienia awatar dla obecnego pokoju",
|
||||
"Use an identity server": "Użyj serwera tożsamości",
|
||||
"Show previews/thumbnails for images": "Pokaż podgląd/miniatury obrazów"
|
||||
"Show previews/thumbnails for images": "Pokaż podgląd/miniatury obrazów",
|
||||
"Trust": "Zaufaj",
|
||||
"Custom (%(level)s)": "Własny (%(level)s)",
|
||||
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Użyj serwera tożsamości, by zaprosić z użyciem adresu e-mail. Kliknij dalej, żeby użyć domyślnego serwera tożsamości (%(defaultIdentityServerName)s), lub zmień w Ustawieniach.",
|
||||
"Use an identity server to invite by email. Manage in Settings.": "Użyj serwera tożsamości, by zaprosić za pomocą adresu e-mail. Zarządzaj w ustawieniach.",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"Use the new, consistent UserInfo panel for Room Members and Group Members": "Użyj nowego, spójnego panelu informacji o użytkowniku dla członków pokoju i grup",
|
||||
"Try out new ways to ignore people (experimental)": "Wypróbuj nowe sposoby na ignorowanie ludzi (eksperymentalne)",
|
||||
"Send verification requests in direct message": "Wysyłaj prośby o weryfikację w bezpośredniej wiadomości",
|
||||
"Use the new, faster, composer for writing messages": "Używaj nowego, szybszego kompozytora do pisania wiadomości",
|
||||
"My Ban List": "Moja lista zablokowanych",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "To jest Twoja lista zablokowanych użytkowników/serwerów – nie opuszczaj tego pokoju!",
|
||||
"Change identity server": "Zmień serwer tożsamości",
|
||||
"Disconnect from the identity server <current /> and connect to <new /> instead?": "Rozłączyć się z serwerem tożsamości <current /> i połączyć się w jego miejsce z <new />?",
|
||||
"Disconnect identity server": "Odłączanie serwera tożsamości",
|
||||
"You should:": "Należy:",
|
||||
"check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "sprawdzić rozszerzenia przeglądarki, które mogą blokować serwer tożsamości (takie jak Privacy Badger)",
|
||||
"contact the administrators of identity server <idserver />": "skontaktować się z administratorami serwera tożsamości <idserver />",
|
||||
"wait and try again later": "zaczekaj i spróbuj ponownie później",
|
||||
"Disconnect anyway": "Odłącz mimo to",
|
||||
"You are still <b>sharing your personal data</b> on the identity server <idserver />.": "W dalszym ciągu <b>udostępniasz swoje dane osobowe</b> na serwerze tożsamości <idserver />.",
|
||||
"We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "Zalecamy, by usunąć swój adres e-mail i numer telefonu z serwera tożsamości przed odłączeniem.",
|
||||
"If you don't want to use <server /> to discover and be discoverable by existing contacts you know, enter another identity server below.": "Jeżeli nie chcesz używać <server /> do odnajdywania i bycia odnajdywanym przez osoby, które znasz, wpisz inny serwer tożsamości poniżej.",
|
||||
"Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Używanie serwera tożsamości jest opcjonalne. Jeżeli postanowisz nie używać serwera tożsamości, pozostali użytkownicy nie będą w stanie Cię odnaleźć ani nie będziesz mógł zaprosić innych po adresie e-mail czy numerze telefonu.",
|
||||
"Do not use an identity server": "Nie używaj serwera tożsamości",
|
||||
"Clear cache and reload": "Wyczyść pamięć podręczną i przeładuj",
|
||||
"Something went wrong. Please try again or view your console for hints.": "Coś poszło nie tak. Spróbuj ponownie lub sprawdź konsolę przeglądarki dla wskazówek.",
|
||||
"Please verify the room ID or alias and try again.": "Zweryfikuj poprawność ID pokoju lub nazwy zastępczej i spróbuj ponownie.",
|
||||
"Please try again or view your console for hints.": "Spróbuj ponownie lub sprawdź konsolę przeglądarki dla wskazówek.",
|
||||
"Personal ban list": "Osobista lista zablokowanych",
|
||||
"Server or user ID to ignore": "ID serwera lub użytkownika do zignorowania",
|
||||
"eg: @bot:* or example.org": "np: @bot:* lub przykład.pl",
|
||||
"Composer": "Kompozytor",
|
||||
"Autocomplete delay (ms)": "Opóźnienie autouzupełniania (ms)",
|
||||
"Explore": "Przeglądaj",
|
||||
"Filter": "Filtruj",
|
||||
"Add room": "Dodaj pokój",
|
||||
"A device's public name is visible to people you communicate with": "Publiczna nazwa urządzenia jest widoczna dla ludzi, z którymi się komunikujesz",
|
||||
"Request media permissions": "Zapytaj o uprawnienia",
|
||||
"Voice & Video": "Głos & Wideo",
|
||||
"this room": "ten pokój",
|
||||
"View older messages in %(roomName)s.": "Wyświetl starsze wiadomości w %(roomName)s.",
|
||||
"Room information": "Informacje o pokoju",
|
||||
"Internal room ID:": "Wewnętrzne ID pokoju:",
|
||||
"Uploaded sound": "Przesłano dźwięk",
|
||||
"Change history visibility": "Zmień widoczność historii",
|
||||
"Upgrade the room": "Zaktualizuj pokój",
|
||||
"Enable room encryption": "Włącz szyfrowanie pokoju",
|
||||
"Select the roles required to change various parts of the room": "Wybierz role wymagane do zmieniania różnych części pokoju",
|
||||
"Enable encryption?": "Włączyć szyfrowanie?",
|
||||
"Your email address hasn't been verified yet": "Twój adres e-mail nie został jeszcze zweryfikowany",
|
||||
"Verification code": "Kod weryfikacyjny",
|
||||
"Remove %(email)s?": "Usunąć %(email)s?",
|
||||
"Remove %(phone)s?": "Usunąć %(phone)s?",
|
||||
"Some devices in this encrypted room are not trusted": "Niektóre urządzenia w tym zaszyfrowanym pokoju nie są zaufane",
|
||||
"Loading …": "Ładowanie…",
|
||||
"Loading room preview": "Wczytywanie podglądu pokoju",
|
||||
"Try to join anyway": "Spróbuj dołączyć mimo tego",
|
||||
"You can still join it because this is a public room.": "Możesz mimo to dołączyć, gdyż pokój jest publiczny.",
|
||||
"This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "To zaproszenie do %(roomName)s zostało wysłane na adres %(email)s, który nie jest przypisany do Twojego konta",
|
||||
"Link this email with your account in Settings to receive invites directly in Riot.": "Połącz ten adres e-mail z Twoim kontem w Ustawieniach, aby otrzymywać zaproszenia bezpośrednio w Riot.",
|
||||
"This invite to %(roomName)s was sent to %(email)s": "To zaproszenie do %(roomName)s zostało wysłane do %(email)s",
|
||||
"Use an identity server in Settings to receive invites directly in Riot.": "Użyj serwera tożsamości w Ustawieniach, aby otrzymywać zaproszenia bezpośrednio w Riot.",
|
||||
"Do you want to chat with %(user)s?": "Czy chcesz rozmawiać z %(user)s?",
|
||||
"Do you want to join %(roomName)s?": "Czy chcesz dołączyć do %(roomName)s?",
|
||||
"<userName/> invited you": "<userName/> zaprosił(a) CIę",
|
||||
"You're previewing %(roomName)s. Want to join it?": "Przeglądasz %(roomName)s. Czy chcesz dołączyć do pokoju?",
|
||||
"Not now": "Nie teraz",
|
||||
"Don't ask me again": "Nie pytaj ponownie",
|
||||
"%(count)s unread messages including mentions.|other": "%(count)s nieprzeczytanych wiadomości, wliczając wzmianki.",
|
||||
"%(count)s unread messages including mentions.|one": "1 nieprzeczytana wzmianka.",
|
||||
"%(count)s unread messages.|other": "%(count)s nieprzeczytanych wiadomości.",
|
||||
"%(count)s unread messages.|one": "1 nieprzeczytana wiadomość.",
|
||||
"Unread mentions.": "Nieprzeczytane wzmianki.",
|
||||
"Unread messages.": "Nieprzeczytane wiadomości.",
|
||||
"Join": "Dołącz",
|
||||
"%(creator)s created and configured the room.": "%(creator)s stworzył(a) i skonfigurował(a) pokój.",
|
||||
"Preview": "Przejrzyj",
|
||||
"View": "Wyświetl",
|
||||
"Missing media permissions, click the button below to request.": "Brakuje uprawnień do mediów, kliknij przycisk poniżej, aby o nie zapytać."
|
||||
}
|
||||
|
|
|
@ -842,5 +842,11 @@
|
|||
"Collapse panel": "Colapsar o painel",
|
||||
"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!": "Com o seu navegador atual, a aparência e sensação de uso da aplicação podem estar completamente incorretas, e algumas das funcionalidades poderão não funcionar. Se quiser tentar de qualquer maneira pode continuar, mas está por sua conta com algum problema que possa encontrar!",
|
||||
"Checking for an update...": "A procurar uma atualização...",
|
||||
"There are advanced notifications which are not shown here": "Existem notificações avançadas que não são exibidas aqui"
|
||||
"There are advanced notifications which are not shown here": "Existem notificações avançadas que não são exibidas aqui",
|
||||
"Add Email Address": "Adicione adresso de e-mail",
|
||||
"Add Phone Number": "Adicione número de telefone",
|
||||
"The platform you're on": "A plataforma em que se encontra",
|
||||
"The version of Riot.im": "A versão do RIOT.im",
|
||||
"Whether or not you're logged in (we don't record your username)": "Tenha ou não, iniciado sessão (não iremos guardar o seu username)",
|
||||
"Your language of choice": "O seu idioma de escolha"
|
||||
}
|
||||
|
|
|
@ -2018,7 +2018,7 @@
|
|||
"Create a private room": "Создать приватную комнату",
|
||||
"Topic (optional)": "Тема (опционально)",
|
||||
"Make this room public": "Сделать комнату публичной",
|
||||
"Use the new, faster, composer for writing messages": "Используйте новый, более быстрый, редактор для написания сообщений.",
|
||||
"Use the new, faster, composer for writing messages": "Используйте новый, более быстрый, редактор для написания сообщений",
|
||||
"Send read receipts for messages (requires compatible homeserver to disable)": "Отправлять подтверждения о прочтении сообщений (требуется отключение совместимого домашнего сервера)",
|
||||
"Show previews/thumbnails for images": "Показать превью / миниатюры для изображений",
|
||||
"Disconnect from the identity server <current /> and connect to <new /> instead?": "Отключиться от сервера идентификации <current /> и вместо этого подключиться к <new />?",
|
||||
|
@ -2050,7 +2050,7 @@
|
|||
"contact the administrators of identity server <idserver />": "связаться с администраторами сервера идентификации <idserver />",
|
||||
"wait and try again later": "Подождите и повторите попытку позже",
|
||||
"Error changing power level requirement": "Ошибка изменения требования к уровню прав",
|
||||
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "Произошла ошибка при изменении требований к уровню прав комнаты. Убедитесь, что у вас достаточно прав и попробуйте снова.",
|
||||
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "Произошла ошибка при изменении требований к уровню доступа комнаты. Убедитесь, что у вас достаточно прав и попробуйте снова.",
|
||||
"Error changing power level": "Ошибка изменения уровня прав",
|
||||
"An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "Произошла ошибка при изменении уровня прав пользователя. Убедитесь, что у вас достаточно прав и попробуйте снова.",
|
||||
"Unable to revoke sharing for email address": "Не удается отменить общий доступ к адресу электронной почты",
|
||||
|
@ -2165,5 +2165,14 @@
|
|||
"%(count)s unread messages including mentions.|one": "1 непрочитанное упоминание.",
|
||||
"%(count)s unread messages.|one": "1 непрочитанное сообщение.",
|
||||
"Unread messages.": "Непрочитанные сообщения.",
|
||||
"Message Actions": "Сообщение действий"
|
||||
"Message Actions": "Сообщение действий",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Это действие требует по умолчанию доступа к серверу идентификации <server/> для подтверждения адреса электронной почты или номера телефона, но у сервера нет никакого пользовательского соглашения.",
|
||||
"Custom (%(level)s)": "Пользовательский (%(level)s)",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"Try out new ways to ignore people (experimental)": "Попробуйте новые способы игнорировать людей (экспериментальные)",
|
||||
"Send verification requests in direct message": "Отправить запросы на подтверждение в прямом сообщении",
|
||||
"My Ban List": "Мой список запрещенных",
|
||||
"Ignored/Blocked": "Игнорируемые/Заблокированные",
|
||||
"Error adding ignored user/server": "Ошибка добавления игнорируемого пользователя/сервера",
|
||||
"Error subscribing to list": "Ошибка при подписке на список"
|
||||
}
|
||||
|
|
|
@ -2063,7 +2063,7 @@
|
|||
"Discovery options will appear once you have added a phone number above.": "Mundësitë e zbulimit do të shfaqen sapo të keni shtuar më sipër një numër telefoni.",
|
||||
"Call failed due to misconfigured server": "Thirrja dështoi për shkak shërbyesi të keqformësuar",
|
||||
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "Që thirrjet të funksionojnë pa probleme, ju lutemi, kërkojini përgjegjësit të shërbyesit tuaj Home (<code>%(homeserverDomain)s</code>) të formësojë një shërbyes TURN.",
|
||||
"Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Ndryshe, mund të provoni të përdorni shërbyesin publik te <code>turn.matrix.org</code>, por kjo s’do të jetë edhe aq e qëndrueshme, dhe adresa juaj IP do t’i bëhet e njohur atij shërbyesi.Këtë mund ta bëni edhe që nga Rregullimet.",
|
||||
"Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Ndryshe, mund të provoni të përdorni shërbyesin publik te <code>turn.matrix.org</code>, por kjo s’do të jetë edhe aq e qëndrueshme, dhe adresa juaj IP do t’i bëhet e njohur atij shërbyesi. Këtë mund ta bëni edhe që nga Rregullimet.",
|
||||
"Try using turn.matrix.org": "Provo të përdorësh turn.matrix.org",
|
||||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Lejoni shërbyes rrugëzgjidhje asistimi thirrjesh turn.matrix.org kur shërbyesi juaj Home nuk ofron një të tillë (gjatë thirrjes, adresa juaj IP do t’i bëhet e ditur)",
|
||||
"ID": "ID",
|
||||
|
@ -2246,5 +2246,63 @@
|
|||
"Cancel search": "Anulo kërkimin",
|
||||
"No identity server is configured so you cannot add an email address in order to reset your password in the future.": "S’ka shërbyes identitetesh të formësuar, ndaj s’mund të shtoni një adresë email që të mund të ricaktoni fjalëkalimin tuaj në të ardhmen.",
|
||||
"Jump to first unread room.": "Hidhu te dhoma e parë e palexuar.",
|
||||
"Jump to first invite.": "Hidhu te ftesa e parë."
|
||||
"Jump to first invite.": "Hidhu te ftesa e parë.",
|
||||
"Try out new ways to ignore people (experimental)": "Provoni rrugë të reja për shpërfillje personash (eksperimentale)",
|
||||
"My Ban List": "Lista Ime e Dëbimeve",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "Kjo është lista juaj e përdoruesve/shërbyesve që keni bllokuar - mos dilni nga dhoma!",
|
||||
"Ignored/Blocked": "Të shpërfillur/Të bllokuar",
|
||||
"Error adding ignored user/server": "Gabim shtimi përdoruesi/shërbyesi të shpërfillur",
|
||||
"Something went wrong. Please try again or view your console for hints.": "Diç shkoi ters. Ju lutemi, riprovoni ose, për ndonjë ide, shihni konsolën tuaj.",
|
||||
"Error subscribing to list": "Gabim pajtimi te lista",
|
||||
"Please verify the room ID or alias and try again.": "Ju lutemi, verifikoni ID-në ose aliasin e dhomës dhe riprovoni.",
|
||||
"Error removing ignored user/server": "Gabim në heqje përdoruesi/shërbyes të shpërfillur",
|
||||
"Error unsubscribing from list": "Gabim shpajtimi nga lista",
|
||||
"Please try again or view your console for hints.": "Ju lutemi, riprovoni, ose shihni konsolën tuaj, për ndonjë ide.",
|
||||
"None": "Asnjë",
|
||||
"Ban list rules - %(roomName)s": "Rregulla liste dëbimesh - %(roomName)s",
|
||||
"Server rules": "Rregulla shërbyesi",
|
||||
"User rules": "Rregulla përdoruesi",
|
||||
"You have not ignored anyone.": "S’keni shpërfillur ndonjë.",
|
||||
"You are currently ignoring:": "Aktualisht shpërfillni:",
|
||||
"You are not subscribed to any lists": "S’jeni pajtuar te ndonjë listë",
|
||||
"Unsubscribe": "Shpajtohuni",
|
||||
"View rules": "Shihni rregulla",
|
||||
"You are currently subscribed to:": "Jeni i pajtuar te:",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ Këto rregullime janë menduar për përdorues të përparuar.",
|
||||
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "Shtoni këtu përdorues dhe shërbyes që doni të shpërfillen. Që Riot të kërkojë për përputhje me çfarëdo shkronjash, përdorni yllthin. Për shembull, <code>@bot:*</code> do të shpërfillë krej përdoruesit që kanë emrin 'bot' në çfarëdo shërbyesi.",
|
||||
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Shpërfillja e personave kryhet përmes listash dëbimi, të cilat përmbajnë rregulla se cilët të dëbohen. Pajtimi te një listë dëbimesh do të thotë se përdoruesit/shërbyesit e bllokuar nga ajo listë do t’ju fshihen juve.",
|
||||
"Personal ban list": "Listë personale dëbimesh",
|
||||
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Lista juaj personale e dëbimeve mban krejt përdoruesit/shërbyesit prej të cilëve ju personalisht s’dëshironi të shihni mesazhe. Pas shpërfilljes së përdoruesit/shërbyesit tuaj të parë, te lista juaj e dhomave do të shfaqet një dhomë e re e quajtur 'Lista Ime e Dëbimeve' - qëndroni në këtë dhomë që ta mbani listën e dëbimeve në fuqi.",
|
||||
"Server or user ID to ignore": "Shërbyes ose ID përdoruesi për t’u shpërfillur",
|
||||
"eg: @bot:* or example.org": "p.sh.: @bot:* ose example.org",
|
||||
"Subscribed lists": "Lista me pajtim",
|
||||
"Subscribing to a ban list will cause you to join it!": "Pajtimi te një listë dëbimesh do të shkaktojë pjesëmarrjen tuaj në të!",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "Nëse kjo s’është ajo çka doni, ju lutemi, përdorni një tjetër mjet për të shpërfillur përdorues.",
|
||||
"Room ID or alias of ban list": "ID dhome ose alias e listës së dëbimeve",
|
||||
"Subscribe": "Pajtohuni",
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>": "E keni shpërfillur këtë përdorues, ndaj mesazhi i tij është fshehur. <a>Shfaqe, sido qoftë.</a>",
|
||||
"Custom (%(level)s)": "Vetjak (%(level)s)",
|
||||
"Trusted": "E besuar",
|
||||
"Not trusted": "Jo e besuar",
|
||||
"Hide verified Sign-In's": "Fshihi Hyrjet e verifikuara",
|
||||
"%(count)s verified Sign-In's|other": "%(count)s Hyrje të verifikuara",
|
||||
"%(count)s verified Sign-In's|one": "1 Hyrje e verifikuar",
|
||||
"Direct message": "Mesazh i Drejtpërdrejtë",
|
||||
"Unverify user": "Hiqi verifikimin përdoruesit",
|
||||
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> në %(roomName)s",
|
||||
"Messages in this room are end-to-end encrypted.": "Mesazhet në këtë dhomë janë të fshehtëzuara skaj-më-skaj.",
|
||||
"Security": "Siguri",
|
||||
"Verify": "Verifikoje",
|
||||
"Any of the following data may be shared:": "Mund të ndahen me të tjerët cilado prej të dhënave vijuese:",
|
||||
"Your display name": "Emri juaj në ekran",
|
||||
"Your avatar URL": "URL-ja e avatarit tuaj",
|
||||
"Your user ID": "ID-ja juaj e përdoruesit",
|
||||
"Your theme": "Tema juaj",
|
||||
"Riot URL": "URL Riot-i",
|
||||
"Room ID": "ID dhome",
|
||||
"Widget ID": "ID widget-i",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Përdorimi i këtij widget-i mund të sjellë ndarje të dhënash <helpIcon /> me %(widgetDomain)s & Përgjegjësin tuaj të Integrimeve.",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Përdorimi i këtij widget-i mund të sjellë ndarje të dhënash <helpIcon /> me %(widgetDomain)s.",
|
||||
"Widget added by": "Widget i shtuar nga",
|
||||
"This widget may use cookies.": "Ky <em>widget</em> mund të përdorë <em>cookies</em>."
|
||||
}
|
||||
|
|
|
@ -757,7 +757,7 @@
|
|||
"Device Name": "Enhetsnamn",
|
||||
"Select devices": "Välj enheter",
|
||||
"Disable Emoji suggestions while typing": "Inaktivera Emoji-förslag medan du skriver",
|
||||
"Use compact timeline layout": "Använd kompakt chattlayout",
|
||||
"Use compact timeline layout": "Använd kompakt tidslinjelayout",
|
||||
"Not a valid Riot keyfile": "Inte en giltig Riot-nyckelfil",
|
||||
"Authentication check failed: incorrect password?": "Autentiseringskontroll misslyckades: felaktigt lösenord?",
|
||||
"Always show encryption icons": "Visa alltid krypteringsikoner",
|
||||
|
@ -1019,7 +1019,7 @@
|
|||
"Add rooms to the community": "Lägg till rum i communityn",
|
||||
"Add to community": "Lägg till i community",
|
||||
"Failed to invite users to community": "Det gick inte att bjuda in användare till communityn",
|
||||
"Mirror local video feed": "Spegelvänd lokal video",
|
||||
"Mirror local video feed": "Speglad lokal-video",
|
||||
"Disable Community Filter Panel": "Inaktivera community-filterpanel",
|
||||
"Community Invites": "Community-inbjudningar",
|
||||
"Invalid community ID": "Ogiltigt community-ID",
|
||||
|
@ -1353,9 +1353,9 @@
|
|||
"Show read receipts sent by other users": "Visa läskvitton som skickats av andra användare",
|
||||
"Show avatars in user and room mentions": "Visa avatarer i användar- och rumsnämningar",
|
||||
"Enable big emoji in chat": "Aktivera stor emoji i chatt",
|
||||
"Send typing notifications": "Skicka \"skriver\"-status",
|
||||
"Send typing notifications": "Skicka \"skriver\"-statusar",
|
||||
"Enable Community Filter Panel": "Aktivera community-filterpanel",
|
||||
"Allow Peer-to-Peer for 1:1 calls": "Tillåt enhet-till-enhet-kommunikation för direktsamtal (mellan två personer)",
|
||||
"Allow Peer-to-Peer for 1:1 calls": "Tillåt peer-to-peer kommunikation för 1:1 samtal",
|
||||
"Messages containing my username": "Meddelanden som innehåller mitt användarnamn",
|
||||
"Messages containing @room": "Meddelanden som innehåller @room",
|
||||
"Encrypted messages in one-to-one chats": "Krypterade meddelanden i privata chattar",
|
||||
|
@ -1564,7 +1564,7 @@
|
|||
"Please supply a https:// or http:// widget URL": "Ange en widget-URL med https:// eller http://",
|
||||
"You cannot modify widgets in this room.": "Du kan inte ändra widgets i detta rum.",
|
||||
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s återkallade inbjudan för %(targetDisplayName)s att gå med i rummet.",
|
||||
"Show a reminder to enable Secure Message Recovery in encrypted rooms": "",
|
||||
"Show a reminder to enable Secure Message Recovery in encrypted rooms": "Visa en påminnelse för att sätta på säker meddelande återhämtning i krypterade rum",
|
||||
"The other party cancelled the verification.": "Den andra parten avbröt verifieringen.",
|
||||
"Verified!": "Verifierad!",
|
||||
"You've successfully verified this user.": "Du har verifierat den här användaren.",
|
||||
|
@ -1817,5 +1817,11 @@
|
|||
"Add Phone Number": "Lägg till telefonnummer",
|
||||
"Identity server has no terms of service": "Identitetsserver har inga användarvillkor",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Den här åtgärden kräver åtkomst till standardidentitetsservern <server /> för att validera en e-postadress eller telefonnummer, men servern har inga användarvillkor.",
|
||||
"Trust": "Förtroende"
|
||||
"Trust": "Förtroende",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"Use the new, consistent UserInfo panel for Room Members and Group Members": "Använd den nya, konsistenta UserInfo panelen för rum medlemmar och grupp medlemmar",
|
||||
"Try out new ways to ignore people (experimental)": "Testa nya sätt att ignorera personer (experimentalt)",
|
||||
"Send verification requests in direct message": "Skicka verifikations begäran i direkt meddelanden",
|
||||
"Use the new, faster, composer for writing messages": "Använd den nya, snabbare kompositören för att skriva meddelanden",
|
||||
"Show previews/thumbnails for images": "Visa förhandsvisning/tumnagel för bilder"
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Текстове повідомлення було надіслано +%(msisdn)s. Введіть, будь ласка, код підтвердження з цього повідомлення",
|
||||
"Accept": "Прийняти",
|
||||
"Account": "Обліковка",
|
||||
"%(targetName)s accepted an invitation.": "%(targetName)s прийняв/ла запрошення.",
|
||||
"%(targetName)s accepted the invitation for %(displayName)s.": "Запрошення від %(displayName)s прийнято %(targetName)s.",
|
||||
"%(targetName)s accepted an invitation.": "%(targetName)s приймає запрошення.",
|
||||
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s приймає запрошення від %(displayName)s.",
|
||||
"Access Token:": "Токен доступу:",
|
||||
"Active call (%(roomName)s)": "Активний виклик (%(roomName)s)",
|
||||
"Add": "Додати",
|
||||
|
@ -71,7 +71,7 @@
|
|||
"%(senderName)s banned %(targetName)s.": "%(senderName)s заблокував/ла %(targetName)s.",
|
||||
"Ban": "Заблокувати",
|
||||
"Banned users": "Заблоковані користувачі",
|
||||
"Bans user with given id": "Блокує користувача з вказаним ID",
|
||||
"Bans user with given id": "Блокує користувача з вказаним ідентифікатором",
|
||||
"Blacklisted": "В чорному списку",
|
||||
"Bulk Options": "Групові параметри",
|
||||
"Call Timeout": "Час очікування виклика",
|
||||
|
@ -389,15 +389,15 @@
|
|||
"Changes colour scheme of current room": "Змінює кольорову схему кімнати",
|
||||
"Sets the room topic": "Встановлює тему кімнати",
|
||||
"Invites user with given id to current room": "Запрошує користувача з вказаним ідентифікатором до кімнати",
|
||||
"Joins room with given alias": "Приєднується до кімнати з поданим ідентифікатором",
|
||||
"Joins room with given alias": "Приєднується до кімнати під іншим псевдонімом",
|
||||
"Leave room": "Покинути кімнату",
|
||||
"Unrecognised room alias:": "Кімнату не знайдено:",
|
||||
"Kicks user with given id": "Викинути з кімнати користувача з вказаним ідентифікатором",
|
||||
"Unrecognised room alias:": "Не розпізнано псевдонім кімнати:",
|
||||
"Kicks user with given id": "Вилучити з кімнати користувача з вказаним ідентифікатором",
|
||||
"Unbans user with given id": "Розблоковує користувача з вказаним ідентифікатором",
|
||||
"Ignores a user, hiding their messages from you": "Ігнорувати користувача (приховує повідомлення від них)",
|
||||
"Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи повідомлення від них",
|
||||
"Ignored user": "Користувача ігноровано",
|
||||
"You are now ignoring %(userId)s": "Ви ігноруєте %(userId)s",
|
||||
"Stops ignoring a user, showing their messages going forward": "Припинити ігнорувати користувача (показує їхні повідомлення від цього моменту)",
|
||||
"Stops ignoring a user, showing their messages going forward": "Припиняє ігнорувати користувача, від цього моменту показуючи їхні повідомлення",
|
||||
"Unignored user": "Припинено ігнорування користувача",
|
||||
"You are no longer ignoring %(userId)s": "Ви більше не ігноруєте %(userId)s",
|
||||
"Define the power level of a user": "Вказати рівень прав користувача",
|
||||
|
@ -407,9 +407,9 @@
|
|||
"Unknown (user, device) pair:": "Невідома комбінація користувача і пристрою:",
|
||||
"Device already verified!": "Пристрій вже перевірено!",
|
||||
"WARNING: Device already verified, but keys do NOT MATCH!": "УВАГА: Пристрій уже перевірено, але ключі НЕ ЗБІГАЮТЬСЯ!",
|
||||
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "УВАГА: КЛЮЧ НЕ ПРОЙШОВ ПЕРЕВІРКУ! Підписний ключ %(userId)s на пристрої %(deviceId)s — це «%(fprint)s», і він не збігається з наданим ключем «%(fingerprint)s». Це може означати, що ваші повідомлення перехоплюють!",
|
||||
"Verified key": "Ключ перевірено",
|
||||
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Підписний ключ, який ви вказали, збігається з підписним ключем, отриманим від пристрою %(deviceId)s користувача %(userId)s. Пристрій позначено як перевірений.",
|
||||
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "УВАГА: КЛЮЧ НЕ ПРОЙШОВ ПЕРЕВІРКУ! Ключ підпису %(userId)s на пристрої %(deviceId)s — це «%(fprint)s», і він не збігається з наданим ключем «%(fingerprint)s». Це може означати, що ваші повідомлення перехоплюють!",
|
||||
"Verified key": "Перевірений ключ",
|
||||
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Ключ підпису, який ви вказали, збігається з ключем підпису, отриманим від пристрою %(deviceId)s користувача %(userId)s. Пристрій позначено як перевірений.",
|
||||
"Displays action": "Показує дію",
|
||||
"Unrecognised command:": "Невідома команда:",
|
||||
"Reason": "Причина",
|
||||
|
@ -579,5 +579,54 @@
|
|||
"A conference call could not be started because the integrations server is not available": "Конференц-дзвінок не можна розпочати оскільки інтеграційний сервер недоступний",
|
||||
"The file '%(fileName)s' failed to upload.": "Файл '%(fileName)s' не вийшло відвантажити.",
|
||||
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Файл '%(fileName)s' перевищує ліміт розміру для відвантажень домашнього сервера",
|
||||
"The server does not support the room version specified.": "Сервер не підтримує вказану версію кімнати."
|
||||
"The server does not support the room version specified.": "Сервер не підтримує вказану версію кімнати.",
|
||||
"Add Email Address": "Додати адресу е-пошти",
|
||||
"Add Phone Number": "Додати номер телефону",
|
||||
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Чи використовуєте ви «хлібні крихти» (аватари на списком кімнат)",
|
||||
"Call failed due to misconfigured server": "Виклик не вдався через неправильне налаштування сервера",
|
||||
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "Запропонуйте адміністратору вашого домашнього серверу (<code>%(homeserverDomain)s</code>) налаштувати сервер TURN для надійної роботи викликів.",
|
||||
"Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Також ви можете спробувати використати публічний сервер <code>turn.matrix.org</code>, але це буде не настільки надійно, а також цей сервер матиме змогу бачити вашу IP-адресу. Ви можете керувати цим у налаштуваннях.",
|
||||
"Try using turn.matrix.org": "Спробуйте використати turn.matrix.org",
|
||||
"Replying With Files": "Відповісти файлами",
|
||||
"At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Зараз неможливо відповісти файлом. Хочете завантажити цей файл без відповіді?",
|
||||
"Name or Matrix ID": "Імʼя або Matrix ID",
|
||||
"Identity server has no terms of service": "Сервер ідентифікації не має умов надання послуг",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Щоб підтвердити адресу е-пошту або телефон ця дія потребує доступу до типового серверу ідентифікації <server />, але сервер не має жодних умов надання послуг.",
|
||||
"Only continue if you trust the owner of the server.": "Продовжуйте тільки якщо довіряєте власнику сервера.",
|
||||
"Trust": "Довіра",
|
||||
"Unable to load! Check your network connectivity and try again.": "Завантаження неможливе! Перевірте інтернет-зʼєднання та спробуйте ще.",
|
||||
"Email, name or Matrix ID": "Е-пошта, імʼя або Matrix ID",
|
||||
"Failed to start chat": "Не вдалося розпочати чат",
|
||||
"Failed to invite users to the room:": "Не вдалося запросити користувачів до кімнати:",
|
||||
"Messages": "Повідомлення",
|
||||
"Actions": "Дії",
|
||||
"Other": "Інше",
|
||||
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Додає ¯\\_(ツ)_/¯ на початку текстового повідомлення",
|
||||
"Sends a message as plain text, without interpreting it as markdown": "Надсилає повідомлення як чистий текст, не використовуючи markdown",
|
||||
"Upgrades a room to a new version": "Покращує кімнату до нової версії",
|
||||
"You do not have the required permissions to use this command.": "Вам бракує дозволу на використання цієї команди.",
|
||||
"Room upgrade confirmation": "Підтвердження покращення кімнати",
|
||||
"Upgrading a room can be destructive and isn't always necessary.": "Покращення кімнати може призвести до втрати даних та не є обовʼязковим.",
|
||||
"Room upgrades are usually recommended when a room version is considered <i>unstable</i>. Unstable room versions might have bugs, missing features, or security vulnerabilities.": "Рекомендується покращувати кімнату, якщо поточна її версія вважається <i>нестабільною</i>. Нестабільні версії кімнат можуть мати вади, відсутні функції або вразливості безпеки.",
|
||||
"Room upgrades usually only affect <i>server-side</i> processing of the room. If you're having problems with your Riot client, please file an issue with <issueLink />.": "Покращення кімнати загалом впливає лише на роботу з кімнатою на <i>сервері</i>. Якщо ви маєте проблему із вашим клієнтом Riot, надішліть свою проблему на <issueLink />.",
|
||||
"<b>Warning</b>: Upgrading a room will <i>not automatically migrate room members to the new version of the room.</i> We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "<b>Увага!</b>: Покращення кімнати <i>не перенесе автоматично усіх учасників до нової версії кімнати.</i> Ми опублікуємо посилання на нову кімнату у старій версії кімнати, а учасники мають власноруч клацнути це посилання, щоб приєднатися до нової кімнати.",
|
||||
"Please confirm that you'd like to go forward with upgrading this room from <oldVersion /> to <newVersion />.": "Підтвердьте, що ви згодні продовжити покращення цієї кімнати з <oldVersion /> до <newVersion />.",
|
||||
"Upgrade": "Покращення",
|
||||
"Changes your display nickname in the current room only": "Змінює ваше псевдо тільки для поточної кімнати",
|
||||
"Changes the avatar of the current room": "Змінює аватар поточної кімнати",
|
||||
"Changes your avatar in this current room only": "Змінює ваш аватар для поточної кімнати",
|
||||
"Changes your avatar in all rooms": "Змінює ваш аватар для усіх кімнат",
|
||||
"Gets or sets the room topic": "Показує чи встановлює тему кімнати",
|
||||
"This room has no topic.": "Ця кімната не має теми.",
|
||||
"Sets the room name": "Встановлює назву кімнати",
|
||||
"Use an identity server": "Використовувати сервер ідентифікації",
|
||||
"Use an identity server to invite by email. Manage in Settings.": "Використовувати ідентифікаційний сервер для запрошення через е-пошту. Керування у настройках.",
|
||||
"Unbans user with given ID": "Розблоковує користувача з вказаним ідентифікатором",
|
||||
"Adds a custom widget by URL to the room": "Додає власний віджет до кімнати за посиланням",
|
||||
"Please supply a https:// or http:// widget URL": "Вкажіть посилання на віджет — https:// або http://",
|
||||
"You cannot modify widgets in this room.": "Ви не можете змінювати віджети у цій кімнаті.",
|
||||
"Forces the current outbound group session in an encrypted room to be discarded": "Примусово відкидає поточний вихідний груповий сеанс у шифрованій кімнаті",
|
||||
"Sends the given message coloured as a rainbow": "Надсилає вказане повідомлення розфарбоване веселкою",
|
||||
"Your Riot is misconfigured": "Ваш Riot налаштовано неправильно",
|
||||
"Join the discussion": "Приєднатися до обговорення"
|
||||
}
|
||||
|
|
|
@ -2284,5 +2284,65 @@
|
|||
"You cancelled": "您已取消",
|
||||
"%(name)s cancelled": "%(name)s 已取消",
|
||||
"%(name)s wants to verify": "%(name)s 想要驗證",
|
||||
"You sent a verification request": "您已傳送了驗證請求"
|
||||
"You sent a verification request": "您已傳送了驗證請求",
|
||||
"Try out new ways to ignore people (experimental)": "試用新的方式來忽略人們(實驗性)",
|
||||
"My Ban List": "我的封鎖清單",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "這是您已封鎖的的使用者/伺服器清單,不要離開聊天室!",
|
||||
"Ignored/Blocked": "已忽略/已封鎖",
|
||||
"Error adding ignored user/server": "新增要忽略的使用者/伺服器錯誤",
|
||||
"Something went wrong. Please try again or view your console for hints.": "有東西出問題了。請重試或檢視您的主控臺以取得更多資訊。",
|
||||
"Error subscribing to list": "訂閱清單發生錯誤",
|
||||
"Please verify the room ID or alias and try again.": "請驗證聊天室 ID 或別名並再試一次。",
|
||||
"Error removing ignored user/server": "移除要忽略的使用者/伺服器發生錯誤",
|
||||
"Error unsubscribing from list": "從清單取消訂閱時發生錯誤",
|
||||
"Please try again or view your console for hints.": "請重試或檢視您的主控臺以取得更多資訊。",
|
||||
"None": "無",
|
||||
"Ban list rules - %(roomName)s": "封鎖清單規則 - %(roomName)s",
|
||||
"Server rules": "伺服器規則",
|
||||
"User rules": "使用者規則",
|
||||
"You have not ignored anyone.": "您尚未忽略任何人。",
|
||||
"You are currently ignoring:": "您目前正在忽略:",
|
||||
"You are not subscribed to any lists": "您尚未訂閱任何清單",
|
||||
"Unsubscribe": "取消訂閱",
|
||||
"View rules": "檢視規則",
|
||||
"You are currently subscribed to:": "您目前已訂閱:",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ 這些設定適用於進階使用者。",
|
||||
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "在此新增您想要忽略的使用者與伺服器。使用星號以讓 Riot 核對所有字元。舉例來說,<code>@bot:*</code> 將會忽略在任何伺服器上,所有有 'bot' 名稱的使用者。",
|
||||
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "忽略人們已透過封鎖清單完成,其中包含了誰要被封鎖的規則。訂閱封鎖清單代表被此清單封鎖的使用者/伺服器會對您隱藏。",
|
||||
"Personal ban list": "個人封鎖清單",
|
||||
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "您的個人封鎖清單包含了您個人不想要看到的所有使用者/伺服器。在忽略您的第一個使用者/伺服器後,您的聊天室清單中會出現新的聊天室,其名為「我的封鎖清單」,留在這個聊天室裡面以讓封鎖清單生效。",
|
||||
"Server or user ID to ignore": "要忽略的伺服器或使用者 ID",
|
||||
"eg: @bot:* or example.org": "例子:@bot:* 或 example.org",
|
||||
"Subscribed lists": "已訂閱的清單",
|
||||
"Subscribing to a ban list will cause you to join it!": "訂閱封鎖清單會讓您加入它!",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "如果這不是您想要的,請使用不同的工具來忽略使用者。",
|
||||
"Room ID or alias of ban list": "聊天室 ID 或封鎖清單的別名",
|
||||
"Subscribe": "訂閱",
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>": "您已經忽略了這個使用者,所以他們的訊息會隱藏。<a>無論如何都顯示。</a>",
|
||||
"Custom (%(level)s)": "自訂 (%(level)s)",
|
||||
"Trusted": "已信任",
|
||||
"Not trusted": "不信任",
|
||||
"Hide verified Sign-In's": "隱藏已驗證的登入",
|
||||
"%(count)s verified Sign-In's|other": "%(count)s 個已驗證的登入",
|
||||
"%(count)s verified Sign-In's|one": "1 個已驗證的登入",
|
||||
"Direct message": "直接訊息",
|
||||
"Unverify user": "未驗證的使用者",
|
||||
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> 在 %(roomName)s",
|
||||
"Messages in this room are end-to-end encrypted.": "在此聊天室中的訊息為端到端加密。",
|
||||
"Security": "安全",
|
||||
"Verify": "驗證",
|
||||
"Send verification requests in direct message, including a new verification UX in the member panel.": "在直接訊息中傳送驗證請求,包含成員面板中新的驗證使用者體驗。",
|
||||
"Enable cross-signing to verify per-user instead of per-device": "啟用交叉簽章以驗證每個使用者而非每個裝置",
|
||||
"Any of the following data may be shared:": "可能會分享以下資料:",
|
||||
"Your display name": "您的顯示名稱",
|
||||
"Your avatar URL": "您的大頭貼 URL",
|
||||
"Your user ID": "您的使用 ID",
|
||||
"Your theme": "您的佈景主題",
|
||||
"Riot URL": "Riot URL",
|
||||
"Room ID": "聊天室 ID",
|
||||
"Widget ID": "小工具 ID",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "使用這個小工具可能會與 %(widgetDomain)s 以及您的整合管理員分享資料 <helpIcon />。",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "使用這個小工具可能會與 %(widgetDomain)s 分享資料 <helpIcon /> 。",
|
||||
"Widget added by": "小工具新增由",
|
||||
"This widget may use cookies.": "這個小工具可能會使用 cookies。"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export interface MatrixEvent {
|
||||
type: string;
|
||||
sender: string;
|
||||
content: {};
|
||||
event_id: string;
|
||||
origin_server_ts: number;
|
||||
unsigned: ?{};
|
||||
room_id: string;
|
||||
}
|
||||
|
||||
export interface MatrixProfile {
|
||||
avatar_url: string;
|
||||
displayname: string;
|
||||
}
|
||||
|
||||
export interface CrawlerCheckpoint {
|
||||
roomId: string;
|
||||
token: string;
|
||||
fullCrawl: boolean;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
export interface ResultContext {
|
||||
events_before: [MatrixEvent];
|
||||
events_after: [MatrixEvent];
|
||||
profile_info: Map<string, MatrixProfile>;
|
||||
}
|
||||
|
||||
export interface ResultsElement {
|
||||
rank: number;
|
||||
result: MatrixEvent;
|
||||
context: ResultContext;
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
count: number;
|
||||
results: [ResultsElement];
|
||||
highlights: [string];
|
||||
}
|
||||
|
||||
export interface SearchArgs {
|
||||
search_term: string;
|
||||
before_limit: number;
|
||||
after_limit: number;
|
||||
order_by_recency: boolean;
|
||||
room_id: ?string;
|
||||
}
|
||||
|
||||
export interface HistoricEvent {
|
||||
event: MatrixEvent;
|
||||
profile: MatrixProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for classes that provide platform-specific event indexing.
|
||||
*
|
||||
* Instances of this class are provided by the application.
|
||||
*/
|
||||
export default class BaseEventIndexManager {
|
||||
/**
|
||||
* Does our EventIndexManager support event indexing.
|
||||
*
|
||||
* If an EventIndexManager implementor has runtime dependencies that
|
||||
* optionally enable event indexing they may override this method to perform
|
||||
* the necessary runtime checks here.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve to true if event indexing
|
||||
* is supported, false otherwise.
|
||||
*/
|
||||
async supportsEventIndexing(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Initialize the event index for the given user.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve when the event index is
|
||||
* initialized.
|
||||
*/
|
||||
async initEventIndex(): Promise<> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue up an event to be added to the index.
|
||||
*
|
||||
* @param {MatrixEvent} ev The event that should be added to the index.
|
||||
* @param {MatrixProfile} profile The profile of the event sender at the
|
||||
* time of the event receival.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve when the was queued up for
|
||||
* addition.
|
||||
*/
|
||||
async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise<> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if our event index is empty.
|
||||
*/
|
||||
indexIsEmpty(): Promise<boolean> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the previously queued up events to the index.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve once the queued up events
|
||||
* were added to the index.
|
||||
*/
|
||||
async commitLiveEvents(): Promise<> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the event index using the given term for matching events.
|
||||
*
|
||||
* @param {SearchArgs} searchArgs The search configuration sets what should
|
||||
* be searched for and what should be contained in the search result.
|
||||
*
|
||||
* @return {Promise<[SearchResult]>} A promise that will resolve to an array
|
||||
* of search results once the search is done.
|
||||
*/
|
||||
async searchEventIndex(searchArgs: SearchArgs): Promise<SearchResult> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add events from the room history to the event index.
|
||||
*
|
||||
* This is used to add a batch of events to the index.
|
||||
*
|
||||
* @param {[HistoricEvent]} events The list of events and profiles that
|
||||
* should be added to the event index.
|
||||
* @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that
|
||||
* should be stored in the index which should be used to continue crawling
|
||||
* the room.
|
||||
* @param {[CrawlerCheckpoint]} oldCheckpoint The checkpoint that was used
|
||||
* to fetch the current batch of events. This checkpoint will be removed
|
||||
* from the index.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve to true if all the events
|
||||
* were already added to the index, false otherwise.
|
||||
*/
|
||||
async addHistoricEvents(
|
||||
events: [HistoricEvent],
|
||||
checkpoint: CrawlerCheckpoint | null,
|
||||
oldCheckpoint: CrawlerCheckpoint | null,
|
||||
): Promise<bool> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new crawler checkpoint to the index.
|
||||
*
|
||||
* @param {CrawlerCheckpoint} checkpoint The checkpoint that should be added
|
||||
* to the index.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve once the checkpoint has
|
||||
* been stored.
|
||||
*/
|
||||
async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new crawler checkpoint to the index.
|
||||
*
|
||||
* @param {CrawlerCheckpoint} checkpoint The checkpoint that should be
|
||||
* removed from the index.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve once the checkpoint has
|
||||
* been removed.
|
||||
*/
|
||||
async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the stored checkpoints from the index.
|
||||
*
|
||||
* @return {Promise<[CrawlerCheckpoint]>} A promise that will resolve to an
|
||||
* array of crawler checkpoints once they have been loaded from the index.
|
||||
*/
|
||||
async loadCheckpoints(): Promise<[CrawlerCheckpoint]> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* close our event index.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve once the event index has
|
||||
* been closed.
|
||||
*/
|
||||
async closeEventIndex(): Promise<> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete our current event index.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve once the event index has
|
||||
* been deleted.
|
||||
*/
|
||||
async deleteEventIndex(): Promise<> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import PlatformPeg from "../PlatformPeg";
|
||||
import MatrixClientPeg from "../MatrixClientPeg";
|
||||
|
||||
/*
|
||||
* Event indexing class that wraps the platform specific event indexing.
|
||||
*/
|
||||
export default class EventIndex {
|
||||
constructor() {
|
||||
this.crawlerCheckpoints = [];
|
||||
// The time that the crawler will wait between /rooms/{room_id}/messages
|
||||
// requests
|
||||
this._crawlerTimeout = 3000;
|
||||
// The maximum number of events our crawler should fetch in a single
|
||||
// crawl.
|
||||
this._eventsPerCrawl = 100;
|
||||
this._crawler = null;
|
||||
this.liveEventsForIndex = new Set();
|
||||
}
|
||||
|
||||
async init() {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
await indexManager.initEventIndex();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
client.on('sync', this.onSync);
|
||||
client.on('Room.timeline', this.onRoomTimeline);
|
||||
client.on('Event.decrypted', this.onEventDecrypted);
|
||||
client.on('Room.timelineReset', this.onTimelineReset);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client === null) return;
|
||||
|
||||
client.removeListener('sync', this.onSync);
|
||||
client.removeListener('Room.timeline', this.onRoomTimeline);
|
||||
client.removeListener('Event.decrypted', this.onEventDecrypted);
|
||||
client.removeListener('Room.timelineReset', this.onTimelineReset);
|
||||
}
|
||||
|
||||
onSync = async (state, prevState, data) => {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
|
||||
if (prevState === null && state === "PREPARED") {
|
||||
// Load our stored checkpoints, if any.
|
||||
this.crawlerCheckpoints = await indexManager.loadCheckpoints();
|
||||
console.log("EventIndex: Loaded checkpoints",
|
||||
this.crawlerCheckpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevState === "PREPARED" && state === "SYNCING") {
|
||||
const addInitialCheckpoints = async () => {
|
||||
const client = MatrixClientPeg.get();
|
||||
const rooms = client.getRooms();
|
||||
|
||||
const isRoomEncrypted = (room) => {
|
||||
return client.isRoomEncrypted(room.roomId);
|
||||
};
|
||||
|
||||
// We only care to crawl the encrypted rooms, non-encrypted.
|
||||
// rooms can use the search provided by the homeserver.
|
||||
const encryptedRooms = rooms.filter(isRoomEncrypted);
|
||||
|
||||
console.log("EventIndex: Adding initial crawler checkpoints");
|
||||
|
||||
// Gather the prev_batch tokens and create checkpoints for
|
||||
// our message crawler.
|
||||
await Promise.all(encryptedRooms.map(async (room) => {
|
||||
const timeline = room.getLiveTimeline();
|
||||
const token = timeline.getPaginationToken("b");
|
||||
|
||||
console.log("EventIndex: Got token for indexer",
|
||||
room.roomId, token);
|
||||
|
||||
const backCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
direction: "b",
|
||||
};
|
||||
|
||||
const forwardCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
direction: "f",
|
||||
};
|
||||
|
||||
await indexManager.addCrawlerCheckpoint(backCheckpoint);
|
||||
await indexManager.addCrawlerCheckpoint(forwardCheckpoint);
|
||||
this.crawlerCheckpoints.push(backCheckpoint);
|
||||
this.crawlerCheckpoints.push(forwardCheckpoint);
|
||||
}));
|
||||
};
|
||||
|
||||
// If our indexer is empty we're most likely running Riot the
|
||||
// first time with indexing support or running it with an
|
||||
// initial sync. Add checkpoints to crawl our encrypted rooms.
|
||||
const eventIndexWasEmpty = await indexManager.isEventIndexEmpty();
|
||||
if (eventIndexWasEmpty) await addInitialCheckpoints();
|
||||
|
||||
// Start our crawler.
|
||||
this.startCrawler();
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevState === "SYNCING" && state === "SYNCING") {
|
||||
// A sync was done, presumably we queued up some live events,
|
||||
// commit them now.
|
||||
console.log("EventIndex: Committing events");
|
||||
await indexManager.commitLiveEvents();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onRoomTimeline = async (ev, room, toStartOfTimeline, removed, data) => {
|
||||
// We only index encrypted rooms locally.
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
|
||||
|
||||
// If it isn't a live event or if it's redacted there's nothing to
|
||||
// do.
|
||||
if (toStartOfTimeline || !data || !data.liveEvent
|
||||
|| ev.isRedacted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the event is not yet decrypted mark it for the
|
||||
// Event.decrypted callback.
|
||||
if (ev.isBeingDecrypted()) {
|
||||
const eventId = ev.getId();
|
||||
this.liveEventsForIndex.add(eventId);
|
||||
} else {
|
||||
// If the event is decrypted or is unencrypted add it to the
|
||||
// index now.
|
||||
await this.addLiveEventToIndex(ev);
|
||||
}
|
||||
}
|
||||
|
||||
onEventDecrypted = async (ev, err) => {
|
||||
const eventId = ev.getId();
|
||||
|
||||
// If the event isn't in our live event set, ignore it.
|
||||
if (!this.liveEventsForIndex.delete(eventId)) return;
|
||||
if (err) return;
|
||||
await this.addLiveEventToIndex(ev);
|
||||
}
|
||||
|
||||
async addLiveEventToIndex(ev) {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
|
||||
if (["m.room.message", "m.room.name", "m.room.topic"]
|
||||
.indexOf(ev.getType()) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const e = ev.toJSON().decrypted;
|
||||
const profile = {
|
||||
displayname: ev.sender.rawDisplayName,
|
||||
avatar_url: ev.sender.getMxcAvatarUrl(),
|
||||
};
|
||||
|
||||
indexManager.addEventToIndex(e, profile);
|
||||
}
|
||||
|
||||
async crawlerFunc() {
|
||||
// TODO either put this in a better place or find a library provided
|
||||
// method that does this.
|
||||
const sleep = async (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
console.log("EventIndex: Started crawler function");
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
|
||||
this._crawler = {};
|
||||
|
||||
this._crawler.cancel = () => {
|
||||
cancelled = true;
|
||||
};
|
||||
|
||||
while (!cancelled) {
|
||||
// This is a low priority task and we don't want to spam our
|
||||
// homeserver with /messages requests so we set a hefty timeout
|
||||
// here.
|
||||
await sleep(this._crawlerTimeout);
|
||||
|
||||
console.log("EventIndex: Running the crawler loop.");
|
||||
|
||||
if (cancelled) {
|
||||
break;
|
||||
}
|
||||
|
||||
const checkpoint = this.crawlerCheckpoints.shift();
|
||||
|
||||
/// There is no checkpoint available currently, one may appear if
|
||||
// a sync with limited room timelines happens, so go back to sleep.
|
||||
if (checkpoint === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log("EventIndex: crawling using checkpoint", checkpoint);
|
||||
|
||||
// We have a checkpoint, let us fetch some messages, again, very
|
||||
// conservatively to not bother our homeserver too much.
|
||||
const eventMapper = client.getEventMapper();
|
||||
// TODO we need to ensure to use member lazy loading with this
|
||||
// request so we get the correct profiles.
|
||||
let res;
|
||||
|
||||
try {
|
||||
res = await client._createMessagesRequest(
|
||||
checkpoint.roomId, checkpoint.token, this._eventsPerCrawl,
|
||||
checkpoint.direction);
|
||||
} catch (e) {
|
||||
console.log("EventIndex: Error crawling events:", e);
|
||||
this.crawlerCheckpoints.push(checkpoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (res.chunk.length === 0) {
|
||||
console.log("EventIndex: Done with the checkpoint", checkpoint);
|
||||
// We got to the start/end of our timeline, lets just
|
||||
// delete our checkpoint and go back to sleep.
|
||||
await indexManager.removeCrawlerCheckpoint(checkpoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert the plain JSON events into Matrix events so they get
|
||||
// decrypted if necessary.
|
||||
const matrixEvents = res.chunk.map(eventMapper);
|
||||
let stateEvents = [];
|
||||
if (res.state !== undefined) {
|
||||
stateEvents = res.state.map(eventMapper);
|
||||
}
|
||||
|
||||
const profiles = {};
|
||||
|
||||
stateEvents.forEach(ev => {
|
||||
if (ev.event.content &&
|
||||
ev.event.content.membership === "join") {
|
||||
profiles[ev.event.sender] = {
|
||||
displayname: ev.event.content.displayname,
|
||||
avatar_url: ev.event.content.avatar_url,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const decryptionPromises = [];
|
||||
|
||||
matrixEvents.forEach(ev => {
|
||||
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
|
||||
// TODO the decryption promise is a private property, this
|
||||
// should either be made public or we should convert the
|
||||
// event that gets fired when decryption is done into a
|
||||
// promise using the once event emitter method:
|
||||
// https://nodejs.org/api/events.html#events_events_once_emitter_name
|
||||
decryptionPromises.push(ev._decryptionPromise);
|
||||
}
|
||||
});
|
||||
|
||||
// Let us wait for all the events to get decrypted.
|
||||
await Promise.all(decryptionPromises);
|
||||
|
||||
// We filter out events for which decryption failed, are redacted
|
||||
// or aren't of a type that we know how to index.
|
||||
const isValidEvent = (value) => {
|
||||
return ([
|
||||
"m.room.message",
|
||||
"m.room.name",
|
||||
"m.room.topic",
|
||||
].indexOf(value.getType()) >= 0
|
||||
&& !value.isRedacted() && !value.isDecryptionFailure()
|
||||
);
|
||||
// TODO do we need to check if the event has all the valid
|
||||
// attributes?
|
||||
};
|
||||
|
||||
// TODO if there are no events at this point we're missing a lot
|
||||
// decryption keys, do we want to retry this checkpoint at a later
|
||||
// stage?
|
||||
const filteredEvents = matrixEvents.filter(isValidEvent);
|
||||
|
||||
// Let us convert the events back into a format that EventIndex can
|
||||
// consume.
|
||||
const events = filteredEvents.map((ev) => {
|
||||
const jsonEvent = ev.toJSON();
|
||||
|
||||
let e;
|
||||
if (ev.isEncrypted()) e = jsonEvent.decrypted;
|
||||
else e = jsonEvent;
|
||||
|
||||
let profile = {};
|
||||
if (e.sender in profiles) profile = profiles[e.sender];
|
||||
const object = {
|
||||
event: e,
|
||||
profile: profile,
|
||||
};
|
||||
return object;
|
||||
});
|
||||
|
||||
// Create a new checkpoint so we can continue crawling the room for
|
||||
// messages.
|
||||
const newCheckpoint = {
|
||||
roomId: checkpoint.roomId,
|
||||
token: res.end,
|
||||
fullCrawl: checkpoint.fullCrawl,
|
||||
direction: checkpoint.direction,
|
||||
};
|
||||
|
||||
console.log(
|
||||
"EventIndex: Crawled room",
|
||||
client.getRoom(checkpoint.roomId).name,
|
||||
"and fetched", events.length, "events.",
|
||||
);
|
||||
|
||||
try {
|
||||
const eventsAlreadyAdded = await indexManager.addHistoricEvents(
|
||||
events, newCheckpoint, checkpoint);
|
||||
// If all events were already indexed we assume that we catched
|
||||
// up with our index and don't need to crawl the room further.
|
||||
// Let us delete the checkpoint in that case, otherwise push
|
||||
// the new checkpoint to be used by the crawler.
|
||||
if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
|
||||
console.log("EventIndex: Checkpoint had already all events",
|
||||
"added, stopping the crawl", checkpoint);
|
||||
await indexManager.removeCrawlerCheckpoint(newCheckpoint);
|
||||
} else {
|
||||
this.crawlerCheckpoints.push(newCheckpoint);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("EventIndex: Error durring a crawl", e);
|
||||
// An error occurred, put the checkpoint back so we
|
||||
// can retry.
|
||||
this.crawlerCheckpoints.push(checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
this._crawler = null;
|
||||
|
||||
console.log("EventIndex: Stopping crawler function");
|
||||
}
|
||||
|
||||
onTimelineReset = async (room, timelineSet, resetAllTimelines) => {
|
||||
if (room === null) return;
|
||||
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
|
||||
|
||||
const timeline = room.getLiveTimeline();
|
||||
const token = timeline.getPaginationToken("b");
|
||||
|
||||
const backwardsCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
fullCrawl: false,
|
||||
direction: "b",
|
||||
};
|
||||
|
||||
console.log("EventIndex: Added checkpoint because of a limited timeline",
|
||||
backwardsCheckpoint);
|
||||
|
||||
await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);
|
||||
|
||||
this.crawlerCheckpoints.push(backwardsCheckpoint);
|
||||
}
|
||||
|
||||
startCrawler() {
|
||||
if (this._crawler !== null) return;
|
||||
this.crawlerFunc();
|
||||
}
|
||||
|
||||
stopCrawler() {
|
||||
if (this._crawler === null) return;
|
||||
this._crawler.cancel();
|
||||
}
|
||||
|
||||
async close() {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
this.removeListeners();
|
||||
this.stopCrawler();
|
||||
return indexManager.closeEventIndex();
|
||||
}
|
||||
|
||||
async search(searchArgs) {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
return indexManager.searchEventIndex(searchArgs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Object holding the global EventIndex object. Can only be initialized if the
|
||||
* platform supports event indexing.
|
||||
*/
|
||||
|
||||
import PlatformPeg from "../PlatformPeg";
|
||||
import EventIndex from "../indexing/EventIndex";
|
||||
import SettingsStore from '../settings/SettingsStore';
|
||||
|
||||
class EventIndexPeg {
|
||||
constructor() {
|
||||
this.index = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new EventIndex and initialize it if the platform supports it.
|
||||
*
|
||||
* @return {Promise<bool>} A promise that will resolve to true if an
|
||||
* EventIndex was successfully initialized, false otherwise.
|
||||
*/
|
||||
async init() {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_event_indexing")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
if (!indexManager || await indexManager.supportsEventIndexing() !== true) {
|
||||
console.log("EventIndex: Platform doesn't support event indexing,",
|
||||
"not initializing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const index = new EventIndex();
|
||||
|
||||
try {
|
||||
await index.init();
|
||||
} catch (e) {
|
||||
console.log("EventIndex: Error initializing the event index", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("EventIndex: Successfully initialized the event index");
|
||||
|
||||
this.index = index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current event index.
|
||||
*
|
||||
* @return {EventIndex} The current event index.
|
||||
*/
|
||||
get() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.index === null) return;
|
||||
this.index.stopCrawler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset our event store
|
||||
*
|
||||
* After a call to this the init() method will need to be called again.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve once the event index is
|
||||
* closed.
|
||||
*/
|
||||
async unset() {
|
||||
if (this.index === null) return;
|
||||
this.index.close();
|
||||
this.index = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete our event indexer.
|
||||
*
|
||||
* After a call to this the init() method will need to be called again.
|
||||
*
|
||||
* @return {Promise} A promise that will resolve once the event index is
|
||||
* deleted.
|
||||
*/
|
||||
async deleteEventIndex() {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
|
||||
if (indexManager !== null) {
|
||||
this.unset();
|
||||
console.log("EventIndex: Deleting event index.");
|
||||
await indexManager.deleteEventIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!global.mxEventIndexPeg) {
|
||||
global.mxEventIndexPeg = new EventIndexPeg();
|
||||
}
|
||||
module.exports = global.mxEventIndexPeg;
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from "./controllers/NotificationControllers";
|
||||
import CustomStatusController from "./controllers/CustomStatusController";
|
||||
import ThemeController from './controllers/ThemeController';
|
||||
import LowBandwidthController from "./controllers/LowBandwidthController";
|
||||
import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
|
||||
|
||||
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
|
||||
const LEVELS_ROOM_SETTINGS = ['device', 'room-device', 'room-account', 'account', 'config'];
|
||||
|
@ -120,12 +120,6 @@ export const SETTINGS = {
|
|||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_user_info_panel": {
|
||||
isFeature: true,
|
||||
displayName: _td("Use the new, consistent UserInfo panel for Room Members and Group Members"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_mjolnir": {
|
||||
isFeature: true,
|
||||
displayName: _td("Try out new ways to ignore people (experimental)"),
|
||||
|
@ -142,10 +136,24 @@ export const SETTINGS = {
|
|||
},
|
||||
"feature_dm_verification": {
|
||||
isFeature: true,
|
||||
displayName: _td("Send verification requests in direct message"),
|
||||
displayName: _td("Send verification requests in direct message," +
|
||||
" including a new verification UX in the member panel."),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_cross_signing": {
|
||||
isFeature: true,
|
||||
displayName: _td("Enable cross-signing to verify per-user instead of per-device"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
controller: new ReloadOnChangeController(),
|
||||
},
|
||||
"feature_event_indexing": {
|
||||
isFeature: true,
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
displayName: _td("Enable local event indexing and E2EE search (requires restart)"),
|
||||
default: false,
|
||||
},
|
||||
"useCiderComposer": {
|
||||
displayName: _td("Use the new, faster, composer for writing messages"),
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
|
@ -427,7 +435,7 @@ export const SETTINGS = {
|
|||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
displayName: _td('Low bandwidth mode'),
|
||||
default: false,
|
||||
controller: new LowBandwidthController(),
|
||||
controller: new ReloadOnChangeController(),
|
||||
},
|
||||
"fallbackICEServerAllowed": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import SettingController from "./SettingController";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
|
||||
export default class LowBandwidthController extends SettingController {
|
||||
export default class ReloadOnChangeController extends SettingController {
|
||||
onChange(level, roomId, newValue) {
|
||||
PlatformPeg.get().reload();
|
||||
}
|
|
@ -126,6 +126,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
if (!content || !content['recent_rooms']) {
|
||||
content = this._getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
|
||||
}
|
||||
if (!content) content = {}; // If we still don't have content, make some
|
||||
|
||||
content['recent_rooms'] = newValue;
|
||||
return MatrixClientPeg.get().setAccountData(BREADCRUMBS_EVENT_TYPE, content);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -33,6 +34,8 @@ export class ValidatedServerConfig {
|
|||
isUrl: string;
|
||||
|
||||
isDefault: boolean;
|
||||
|
||||
warning: string;
|
||||
}
|
||||
|
||||
export default class AutoDiscoveryUtils {
|
||||
|
@ -56,7 +59,14 @@ export default class AutoDiscoveryUtils {
|
|||
* implementation for known values.
|
||||
* @returns {*} The state for the component, given the error.
|
||||
*/
|
||||
static authComponentStateForError(err: Error, pageName="login"): Object {
|
||||
static authComponentStateForError(err: string | Error | null, pageName = "login"): Object {
|
||||
if (!err) {
|
||||
return {
|
||||
serverIsAlive: true,
|
||||
serverErrorIsFatal: false,
|
||||
serverDeadError: null,
|
||||
};
|
||||
}
|
||||
let title = _t("Cannot reach homeserver");
|
||||
let body = _t("Ensure you have a stable internet connection, or get in touch with the server admin");
|
||||
if (!AutoDiscoveryUtils.isLivelinessError(err)) {
|
||||
|
@ -153,11 +163,9 @@ export default class AutoDiscoveryUtils {
|
|||
/**
|
||||
* Validates a server configuration, using a homeserver domain name as input.
|
||||
* @param {string} serverName The homeserver domain name (eg: "matrix.org") to validate.
|
||||
* @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will
|
||||
* not be raised.
|
||||
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
|
||||
*/
|
||||
static async validateServerName(serverName: string, syntaxOnly=false): ValidatedServerConfig {
|
||||
static async validateServerName(serverName: string): ValidatedServerConfig {
|
||||
const result = await AutoDiscovery.findClientConfig(serverName);
|
||||
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result);
|
||||
}
|
||||
|
@ -186,7 +194,7 @@ export default class AutoDiscoveryUtils {
|
|||
const defaultConfig = SdkConfig.get()["validated_server_config"];
|
||||
|
||||
// Validate the identity server first because an invalid identity server causes
|
||||
// and invalid homeserver, which may not be picked up correctly.
|
||||
// an invalid homeserver, which may not be picked up correctly.
|
||||
|
||||
// Note: In the cases where we rely on the default IS from the config (namely
|
||||
// lack of identity server provided by the discovery method), we intentionally do not
|
||||
|
@ -197,20 +205,18 @@ export default class AutoDiscoveryUtils {
|
|||
preferredIdentityUrl = isResult["base_url"];
|
||||
} else if (isResult && isResult.state !== AutoDiscovery.PROMPT) {
|
||||
console.error("Error determining preferred identity server URL:", isResult);
|
||||
if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(isResult.error)) {
|
||||
if (isResult.state === AutoDiscovery.FAIL_ERROR) {
|
||||
if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error) !== -1) {
|
||||
throw newTranslatableError(isResult.error);
|
||||
}
|
||||
throw newTranslatableError(_td("Unexpected error resolving identity server configuration"));
|
||||
} // else the error is not related to syntax - continue anyways.
|
||||
|
||||
// rewrite homeserver error if we don't care about problems
|
||||
if (syntaxOnly) {
|
||||
hsResult.error = AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
|
||||
// rewrite homeserver error since we don't care about problems
|
||||
hsResult.error = AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
|
||||
|
||||
// Also use the user's supplied identity server if provided
|
||||
if (isResult["base_url"]) preferredIdentityUrl = isResult["base_url"];
|
||||
}
|
||||
// Also use the user's supplied identity server if provided
|
||||
if (isResult["base_url"]) preferredIdentityUrl = isResult["base_url"];
|
||||
}
|
||||
|
||||
if (hsResult.state !== AutoDiscovery.SUCCESS) {
|
||||
|
@ -241,6 +247,7 @@ export default class AutoDiscoveryUtils {
|
|||
hsNameIsDifferent: url.hostname !== preferredHomeserverName,
|
||||
isUrl: preferredIdentityUrl,
|
||||
isDefault: false,
|
||||
warning: hsResult.error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ export default class WidgetUtils {
|
|||
return client.setAccountData('m.widgets', userWidgets);
|
||||
}
|
||||
|
||||
static makeAppConfig(appId, app, sender, roomId) {
|
||||
static makeAppConfig(appId, app, senderUserId, roomId) {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const user = MatrixClientPeg.get().getUser(myUserId);
|
||||
const params = {
|
||||
|
@ -413,6 +413,11 @@ export default class WidgetUtils {
|
|||
'$theme': SettingsStore.getValue("theme"),
|
||||
};
|
||||
|
||||
if (!senderUserId) {
|
||||
throw new Error("Widgets must be created by someone - provide a senderUserId");
|
||||
}
|
||||
app.creatorUserId = senderUserId;
|
||||
|
||||
app.id = appId;
|
||||
app.name = app.name || app.type;
|
||||
|
||||
|
@ -425,7 +430,6 @@ export default class WidgetUtils {
|
|||
}
|
||||
|
||||
app.url = encodeUri(app.url, params);
|
||||
app.creatorUserId = (sender && sender.userId) ? sender.userId : null;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue