Merge branch 'develop' into rob/apps

This commit is contained in:
Robert Swain
2017-06-09 12:06:44 +02:00
208 changed files with 16199 additions and 3028 deletions

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
var MatrixClientPeg = require("./MatrixClientPeg");
import { _t } from './languageHandler';
/**
* Allows a user to add a third party identifier to their Home Server and,
@@ -44,7 +45,7 @@ class AddThreepid {
return res;
}, function(err) {
if (err.errcode == 'M_THREEPID_IN_USE') {
err.message = "This email address is already in use";
err.message = _t('This email address is already in use');
} else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`;
}
@@ -69,7 +70,7 @@ class AddThreepid {
return res;
}, function(err) {
if (err.errcode == 'M_THREEPID_IN_USE') {
err.message = "This phone number is already in use";
err.message = _t('This phone number is already in use');
} else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`;
}
@@ -91,7 +92,7 @@ class AddThreepid {
id_server: identityServerDomain
}, this.bind).catch(function(err) {
if (err.httpStatus === 401) {
err.message = "Failed to verify email address: make sure you clicked the link in the email";
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
}
else if (err.httpStatus) {
err.message += ` (Status ${err.httpStatus})`;

153
src/Analytics.js Normal file
View File

@@ -0,0 +1,153 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
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 { getCurrentLanguage } from './languageHandler';
import MatrixClientPeg from './MatrixClientPeg';
import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig';
function getRedactedUrl() {
const redactedHash = window.location.hash.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
// hardcoded url to make piwik happy
return 'https://riot.im/app/' + redactedHash;
}
const customVariables = {
'App Platform': 1,
'App Version': 2,
'User Type': 3,
'Chosen Language': 4,
'Instance': 5,
};
class Analytics {
constructor() {
this._paq = null;
this.disabled = true;
this.firstPage = true;
}
/**
* Enable Analytics if initialized but disabled
* otherwise try and initalize, no-op if piwik config missing
*/
enable() {
if (this._paq || this._init()) {
this.disabled = false;
}
}
/**
* Disable Analytics calls, will not fully unload Piwik until a refresh,
* but this is second best, Piwik should not pull anything implicitly.
*/
disable() {
this.trackEvent('Analytics', 'opt-out');
this.disabled = true;
}
_init() {
const config = SdkConfig.get();
if (!config || !config.piwik || !config.piwik.url || !config.piwik.siteId) return;
const url = config.piwik.url;
const siteId = config.piwik.siteId;
const self = this;
window._paq = this._paq = window._paq || [];
this._paq.push(['setTrackerUrl', url+'piwik.php']);
this._paq.push(['setSiteId', siteId]);
this._paq.push(['trackAllContentImpressions']);
this._paq.push(['discardHashTag', false]);
this._paq.push(['enableHeartBeatTimer']);
this._paq.push(['enableLinkTracking', true]);
const platform = PlatformPeg.get();
this._setVisitVariable('App Platform', platform.getHumanReadableName());
platform.getAppVersion().then((version) => {
this._setVisitVariable('App Version', version);
}).catch(() => {
this._setVisitVariable('App Version', 'unknown');
});
this._setVisitVariable('Chosen Language', getCurrentLanguage());
if (window.location.hostname === 'riot.im') {
this._setVisitVariable('Instance', window.location.pathname);
}
(function() {
const g = document.createElement('script');
const s = document.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=url+'piwik.js';
g.onload = function() {
console.log('Initialised anonymous analytics');
self._paq = window._paq;
};
s.parentNode.insertBefore(g, s);
})();
return true;
}
trackPageChange() {
if (this.disabled) return;
if (this.firstPage) {
// De-duplicate first page
// router seems to hit the fn twice
this.firstPage = false;
return;
}
this._paq.push(['setCustomUrl', getRedactedUrl()]);
this._paq.push(['trackPageView']);
}
trackEvent(category, action, name) {
if (this.disabled) return;
this._paq.push(['trackEvent', category, action, name]);
}
logout() {
if (this.disabled) return;
this._paq.push(['deleteCookies']);
}
login() { // not used currently
const cli = MatrixClientPeg.get();
if (this.disabled || !cli) return;
this._paq.push(['setUserId', `@${cli.getUserIdLocalpart()}:${cli.getDomain()}`]);
}
_setVisitVariable(key, value) {
this._paq.push(['setCustomVariable', customVariables[key], key, value, 'visit']);
}
setGuest(guest) {
if (this.disabled) return;
this._setVisitVariable('User Type', guest ? 'Guest' : 'Logged In');
}
}
if (!global.mxAnalytics) {
global.mxAnalytics = new Analytics();
}
module.exports = global.mxAnalytics;

View File

@@ -22,8 +22,8 @@ module.exports = {
avatarUrlForMember: function(member, width, height, resizeMethod) {
var url = member.getAvatarUrl(
MatrixClientPeg.get().getHomeserverUrl(),
width,
height,
Math.floor(width * window.devicePixelRatio),
Math.floor(height * window.devicePixelRatio),
resizeMethod,
false,
false
@@ -40,7 +40,9 @@ module.exports = {
avatarUrlForUser: function(user, width, height, resizeMethod) {
var url = ContentRepo.getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
width, height, resizeMethod
Math.floor(width * window.devicePixelRatio),
Math.floor(height * window.devicePixelRatio),
resizeMethod
);
if (!url || url.length === 0) {
return null;
@@ -57,4 +59,3 @@ module.exports = {
return 'img/' + images[total % images.length] + '.png';
}
};

View File

@@ -29,6 +29,11 @@ export default class BasePlatform {
this.errorDidOccur = false;
}
// Used primarily for Analytics
getHumanReadableName(): string {
return 'Base Platform';
}
setNotificationCount(count: number) {
this.notificationCount = count;
}
@@ -66,11 +71,14 @@ export default class BasePlatform {
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
}
loudNotification(ev: Event, room: Object) {
}
/**
* Returns a promise that resolves to a string representing
* the current version of the application.
*/
getAppVersion() {
getAppVersion(): Promise<string> {
throw new Error("getAppVersion not implemented!");
}
@@ -79,10 +87,12 @@ export default class BasePlatform {
* with getUserMedia, return a string explaining why not.
* Otherwise, return null.
*/
screenCaptureErrorString() {
screenCaptureErrorString(): string {
return "Not implemented";
}
isElectron(): boolean { return false; }
/**
* Restarts the application, without neccessarily reloading
* any application code

View File

@@ -55,6 +55,7 @@ var MatrixClientPeg = require('./MatrixClientPeg');
var PlatformPeg = require("./PlatformPeg");
var Modal = require('./Modal');
var sdk = require('./index');
import { _t } from './languageHandler';
var Matrix = require("matrix-js-sdk");
var dis = require("./dispatcher");
@@ -142,8 +143,8 @@ function _setCallListeners(call) {
play("busyAudio");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Call Timeout",
description: "The remote side failed to pick up."
title: _t('Call Timeout'),
description: _t('The remote side failed to pick up') + '.',
});
}
else if (oldState === "invite_sent") {
@@ -179,7 +180,8 @@ function _setCallState(call, roomId, status) {
}
dis.dispatch({
action: 'call_state',
room_id: roomId
room_id: roomId,
state: status,
});
}
@@ -203,8 +205,8 @@ function _onAction(payload) {
console.log("Can't capture screen: " + screenCapErrorString);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Unable to capture screen",
description: screenCapErrorString
title: _t('Unable to capture screen'),
description: screenCapErrorString,
});
return;
}
@@ -223,8 +225,8 @@ function _onAction(payload) {
if (module.exports.getAnyActiveCall()) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Existing Call",
description: "You are already in a call."
title: _t('Existing Call'),
description: _t('You are already in a call.'),
});
return; // don't allow >1 call to be placed.
}
@@ -233,8 +235,8 @@ function _onAction(payload) {
if (!MatrixClientPeg.get().supportsVoip()) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "VoIP is unsupported",
description: "You cannot place VoIP calls in this browser."
title: _t('VoIP is unsupported'),
description: _t('You cannot place VoIP calls in this browser.'),
});
return;
}
@@ -249,7 +251,7 @@ function _onAction(payload) {
if (members.length <= 1) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
description: "You cannot place a call with yourself."
description: _t('You cannot place a call with yourself.'),
});
return;
}
@@ -275,14 +277,14 @@ function _onAction(payload) {
if (!ConferenceHandler) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
description: "Conference calls are not supported in this client"
description: _t('Conference calls are not supported in this client'),
});
}
else if (!MatrixClientPeg.get().supportsVoip()) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "VoIP is unsupported",
description: "You cannot place VoIP calls in this browser."
title: _t('VoIP is unsupported'),
description: _t('You cannot place VoIP calls in this browser.'),
});
}
else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
@@ -294,14 +296,14 @@ function _onAction(payload) {
// Therefore we disable conference calling in E2E rooms.
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
description: "Conference calls are not supported in encrypted rooms",
description: _t('Conference calls are not supported in encrypted rooms'),
});
}
else {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning!",
description: "Conference calling is in development and may not be reliable.",
title: _t('Warning!'),
description: _t('Conference calling is in development and may not be reliable.'),
onFinished: confirm=>{
if (confirm) {
ConferenceHandler.createNewMatrixCall(
@@ -312,8 +314,8 @@ function _onAction(payload) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Conference call failed: " + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to set up conference call",
description: "Conference call failed.",
title: _t('Failed to set up conference call'),
description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''),
});
});
}

64
src/CallMediaHandler.js Normal file
View File

@@ -0,0 +1,64 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
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 UserSettingsStore from './UserSettingsStore';
import * as Matrix from 'matrix-js-sdk';
export default {
getDevices: function() {
// Only needed for Electron atm, though should work in modern browsers
// once permission has been granted to the webapp
return navigator.mediaDevices.enumerateDevices().then(function(devices) {
const audioIn = [];
const videoIn = [];
if (devices.some((device) => !device.label)) return false;
devices.forEach((device) => {
switch (device.kind) {
case 'audioinput': audioIn.push(device); break;
case 'videoinput': videoIn.push(device); break;
}
});
// console.log("Loaded WebRTC Devices", mediaDevices);
return {
audioinput: audioIn,
videoinput: videoIn,
};
}, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); });
},
loadDevices: function() {
// this.getDevices().then((devices) => {
const localSettings = UserSettingsStore.getLocalSettings();
// // if deviceId is not found, automatic fallback is in spec
// // recall previously stored inputs if any
Matrix.setMatrixCallAudioInput(localSettings['webrtc_audioinput']);
Matrix.setMatrixCallVideoInput(localSettings['webrtc_videoinput']);
// });
},
setAudioInput: function(deviceId) {
UserSettingsStore.setLocalSetting('webrtc_audioinput', deviceId);
Matrix.setMatrixCallAudioInput(deviceId);
},
setVideoInput: function(deviceId) {
UserSettingsStore.setLocalSetting('webrtc_videoinput', deviceId);
Matrix.setMatrixCallVideoInput(deviceId);
},
};

View File

@@ -21,6 +21,7 @@ var extend = require('./extend');
var dis = require('./dispatcher');
var MatrixClientPeg = require('./MatrixClientPeg');
var sdk = require('./index');
import { _t } from './languageHandler';
var Modal = require('./Modal');
var encrypt = require("browser-encrypt-attachment");
@@ -347,14 +348,14 @@ class ContentMessages {
}, function(err) {
error = err;
if (!upload.canceled) {
var desc = "The file '"+upload.fileName+"' failed to upload.";
var desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
if (err.http_status == 413) {
desc = "The file '"+upload.fileName+"' exceeds this home server's size limit for uploads";
desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName});
}
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Upload Failed",
description: desc
title: _t('Upload Failed'),
description: desc,
});
}
}).finally(() => {

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,38 +16,89 @@ limitations under the License.
*/
'use strict';
import { _t } from './languageHandler';
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function getDaysArray() {
return [
_t('Sun'),
_t('Mon'),
_t('Tue'),
_t('Wed'),
_t('Thu'),
_t('Fri'),
_t('Sat'),
];
}
function getMonthsArray() {
return [
_t('Jan'),
_t('Feb'),
_t('Mar'),
_t('Apr'),
_t('May'),
_t('Jun'),
_t('Jul'),
_t('Aug'),
_t('Sep'),
_t('Oct'),
_t('Nov'),
_t('Dec'),
];
}
function pad(n) {
return (n < 10 ? '0' : '') + n;
}
function twelveHourTime(date) {
let hours = date.getHours() % 12;
const minutes = pad(date.getMinutes());
const ampm = date.getHours() >= 12 ? 'PM' : 'AM';
hours = pad(hours ? hours : 12);
return `${hours}:${minutes}${ampm}`;
}
module.exports = {
formatDate: function(date) {
// date.toLocaleTimeString is completely system dependent.
// just go 24h for now
function pad(n) {
return (n < 10 ? '0' : '') + n;
}
var now = new Date();
const days = getDaysArray();
const months = getMonthsArray();
if (date.toDateString() === now.toDateString()) {
return pad(date.getHours()) + ':' + pad(date.getMinutes());
return this.formatTime(date);
}
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
// TODO: use standard date localize function provided in counterpart
return _t('%(weekDayName)s %(time)s', {weekDayName: days[date.getDay()], time: this.formatTime(date)});
}
else /* if (now.getFullYear() === date.getFullYear()) */ {
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
else if (now.getFullYear() === date.getFullYear()) {
// TODO: use standard date localize function provided in counterpart
return _t('%(weekDayName)s, %(monthName)s %(day)s %(time)s', {
weekDayName: days[date.getDay()],
monthName: months[date.getMonth()],
day: date.getDate(),
time: this.formatTime(date),
});
}
/*
else {
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
}
*/
return this.formatFullDate(date);
},
formatTime: function(date) {
//return pad(date.getHours()) + ':' + pad(date.getMinutes());
return ('00' + date.getHours()).slice(-2) + ':' + ('00' + date.getMinutes()).slice(-2);
}
};
formatFullDate: function(date, showTwelveHour=false) {
const days = getDaysArray();
const months = getMonthsArray();
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', {
weekDayName: days[date.getDay()],
monthName: months[date.getMonth()],
day: date.getDate(),
fullYear: date.getFullYear(),
time: showTwelveHour ? twelveHourTime(date) : this.formatTime(date),
});
},
formatTime: function(date, showTwelveHour=false) {
if (showTwelveHour) {
return twelveHourTime(date);
}
return pad(date.getHours()) + ':' + pad(date.getMinutes());
},
};

View File

@@ -25,6 +25,9 @@ import emojione from 'emojione';
import classNames from 'classnames';
emojione.imagePathSVG = 'emojione/svg/';
// Store PNG path for displaying many flags at once (for increased performance over SVG)
emojione.imagePathPNG = 'emojione/png/';
// Use SVGs for emojis
emojione.imageType = 'svg';
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
@@ -64,16 +67,23 @@ export function unicodeToImage(str) {
* emoji.
*
* @param alt {string} String to use for the image alt text
* @param useSvg {boolean} Whether to use SVG image src. If False, PNG will be used.
* @param unicode {integer} One or more integers representing unicode characters
* @returns A img node with the corresponding emoji
*/
export function charactersToImageNode(alt, ...unicode) {
export function charactersToImageNode(alt, useSvg, ...unicode) {
const fileName = unicode.map((u) => {
return u.toString(16);
}).join('-');
return <img alt={alt} src={`${emojione.imagePathSVG}${fileName}.svg${emojione.cacheBustParam}`}/>;
const path = useSvg ? emojione.imagePathSVG : emojione.imagePathPNG;
const fileType = useSvg ? 'svg' : 'png';
return <img
alt={alt}
src={`${path}${fileName}.${fileType}${emojione.cacheBustParam}`}
/>;
}
export function stripParagraphs(html: string): string {
const contentDiv = document.createElement('div');
contentDiv.innerHTML = html;
@@ -101,8 +111,7 @@ var sanitizeHtmlParams = {
allowedTags: [
'font', // custom to matrix for IRC-style font coloring
'del', // for markdown
// deliberately no h1/h2 to stop people shouting.
'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span',
],
@@ -139,17 +148,18 @@ var sanitizeHtmlParams = {
attribs.href = m[1];
delete attribs.target;
}
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
if (m) {
var entity = m[1];
if (entity[0] === '@') {
attribs.href = '#/user/' + entity;
else {
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
if (m) {
var entity = m[1];
if (entity[0] === '@') {
attribs.href = '#/user/' + entity;
}
else if (entity[0] === '#' || entity[0] === '!') {
attribs.href = '#/room/' + entity;
}
delete attribs.target;
}
else if (entity[0] === '#' || entity[0] === '!') {
attribs.href = '#/room/' + entity;
}
delete attribs.target;
}
}
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
@@ -335,6 +345,7 @@ export function bodyToHtml(content, highlights, opts) {
}
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
safeBody = unicodeToImage(safeBody);
safeBody = addCodeCopyButton(safeBody);
}
finally {
delete sanitizeHtmlParams.textFilter;
@@ -350,7 +361,24 @@ export function bodyToHtml(content, highlights, opts) {
'mx_EventTile_bigEmoji': emojiBody,
'markdown-body': isHtml,
});
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />;
}
function addCodeCopyButton(safeBody) {
// Adds 'copy' buttons to pre blocks
// Note that this only manipulates the markup to add the buttons:
// we need to add the event handlers once the nodes are in the DOM
// since we can't save functions in the markup.
// This is done in TextualBody
const el = document.createElement("div");
el.innerHTML = safeBody;
const codeBlocks = Array.from(el.getElementsByTagName("pre"));
codeBlocks.forEach(p => {
const button = document.createElement("span");
button.className = "mx_EventTile_copyButton";
p.appendChild(button);
});
return el.innerHTML;
}
export function emojifyText(text) {

View File

@@ -32,4 +32,5 @@ module.exports = {
DELETE: 46,
KEY_D: 68,
KEY_E: 69,
KEY_M: 77,
};

View File

@@ -19,6 +19,7 @@ import q from 'q';
import Matrix from 'matrix-js-sdk';
import MatrixClientPeg from './MatrixClientPeg';
import Analytics from './Analytics';
import Notifier from './Notifier';
import UserActivity from './UserActivity';
import Presence from './Presence';
@@ -27,6 +28,7 @@ import DMRoomMap from './utils/DMRoomMap';
import RtsClient from './RtsClient';
import Modal from './Modal';
import sdk from './index';
import { _t } from './languageHandler';
/**
* Called at startup, to attempt to build a logged-in Matrix session. It tries
@@ -49,7 +51,7 @@ import sdk from './index';
* If any of steps 1-4 are successful, it will call {setLoggedIn}, which in
* turn will raise on_logged_in and will_start_client events.
*
* It returns a promise which resolves when the above process completes.
* @param {object} opts
*
* @param {object} opts.realQueryParams: string->string map of the
* query-parameters extracted from the real query-string of the starting
@@ -67,6 +69,7 @@ import sdk from './index';
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
* true; defines the IS to use.
*
* @returns {Promise} a promise which resolves when the above process completes.
*/
export function loadSession(opts) {
const realQueryParams = opts.realQueryParams || {};
@@ -127,7 +130,7 @@ export function loadSession(opts) {
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
// create a temporary MatrixClient to do the login
var client = Matrix.createClient({
const client = Matrix.createClient({
baseUrl: queryParams.homeserver,
});
@@ -159,7 +162,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
// Not really sure where the right home for it is.
// create a temporary MatrixClient to do the login
var client = Matrix.createClient({
const client = Matrix.createClient({
baseUrl: hsUrl,
});
@@ -184,34 +187,42 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
// returns a promise which resolves to true if a session is found in
// localstorage
//
// N.B. Lifecycle.js should not maintain any further localStorage state, we
// are moving towards using SessionStore to keep track of state related
// to the current session (which is typically backed by localStorage).
//
// The plan is to gradually move the localStorage access done here into
// SessionStore to avoid bugs where the view becomes out-of-sync with
// localStorage (e.g. teamToken, isGuest etc.)
function _restoreFromLocalStorage() {
if (!localStorage) {
return q(false);
}
const hs_url = localStorage.getItem("mx_hs_url");
const is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
const access_token = localStorage.getItem("mx_access_token");
const user_id = localStorage.getItem("mx_user_id");
const device_id = localStorage.getItem("mx_device_id");
const hsUrl = localStorage.getItem("mx_hs_url");
const isUrl = localStorage.getItem("mx_is_url") || 'https://matrix.org';
const accessToken = localStorage.getItem("mx_access_token");
const userId = localStorage.getItem("mx_user_id");
const deviceId = localStorage.getItem("mx_device_id");
let is_guest;
let isGuest;
if (localStorage.getItem("mx_is_guest") !== null) {
is_guest = localStorage.getItem("mx_is_guest") === "true";
isGuest = localStorage.getItem("mx_is_guest") === "true";
} else {
// legacy key name
is_guest = localStorage.getItem("matrix-is-guest") === "true";
isGuest = localStorage.getItem("matrix-is-guest") === "true";
}
if (access_token && user_id && hs_url) {
console.log("Restoring session for %s", user_id);
if (accessToken && userId && hsUrl) {
console.log("Restoring session for %s", userId);
try {
setLoggedIn({
userId: user_id,
deviceId: device_id,
accessToken: access_token,
homeserverUrl: hs_url,
identityServerUrl: is_url,
guest: is_guest,
userId: userId,
deviceId: deviceId,
accessToken: accessToken,
homeserverUrl: hsUrl,
identityServerUrl: isUrl,
guest: isGuest,
});
return q(true);
} catch (e) {
@@ -228,14 +239,16 @@ function _handleRestoreFailure(e) {
let msg = e.message;
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
msg = "You need to log back in to generate end-to-end encryption keys "
+ "for this device and submit the public key to your homeserver. "
+ "This is a once off; sorry for the inconvenience.";
msg = _t(
'You need to log back in to generate end-to-end encryption keys'
+ ' for this device and submit the public key to your homeserver.'
+ ' This is a once off; sorry for the inconvenience.',
);
_clearLocalStorage();
return q.reject(new Error(
"Unable to restore previous session: " + msg,
_t('Unable to restore previous session') + ': ' + msg,
));
}
@@ -273,9 +286,15 @@ export function initRtsClient(url) {
*/
export function setLoggedIn(credentials) {
credentials.guest = Boolean(credentials.guest);
console.log("setLoggedIn => %s (guest=%s) hs=%s",
credentials.userId, credentials.guest,
credentials.homeserverUrl);
Analytics.setGuest(credentials.guest);
console.log(
"setLoggedIn: mxid:", credentials.userId,
"deviceId:", credentials.deviceId,
"guest:", credentials.guest,
"hs:", credentials.homeserverUrl,
);
// This is dispatched to indicate that the user is still in the process of logging in
// because `teamPromise` may take some time to resolve, breaking the assumption that
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
@@ -303,6 +322,16 @@ export function setLoggedIn(credentials) {
localStorage.setItem("mx_device_id", credentials.deviceId);
}
// The user registered as a PWLU (PassWord-Less User), the generated password
// is cached here such that the user can change it at a later time.
if (credentials.password) {
// Update SessionStore
dis.dispatch({
action: 'cached_password',
cachedPassword: credentials.password,
});
}
console.log("Session persisted for %s", credentials.userId);
} catch (e) {
console.warn("Error using local storage: can't persist session!", e);
@@ -352,7 +381,7 @@ export function logout() {
return;
}
return MatrixClientPeg.get().logout().then(onLoggedOut,
MatrixClientPeg.get().logout().then(onLoggedOut,
(err) => {
// Just throwing an error here is going to be very unhelpful
// if you're trying to log out because your server's down and
@@ -363,8 +392,8 @@ export function logout() {
// change your password).
console.log("Failed to call logout API: token will not be invalidated");
onLoggedOut();
}
);
},
).done();
}
/**
@@ -397,6 +426,7 @@ export function onLoggedOut() {
}
function _clearLocalStorage() {
Analytics.logout();
if (!window.localStorage) {
return;
}
@@ -420,7 +450,7 @@ export function stopMatrixClient() {
UserActivity.stop();
Presence.stop();
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
var cli = MatrixClientPeg.get();
const cli = MatrixClientPeg.get();
if (cli) {
cli.stopClient();
cli.removeAllListeners();

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
import Matrix from "matrix-js-sdk";
import { _t } from "./languageHandler";
import q from 'q';
import url from 'url';
@@ -97,9 +98,9 @@ export default class Login {
};
}, (error) => {
if (error.httpStatus === 403) {
error.friendlyText = "Guest access is disabled on this Home Server.";
error.friendlyText = _t("Guest access is disabled on this Home Server.");
} else {
error.friendlyText = "Failed to register as guest: " + error.data;
error.friendlyText = _t("Failed to register as guest:") + ' ' + error.data;
}
throw error;
});
@@ -158,12 +159,12 @@ export default class Login {
}, function(error) {
if (error.httpStatus == 400 && loginParams.medium) {
error.friendlyText = (
'This Home Server does not support login using email address.'
_t('This Home Server does not support login using email address.')
);
}
else if (error.httpStatus === 403) {
error.friendlyText = (
'Incorrect username and/or password.'
_t('Incorrect username and/or password.')
);
if (self._fallbackHsUrl) {
var fbClient = Matrix.createClient({
@@ -187,7 +188,7 @@ export default class Login {
}
else {
error.friendlyText = (
'There was a problem logging in. (HTTP ' + error.httpStatus + ")"
_t("There was a problem logging in.") + ' (HTTP ' + error.httpStatus + ")"
);
}
throw error;

View File

@@ -16,7 +16,6 @@ limitations under the License.
'use strict';
import q from "q";
import Matrix from 'matrix-js-sdk';
import utils from 'matrix-js-sdk/lib/utils';
import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';

View File

@@ -19,6 +19,7 @@ limitations under the License.
var React = require('react');
var ReactDOM = require('react-dom');
import Analytics from './Analytics';
import sdk from './index';
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
@@ -104,6 +105,9 @@ class ModalManager {
}
createDialog(Element, props, className) {
if (props && props.title) {
Analytics.trackEvent('Modal', props.title, 'createDialog');
}
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
}

View File

@@ -15,11 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var MatrixClientPeg = require("./MatrixClientPeg");
var PlatformPeg = require("./PlatformPeg");
var TextForEvent = require('./TextForEvent');
var Avatar = require('./Avatar');
var dis = require("./dispatcher");
import MatrixClientPeg from './MatrixClientPeg';
import PlatformPeg from './PlatformPeg';
import TextForEvent from './TextForEvent';
import Analytics from './Analytics';
import Avatar from './Avatar';
import dis from './dispatcher';
import sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
/*
* Dispatches:
@@ -29,7 +33,7 @@ var dis = require("./dispatcher");
* }
*/
var Notifier = {
const Notifier = {
notifsByRoom: {},
notificationMessageForEvent: function(ev) {
@@ -48,16 +52,16 @@ var Notifier = {
return;
}
var msg = this.notificationMessageForEvent(ev);
let msg = this.notificationMessageForEvent(ev);
if (!msg) return;
var title;
if (!ev.sender || room.name == ev.sender.name) {
let title;
if (!ev.sender || room.name === ev.sender.name) {
title = room.name;
// notificationMessageForEvent includes sender,
// but we already have the sender here
if (ev.getContent().body) msg = ev.getContent().body;
} else if (ev.getType() == 'm.room.member') {
} else if (ev.getType() === 'm.room.member') {
// context is all in the message here, we don't need
// to display sender info
title = room.name;
@@ -68,7 +72,7 @@ var Notifier = {
if (ev.getContent().body) msg = ev.getContent().body;
}
var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
const avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
ev.sender, 40, 40, 'crop'
) : null;
@@ -83,7 +87,7 @@ var Notifier = {
},
_playAudioNotification: function(ev, room) {
var e = document.getElementById("messageAudio");
const e = document.getElementById("messageAudio");
if (e) {
e.load();
e.play();
@@ -95,7 +99,7 @@ var Notifier = {
this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
this.boundOnRoomReceipt = this.onRoomReceipt.bind(this);
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
MatrixClientPeg.get().on("Room.receipt", this.boundOnRoomReceipt);
MatrixClientPeg.get().on('Room.receipt', this.boundOnRoomReceipt);
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
this.toolbarHidden = false;
this.isSyncing = false;
@@ -104,7 +108,7 @@ var Notifier = {
stop: function() {
if (MatrixClientPeg.get() && this.boundOnRoomTimeline) {
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
MatrixClientPeg.get().removeListener("Room.receipt", this.boundOnRoomReceipt);
MatrixClientPeg.get().removeListener('Room.receipt', this.boundOnRoomReceipt);
MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
}
this.isSyncing = false;
@@ -118,10 +122,13 @@ var Notifier = {
setEnabled: function(enable, callback) {
const plaf = PlatformPeg.get();
if (!plaf) return;
Analytics.trackEvent('Notifier', 'Set Enabled', enable);
// make sure that we persist the current setting audio_enabled setting
// before changing anything
if (global.localStorage) {
if(global.localStorage.getItem('audio_notifications_enabled') == null) {
if (global.localStorage.getItem('audio_notifications_enabled') === null) {
this.setAudioEnabled(this.isEnabled());
}
}
@@ -131,6 +138,14 @@ var Notifier = {
plaf.requestNotificationPermission().done((result) => {
if (result !== 'granted') {
// The permission request was dismissed or denied
const description = result === 'denied'
? _t('Riot does not have permission to send you notifications - please check your browser settings')
: _t('Riot was not given permission to send notifications - please try again');
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createDialog(ErrorDialog, {
title: _t('Unable to enable Notifications'),
description,
});
return;
}
@@ -141,7 +156,7 @@ var Notifier = {
if (callback) callback();
dis.dispatch({
action: "notifier_enabled",
value: true
value: true,
});
});
// clear the notifications_hidden flag, so that if notifications are
@@ -152,7 +167,7 @@ var Notifier = {
global.localStorage.setItem('notifications_enabled', 'false');
dis.dispatch({
action: "notifier_enabled",
value: false
value: false,
});
}
},
@@ -165,7 +180,7 @@ var Notifier = {
if (!global.localStorage) return true;
var enabled = global.localStorage.getItem('notifications_enabled');
const enabled = global.localStorage.getItem('notifications_enabled');
if (enabled === null) return true;
return enabled === 'true';
},
@@ -173,12 +188,12 @@ var Notifier = {
setAudioEnabled: function(enable) {
if (!global.localStorage) return;
global.localStorage.setItem('audio_notifications_enabled',
enable ? 'true' : 'false');
enable ? 'true' : 'false');
},
isAudioEnabled: function(enable) {
if (!global.localStorage) return true;
var enabled = global.localStorage.getItem(
const enabled = global.localStorage.getItem(
'audio_notifications_enabled');
// default to true if the popups are enabled
if (enabled === null) return this.isEnabled();
@@ -188,11 +203,13 @@ var Notifier = {
setToolbarHidden: function(hidden, persistent = true) {
this.toolbarHidden = hidden;
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
// XXX: why are we dispatching this here?
// this is nothing to do with notifier_enabled
dis.dispatch({
action: "notifier_enabled",
value: this.isEnabled()
value: this.isEnabled(),
});
// update the info to localStorage for persistent settings
@@ -215,8 +232,7 @@ var Notifier = {
onSyncStateChange: function(state) {
if (state === "SYNCING") {
this.isSyncing = true;
}
else if (state === "STOPPED" || state === "ERROR") {
} else if (state === "STOPPED" || state === "ERROR") {
this.isSyncing = false;
}
},
@@ -225,22 +241,23 @@ var Notifier = {
if (toStartOfTimeline) return;
if (!room) return;
if (!this.isSyncing) return; // don't alert for any messages initially
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions && actions.notify) {
if (this.isEnabled()) {
this._displayPopupNotification(ev, room);
}
if (actions.tweaks.sound && this.isAudioEnabled()) {
PlatformPeg.get().loudNotification(ev, room);
this._playAudioNotification(ev, room);
}
}
},
onRoomReceipt: function(ev, room) {
if (room.getUnreadNotificationCount() == 0) {
if (room.getUnreadNotificationCount() === 0) {
// ideally we would clear each notification when it was read,
// but we have no way, given a read receipt, to know whether
// the receipt comes before or after an event, so we can't
@@ -255,7 +272,7 @@ var Notifier = {
}
delete this.notifsByRoom[room.roomId];
}
}
},
};
if (!global.mxNotifier) {

View File

@@ -15,6 +15,7 @@ limitations under the License.
*/
var Matrix = require("matrix-js-sdk");
import { _t } from './languageHandler';
/**
* Allows a user to reset their password on a homeserver.
@@ -53,7 +54,7 @@ class PasswordReset {
return res;
}, function(err) {
if (err.errcode == 'M_THREEPID_NOT_FOUND') {
err.message = "This email address was not found";
err.message = _t('This email address was not found');
} else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`;
}
@@ -78,10 +79,10 @@ class PasswordReset {
}
}, this.password).catch(function(err) {
if (err.httpStatus === 401) {
err.message = "Failed to verify email address: make sure you clicked the link in the email";
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
}
else if (err.httpStatus === 404) {
err.message = "Your email address does not appear to be associated with a Matrix ID on this Homeserver.";
err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver.');
}
else if (err.httpStatus) {
err.message += ` (Status ${err.httpStatus})`;

34
src/Roles.js Normal file
View File

@@ -0,0 +1,34 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { _t } from './languageHandler';
export function levelRoleMap() {
return {
undefined: _t('Default'),
0: _t('User'),
50: _t('Moderator'),
100: _t('Admin'),
};
}
export function textualPowerLevel(level, userDefault) {
const LEVEL_ROLE_MAP = this.levelRoleMap();
if (LEVEL_ROLE_MAP[level]) {
return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`);
} else {
return level;
}
}

View File

@@ -15,7 +15,6 @@ limitations under the License.
*/
import MatrixClientPeg from './MatrixClientPeg';
import DMRoomMap from './utils/DMRoomMap';
import q from 'q';
/**

View File

@@ -94,6 +94,22 @@ Example:
}
}
get_membership_count
--------------------
Get the number of joined users in the room.
Request:
- room_id is the room to get the count in.
Response:
78
Example:
{
action: "get_membership_count",
room_id: "!foo:bar",
response: 78
}
membership_state AND bot_options
--------------------------------
Get the content of the "m.room.member" or "m.room.bot.options" state event respectively.
@@ -125,6 +141,7 @@ const SdkConfig = require('./SdkConfig');
const MatrixClientPeg = require("./MatrixClientPeg");
const MatrixEvent = require("matrix-js-sdk").MatrixEvent;
const dis = require("./dispatcher");
import { _t } from './languageHandler';
function sendResponse(event, res) {
const data = JSON.parse(JSON.stringify(event.data));
@@ -150,7 +167,7 @@ function inviteUser(event, roomId, userId) {
console.log(`Received request to invite ${userId} into room ${roomId}`);
const client = MatrixClientPeg.get();
if (!client) {
sendError(event, "You need to be logged in.");
sendError(event, _t('You need to be logged in.'));
return;
}
const room = client.getRoom(roomId);
@@ -170,7 +187,7 @@ function inviteUser(event, roomId, userId) {
success: true,
});
}, function(err) {
sendError(event, "You need to be able to invite users to do that.", err);
sendError(event, _t('You need to be able to invite users to do that.'), err);
});
}
@@ -181,7 +198,7 @@ function setPlumbingState(event, roomId, status) {
console.log(`Received request to set plumbing state to status "${status}" in room ${roomId}`);
const client = MatrixClientPeg.get();
if (!client) {
sendError(event, "You need to be logged in.");
sendError(event, _t('You need to be logged in.'));
return;
}
client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => {
@@ -189,7 +206,7 @@ function setPlumbingState(event, roomId, status) {
success: true,
});
}, (err) => {
sendError(event, err.message ? err.message : "Failed to send request.", err);
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
});
}
@@ -197,7 +214,7 @@ function setBotOptions(event, roomId, userId) {
console.log(`Received request to set options for bot ${userId} in room ${roomId}`);
const client = MatrixClientPeg.get();
if (!client) {
sendError(event, "You need to be logged in.");
sendError(event, _t('You need to be logged in.'));
return;
}
client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => {
@@ -205,20 +222,20 @@ function setBotOptions(event, roomId, userId) {
success: true,
});
}, (err) => {
sendError(event, err.message ? err.message : "Failed to send request.", err);
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
});
}
function setBotPower(event, roomId, userId, level) {
if (!(Number.isInteger(level) && level >= 0)) {
sendError(event, "Power level must be positive integer.");
sendError(event, _t('Power level must be positive integer.'));
return;
}
console.log(`Received request to set power level to ${level} for bot ${userId} in room ${roomId}.`);
const client = MatrixClientPeg.get();
if (!client) {
sendError(event, "You need to be logged in.");
sendError(event, _t('You need to be logged in.'));
return;
}
@@ -235,7 +252,7 @@ function setBotPower(event, roomId, userId, level) {
success: true,
});
}, (err) => {
sendError(event, err.message ? err.message : "Failed to send request.", err);
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
});
});
}
@@ -255,15 +272,30 @@ function botOptions(event, roomId, userId) {
returnStateEvent(event, roomId, "m.room.bot.options", "_" + userId);
}
function returnStateEvent(event, roomId, eventType, stateKey) {
function getMembershipCount(event, roomId) {
const client = MatrixClientPeg.get();
if (!client) {
sendError(event, "You need to be logged in.");
sendError(event, _t('You need to be logged in.'));
return;
}
const room = client.getRoom(roomId);
if (!room) {
sendError(event, "This room is not recognised.");
sendError(event, _t('This room is not recognised.'));
return;
}
const count = room.getJoinedMembers().length;
sendResponse(event, count);
}
function returnStateEvent(event, roomId, eventType, stateKey) {
const client = MatrixClientPeg.get();
if (!client) {
sendError(event, _t('You need to be logged in.'));
return;
}
const room = client.getRoom(roomId);
if (!room) {
sendError(event, _t('This room is not recognised.'));
return;
}
const stateEvent = room.currentState.getStateEvents(eventType, stateKey);
@@ -313,13 +345,13 @@ const onMessage = function(event) {
const roomId = event.data.room_id;
const userId = event.data.user_id;
if (!roomId) {
sendError(event, "Missing room_id in request");
sendError(event, _t('Missing room_id in request'));
return;
}
let promise = Promise.resolve(currentRoomId);
if (!currentRoomId) {
if (!currentRoomAlias) {
sendError(event, "Must be viewing a room");
sendError(event, _t('Must be viewing a room'));
return;
}
// no room ID but there is an alias, look it up.
@@ -331,7 +363,7 @@ const onMessage = function(event) {
promise.then((viewingRoomId) => {
if (roomId !== viewingRoomId) {
sendError(event, "Room " + roomId + " not visible");
sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId}));
return;
}
@@ -342,10 +374,13 @@ const onMessage = function(event) {
} else if (event.data.action === "set_plumbing_state") {
setPlumbingState(event, roomId, event.data.status);
return;
} else if (event.data.action === "get_membership_count") {
getMembershipCount(event, roomId);
return;
}
if (!userId) {
sendError(event, "Missing user_id in request");
sendError(event, _t('Missing user_id in request'));
return;
}
switch (event.data.action) {
@@ -370,7 +405,7 @@ const onMessage = function(event) {
}
}, (err) => {
console.error(err);
sendError(event, "Failed to lookup current room.");
sendError(event, _t('Failed to lookup current room') + '.');
});
};

View File

@@ -23,22 +23,28 @@ class Skinner {
if (this.components === null) {
throw new Error(
"Attempted to get a component before a skin has been loaded."+
"This is probably because either:"+
" This is probably because either:"+
" a) Your app has not called sdk.loadSkin(), or"+
" b) A component has called getComponent at the root level"
" b) A component has called getComponent at the root level",
);
}
var comp = this.components[name];
if (comp) {
return comp;
}
let comp = this.components[name];
// XXX: Temporarily also try 'views.' as we're currently
// leaving the 'views.' off views.
var comp = this.components['views.'+name];
if (comp) {
return comp;
if (!comp) {
comp = this.components['views.'+name];
}
throw new Error("No such component: "+name);
if (!comp) {
throw new Error("No such component: "+name);
}
// components have to be functions.
const validType = typeof comp === 'function';
if (!validType) {
throw new Error(`Not a valid component: ${name}.`);
}
return comp;
}
load(skinObject) {

View File

@@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var MatrixClientPeg = require("./MatrixClientPeg");
var dis = require("./dispatcher");
var Tinter = require("./Tinter");
import MatrixClientPeg from "./MatrixClientPeg";
import dis from "./dispatcher";
import Tinter from "./Tinter";
import sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
@@ -41,58 +42,64 @@ class Command {
}
getUsage() {
return "Usage: " + this.getCommandWithArgs();
return _t('Usage') + ': ' + this.getCommandWithArgs();
}
}
var reject = function(msg) {
function reject(msg) {
return {
error: msg
error: msg,
};
};
}
var success = function(promise) {
function success(promise) {
return {
promise: promise
promise: promise,
};
};
}
var commands = {
/* Disable the "unexpected this" error for these commands - all of the run
* functions are called with `this` bound to the Command instance.
*/
/* eslint-disable babel/no-invalid-this */
const commands = {
ddg: new Command("ddg", "<query>", function(roomId, args) {
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
// TODO Don't explain this away, actually show a search UI here.
Modal.createDialog(ErrorDialog, {
title: "/ddg is not a command",
description: "To use it, just wait for autocomplete results to load and tab through them.",
title: _t('/ddg is not a command'),
description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
});
return success();
}),
// Change your nickname
nick: new Command("nick", "<display_name>", function(room_id, args) {
nick: new Command("nick", "<display_name>", function(roomId, args) {
if (args) {
return success(
MatrixClientPeg.get().setDisplayName(args)
MatrixClientPeg.get().setDisplayName(args),
);
}
return reject(this.getUsage());
}),
// Changes the colorscheme of your current room
tint: new Command("tint", "<color1> [<color2>]", function(room_id, args) {
tint: new Command("tint", "<color1> [<color2>]", function(roomId, args) {
if (args) {
var matches = args.match(/^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))( +(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?$/);
const matches = args.match(/^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))( +(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?$/);
if (matches) {
Tinter.tint(matches[1], matches[4]);
var colorScheme = {};
const colorScheme = {};
colorScheme.primary_color = matches[1];
if (matches[4]) {
colorScheme.secondary_color = matches[4];
}
return success(
MatrixClientPeg.get().setRoomAccountData(
room_id, "org.matrix.room.color_scheme", colorScheme
)
roomId, "org.matrix.room.color_scheme", colorScheme,
),
);
}
}
@@ -100,22 +107,22 @@ var commands = {
}),
// Change the room topic
topic: new Command("topic", "<topic>", function(room_id, args) {
topic: new Command("topic", "<topic>", function(roomId, args) {
if (args) {
return success(
MatrixClientPeg.get().setRoomTopic(room_id, args)
MatrixClientPeg.get().setRoomTopic(roomId, args),
);
}
return reject(this.getUsage());
}),
// Invite a user
invite: new Command("invite", "<userId>", function(room_id, args) {
invite: new Command("invite", "<userId>", function(roomId, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
const matches = args.match(/^(\S+)$/);
if (matches) {
return success(
MatrixClientPeg.get().invite(room_id, matches[1])
MatrixClientPeg.get().invite(roomId, matches[1]),
);
}
}
@@ -123,21 +130,21 @@ var commands = {
}),
// Join a room
join: new Command("join", "#alias:domain", function(room_id, args) {
join: new Command("join", "#alias:domain", function(roomId, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
const matches = args.match(/^(\S+)$/);
if (matches) {
var room_alias = matches[1];
if (room_alias[0] !== '#') {
let roomAlias = matches[1];
if (roomAlias[0] !== '#') {
return reject(this.getUsage());
}
if (!room_alias.match(/:/)) {
room_alias += ':' + MatrixClientPeg.get().getDomain();
if (!roomAlias.match(/:/)) {
roomAlias += ':' + MatrixClientPeg.get().getDomain();
}
dis.dispatch({
action: 'view_room',
room_alias: room_alias,
room_alias: roomAlias,
auto_join: true,
});
@@ -147,29 +154,29 @@ var commands = {
return reject(this.getUsage());
}),
part: new Command("part", "[#alias:domain]", function(room_id, args) {
var targetRoomId;
part: new Command("part", "[#alias:domain]", function(roomId, args) {
let targetRoomId;
if (args) {
var matches = args.match(/^(\S+)$/);
const matches = args.match(/^(\S+)$/);
if (matches) {
var room_alias = matches[1];
if (room_alias[0] !== '#') {
let roomAlias = matches[1];
if (roomAlias[0] !== '#') {
return reject(this.getUsage());
}
if (!room_alias.match(/:/)) {
room_alias += ':' + MatrixClientPeg.get().getDomain();
if (!roomAlias.match(/:/)) {
roomAlias += ':' + MatrixClientPeg.get().getDomain();
}
// Try to find a room with this alias
var rooms = MatrixClientPeg.get().getRooms();
for (var i = 0; i < rooms.length; i++) {
var aliasEvents = rooms[i].currentState.getStateEvents(
"m.room.aliases"
const rooms = MatrixClientPeg.get().getRooms();
for (let i = 0; i < rooms.length; i++) {
const aliasEvents = rooms[i].currentState.getStateEvents(
"m.room.aliases",
);
for (var j = 0; j < aliasEvents.length; j++) {
var aliases = aliasEvents[j].getContent().aliases || [];
for (var k = 0; k < aliases.length; k++) {
if (aliases[k] === room_alias) {
for (let j = 0; j < aliasEvents.length; j++) {
const aliases = aliasEvents[j].getContent().aliases || [];
for (let k = 0; k < aliases.length; k++) {
if (aliases[k] === roomAlias) {
targetRoomId = rooms[i].roomId;
break;
}
@@ -178,27 +185,28 @@ var commands = {
}
if (targetRoomId) { break; }
}
}
if (!targetRoomId) {
return reject("Unrecognised room alias: " + room_alias);
if (!targetRoomId) {
return reject(_t("Unrecognised room alias:") + ' ' + roomAlias);
}
}
}
if (!targetRoomId) targetRoomId = room_id;
if (!targetRoomId) targetRoomId = roomId;
return success(
MatrixClientPeg.get().leave(targetRoomId).then(
function() {
dis.dispatch({action: 'view_next_room'});
})
function() {
dis.dispatch({action: 'view_next_room'});
},
),
);
}),
// Kick a user from the room with an optional reason
kick: new Command("kick", "<userId> [<reason>]", function(room_id, args) {
kick: new Command("kick", "<userId> [<reason>]", function(roomId, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
const matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(
MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
MatrixClientPeg.get().kick(roomId, matches[1], matches[3]),
);
}
}
@@ -206,12 +214,12 @@ var commands = {
}),
// Ban a user from the room with an optional reason
ban: new Command("ban", "<userId> [<reason>]", function(room_id, args) {
ban: new Command("ban", "<userId> [<reason>]", function(roomId, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
const matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(
MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
MatrixClientPeg.get().ban(roomId, matches[1], matches[3]),
);
}
}
@@ -219,13 +227,13 @@ var commands = {
}),
// Unban a user from the room
unban: new Command("unban", "<userId>", function(room_id, args) {
unban: new Command("unban", "<userId>", function(roomId, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
const matches = args.match(/^(\S+)$/);
if (matches) {
// Reset the user membership to "leave" to unban him
return success(
MatrixClientPeg.get().unban(room_id, matches[1])
MatrixClientPeg.get().unban(roomId, matches[1]),
);
}
}
@@ -233,27 +241,27 @@ var commands = {
}),
// Define the power level of a user
op: new Command("op", "<userId> [<power level>]", function(room_id, args) {
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(\d+))?$/);
var powerLevel = 50; // default power level for op
const matches = args.match(/^(\S+?)( +(\d+))?$/);
let powerLevel = 50; // default power level for op
if (matches) {
var user_id = matches[1];
const userId = matches[1];
if (matches.length === 4 && undefined !== matches[3]) {
powerLevel = parseInt(matches[3]);
}
if (powerLevel !== NaN) {
var room = MatrixClientPeg.get().getRoom(room_id);
if (!isNaN(powerLevel)) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
return reject("Bad room ID: " + room_id);
return reject("Bad room ID: " + roomId);
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
const powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", "",
);
return success(
MatrixClientPeg.get().setPowerLevel(
room_id, user_id, powerLevel, powerLevelEvent
)
roomId, userId, powerLevel, powerLevelEvent,
),
);
}
}
@@ -262,32 +270,94 @@ var commands = {
}),
// Reset the power level of a user
deop: new Command("deop", "<userId>", function(room_id, args) {
deop: new Command("deop", "<userId>", function(roomId, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
const matches = args.match(/^(\S+)$/);
if (matches) {
var room = MatrixClientPeg.get().getRoom(room_id);
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
return reject("Bad room ID: " + room_id);
return reject("Bad room ID: " + roomId);
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
const powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", "",
);
return success(
MatrixClientPeg.get().setPowerLevel(
room_id, args, undefined, powerLevelEvent
)
roomId, args, undefined, powerLevelEvent,
),
);
}
}
return reject(this.getUsage());
})
}),
// Verify a user, device, and pubkey tuple
verify: new Command("verify", "<userId> <deviceId> <deviceSigningKey>", function(roomId, args) {
if (args) {
const matches = args.match(/^(\S+) +(\S+) +(\S+)$/);
if (matches) {
const userId = matches[1];
const deviceId = matches[2];
const fingerprint = matches[3];
const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId);
if (!device) {
return reject(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`);
}
if (device.isVerified()) {
if (device.getFingerprint() === fingerprint) {
return reject(_t(`Device already verified!`));
} else {
return reject(_t(`WARNING: Device already verified, but keys do NOT MATCH!`));
}
}
if (device.getFingerprint() === fingerprint) {
MatrixClientPeg.get().setDeviceVerified(
userId, deviceId, true,
);
// Tell the user we verified everything!
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: _t("Verified key"),
description: (
<div>
<p>
{
_t("The signing key you provided matches the signing key you received " +
"from %(userId)s's device %(deviceId)s. Device marked as verified.",
{userId: userId, deviceId: deviceId})
}
</p>
</div>
),
hasCancelButton: false,
});
return success();
} else {
const fprint = device.getFingerprint();
return reject(
_t('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!',
{deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})
);
}
}
}
return reject(this.getUsage());
}),
};
/* eslint-enable babel/no-invalid-this */
// helpful aliases
var aliases = {
j: "join"
const aliases = {
j: "join",
};
module.exports = {
@@ -304,13 +374,13 @@ module.exports = {
// IRC-style commands
input = input.replace(/\s+$/, "");
if (input[0] === "/" && input[1] !== "/") {
var bits = input.match(/^(\S+?)( +((.|\n)*))?$/);
var cmd, args;
const bits = input.match(/^(\S+?)( +((.|\n)*))?$/);
let cmd;
let args;
if (bits) {
cmd = bits[1].substring(1).toLowerCase();
args = bits[3];
}
else {
} else {
cmd = input;
}
if (cmd === "me") return null;
@@ -319,9 +389,8 @@ module.exports = {
}
if (commands[cmd]) {
return commands[cmd].run(roomId, args);
}
else {
return reject("Unrecognised command: " + input);
} else {
return reject(_t("Unrecognised command:") + ' ' + input);
}
}
return null; // not a command
@@ -329,12 +398,12 @@ module.exports = {
getCommandList: function() {
// Return all the commands plus /me and /markdown which aren't handled like normal commands
var cmds = Object.keys(commands).sort().map(function(cmdKey) {
const cmds = Object.keys(commands).sort().map(function(cmdKey) {
return commands[cmdKey];
});
cmds.push(new Command("me", "<action>", function() {}));
cmds.push(new Command("markdown", "<on|off>", function() {}));
return cmds;
}
},
};

View File

@@ -16,6 +16,8 @@ limitations under the License.
var MatrixClientPeg = require("./MatrixClientPeg");
var CallHandler = require("./CallHandler");
import { _t } from './languageHandler';
import * as Roles from './Roles';
function textForMemberEvent(ev) {
// XXX: SYJS-16 "sender is sometimes null for join messages"
@@ -23,95 +25,103 @@ function textForMemberEvent(ev) {
var targetName = ev.target ? ev.target.name : ev.getStateKey();
var ConferenceHandler = CallHandler.getConferenceHandler();
var reason = ev.getContent().reason ? (
" Reason: " + ev.getContent().reason
_t('Reason') + ': ' + ev.getContent().reason
) : "";
switch (ev.getContent().membership) {
case 'invite':
var threePidContent = ev.getContent().third_party_invite;
if (threePidContent) {
if (threePidContent.display_name) {
return targetName + " accepted the invitation for " +
threePidContent.display_name + ".";
return _t('%(targetName)s accepted the invitation for %(displayName)s.', {targetName: targetName, displayName: threePidContent.display_name});
} else {
return targetName + " accepted an invitation.";
return _t('%(targetName)s accepted an invitation.', {targetName: targetName});
}
}
else {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return senderName + " requested a VoIP conference";
return _t('%(senderName)s requested a VoIP conference.', {senderName: senderName});
}
else {
return senderName + " invited " + targetName + ".";
return _t('%(senderName)s invited %(targetName)s.', {senderName: senderName, targetName: targetName});
}
}
case 'ban':
return senderName + " banned " + targetName + "." + reason;
return _t(
'%(senderName)s banned %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
case 'join':
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
return ev.getSender() + " changed their display name from " +
ev.getPrevContent().displayname + " to " +
ev.getContent().displayname;
return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname, displayName: ev.getContent().displayname});
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
return ev.getSender() + " set their display name to " + ev.getContent().displayname;
return _t('%(senderName)s set their display name to %(displayName)s.', {senderName: ev.getSender(), displayName: ev.getContent().displayname});
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) {
return ev.getSender() + " removed their display name (" + ev.getPrevContent().displayname + ")";
return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname});
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) {
return senderName + " removed their profile picture";
return _t('%(senderName)s removed their profile picture.', {senderName: senderName});
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) {
return senderName + " changed their profile picture";
return _t('%(senderName)s changed their profile picture.', {senderName: senderName});
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
return senderName + " set a profile picture";
return _t('%(senderName)s set a profile picture.', {senderName: senderName});
} else {
// hacky hack for https://github.com/vector-im/vector-web/issues/2020
return senderName + " rejoined the room.";
// suppress null rejoins
return '';
}
} else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return "VoIP conference started";
return _t('VoIP conference started.');
}
else {
return targetName + " joined the room.";
return _t('%(targetName)s joined the room.', {targetName: targetName});
}
}
case 'leave':
if (ev.getSender() === ev.getStateKey()) {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return "VoIP conference finished";
return _t('VoIP conference finished.');
}
else if (ev.getPrevContent().membership === "invite") {
return targetName + " rejected the invitation.";
return _t('%(targetName)s rejected the invitation.', {targetName: targetName});
}
else {
return targetName + " left the room.";
return _t('%(targetName)s left the room.', {targetName: targetName});
}
}
else if (ev.getPrevContent().membership === "ban") {
return senderName + " unbanned " + targetName + ".";
return _t('%(senderName)s unbanned %(targetName)s.', {senderName: senderName, targetName: targetName});
}
else if (ev.getPrevContent().membership === "join") {
return senderName + " kicked " + targetName + "." + reason;
return _t(
'%(senderName)s kicked %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else if (ev.getPrevContent().membership === "invite") {
return senderName + " withdrew " + targetName + "'s invitation." + reason;
return _t(
'%(senderName)s withdrew %(targetName)s\'s invitation.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else {
return targetName + " left the room.";
return _t('%(targetName)s left the room.', {targetName: targetName});
}
}
}
function textForTopicEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return senderDisplayName + ' changed the topic to "' + ev.getContent().topic + '"';
return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {senderDisplayName: senderDisplayName, topic: ev.getContent().topic});
}
function textForRoomNameEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return senderDisplayName + ' changed the room name to "' + ev.getContent().name + '"';
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName});
}
return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name});
}
function textForMessageEvent(ev) {
@@ -120,66 +130,111 @@ function textForMessageEvent(ev) {
if (ev.getContent().msgtype === "m.emote") {
message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === "m.image") {
message = senderDisplayName + " sent an image.";
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName: senderDisplayName});
}
return message;
}
function textForCallAnswerEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone";
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
return senderName + " answered the call." + supported;
var senderName = event.sender ? event.sender.name : _t('Someone');
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s answered the call.', {senderName: senderName}) + ' ' + supported;
}
function textForCallHangupEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone";
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
return senderName + " ended the call." + supported;
var senderName = event.sender ? event.sender.name : _t('Someone');
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s ended the call.', {senderName: senderName}) + ' ' + supported;
}
function textForCallInviteEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone";
var senderName = event.sender ? event.sender.name : _t('Someone');
// FIXME: Find a better way to determine this from the event?
var type = "voice";
if (event.getContent().offer && event.getContent().offer.sdp &&
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
type = "video";
}
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
return senderName + " placed a " + type + " call." + supported;
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s placed a %(callType)s call.', {senderName: senderName, callType: type}) + ' ' + supported;
}
function textForThreePidInviteEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender();
return senderName + " sent an invitation to " + event.getContent().display_name +
" to join the room.";
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {senderName: senderName, targetDisplayName: event.getContent().display_name});
}
function textForHistoryVisibilityEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender();
var vis = event.getContent().history_visibility;
var text = senderName + " made future room history visible to ";
// XXX: This i18n just isn't going to work for languages with different sentence structure.
var text = _t('%(senderName)s made future room history visible to', {senderName: senderName}) + ' ';
if (vis === "invited") {
text += "all room members, from the point they are invited.";
text += _t('all room members, from the point they are invited') + '.';
}
else if (vis === "joined") {
text += "all room members, from the point they joined.";
text += _t('all room members, from the point they joined') + '.';
}
else if (vis === "shared") {
text += "all room members.";
text += _t('all room members') + '.';
}
else if (vis === "world_readable") {
text += "anyone.";
text += _t('anyone') + '.';
}
else {
text += " unknown (" + vis + ")";
text += ' ' + _t('unknown') + ' (' + vis + ').';
}
return text;
}
function textForEncryptionEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender();
return senderName + " turned on end-to-end encryption (algorithm " + event.getContent().algorithm + ")";
return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {senderName: senderName, algorithm: event.getContent().algorithm});
}
// Currently will only display a change if a user's power level is changed
function textForPowerEvent(event) {
const senderName = event.sender ? event.sender.name : event.getSender();
if (!event.getPrevContent() || !event.getPrevContent().users) {
return '';
}
const userDefault = event.getContent().users_default || 0;
// Construct set of userIds
let users = [];
Object.keys(event.getContent().users).forEach(
(userId) => {
if (users.indexOf(userId) === -1) users.push(userId);
}
);
Object.keys(event.getPrevContent().users).forEach(
(userId) => {
if (users.indexOf(userId) === -1) users.push(userId);
}
);
let diff = [];
// XXX: This is also surely broken for i18n
users.forEach((userId) => {
// Previous power level
const from = event.getPrevContent().users[userId];
// Current power level
const to = event.getContent().users[userId];
if (to !== from) {
diff.push(
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
userId: userId,
fromPowerLevel: Roles.textualPowerLevel(from, userDefault),
toPowerLevel: Roles.textualPowerLevel(to, userDefault)
})
);
}
});
if (!diff.length) {
return '';
}
return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', {
senderName: senderName,
powerLevelDiffText: diff.join(", ")
});
}
var handlers = {
@@ -193,6 +248,7 @@ var handlers = {
'm.room.third_party_invite': textForThreePidInviteEvent,
'm.room.history_visibility': textForHistoryVisibilityEvent,
'm.room.encryption': textForEncryptionEvent,
'm.room.power_levels': textForPowerEvent,
};
module.exports = {

View File

@@ -22,7 +22,7 @@ let isDialogOpen = false;
const onAction = function(payload) {
if (payload.action === 'unknown_device_error' && !isDialogOpen) {
var UnknownDeviceDialog = sdk.getComponent("dialogs.UnknownDeviceDialog");
const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
isDialogOpen = true;
Modal.createDialog(UnknownDeviceDialog, {
devices: payload.err.devices,
@@ -33,17 +33,17 @@ const onAction = function(payload) {
// https://github.com/vector-im/riot-web/issues/3148
console.log('UnknownDeviceDialog closed with '+r);
},
}, "mx_Dialog_unknownDevice");
}, 'mx_Dialog_unknownDevice');
}
}
};
let ref = null;
export function startListening () {
export function startListening() {
ref = dis.register(onAction);
}
export function stopListening () {
export function stopListening() {
if (ref) {
dis.unregister(ref);
ref = null;

View File

@@ -25,7 +25,9 @@ module.exports = {
eventTriggersUnreadCount: function(ev) {
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
return false;
} else if (ev.getType() == "m.room.member") {
} else if (ev.getType() == 'm.room.member') {
return false;
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
return false;
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
return false;

View File

@@ -32,7 +32,7 @@ class UserActivity {
start() {
document.onmousedown = this._onUserActivity.bind(this);
document.onmousemove = this._onUserActivity.bind(this);
document.onkeypress = this._onUserActivity.bind(this);
document.onkeydown = this._onUserActivity.bind(this);
// can't use document.scroll here because that's only the document
// itself being scrolled. Need to use addEventListener's useCapture.
// also this needs to be the wheel event, not scroll, as scroll is
@@ -50,7 +50,7 @@ class UserActivity {
stop() {
document.onmousedown = undefined;
document.onmousemove = undefined;
document.onkeypress = undefined;
document.onkeydown = undefined;
window.removeEventListener('wheel', this._onUserActivity.bind(this),
{ passive: true, capture: true });
}

View File

@@ -14,26 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var q = require("q");
var MatrixClientPeg = require("./MatrixClientPeg");
var Notifier = require("./Notifier");
import q from 'q';
import MatrixClientPeg from './MatrixClientPeg';
import Notifier from './Notifier';
import { _t } from './languageHandler';
/*
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
*/
module.exports = {
export default {
LABS_FEATURES: [
{
name: 'New Composer & Autocomplete',
name: "-",
id: 'rich_text_editor',
default: false,
},
],
// horrible but it works. The locality makes this somewhat more palatable.
doTranslations: function() {
this.LABS_FEATURES[0].name = _t("New Composer & Autocomplete");
},
loadProfileInfo: function() {
var cli = MatrixClientPeg.get();
const cli = MatrixClientPeg.get();
return cli.getProfileInfo(cli.credentials.userId);
},
@@ -44,7 +49,7 @@ module.exports = {
loadThreePids: function() {
if (MatrixClientPeg.get().isGuest()) {
return q({
threepids: []
threepids: [],
}); // guests can't poke 3pid endpoint
}
return MatrixClientPeg.get().getThreePids();
@@ -73,19 +78,19 @@ module.exports = {
Notifier.setAudioEnabled(enable);
},
changePassword: function(old_password, new_password) {
var cli = MatrixClientPeg.get();
changePassword: function(oldPassword, newPassword) {
const cli = MatrixClientPeg.get();
var authDict = {
const authDict = {
type: 'm.login.password',
user: cli.credentials.userId,
password: old_password
password: oldPassword,
};
return cli.setPassword(authDict, new_password);
return cli.setPassword(authDict, newPassword);
},
/**
/*
* Returns the email pusher (pusher of type 'email') for a given
* email address. Email pushers all have the same app ID, so since
* pushers are unique over (app ID, pushkey), there will be at most
@@ -95,8 +100,8 @@ module.exports = {
if (pushers === undefined) {
return undefined;
}
for (var i = 0; i < pushers.length; ++i) {
if (pushers[i].kind == 'email' && pushers[i].pushkey == address) {
for (let i = 0; i < pushers.length; ++i) {
if (pushers[i].kind === 'email' && pushers[i].pushkey === address) {
return pushers[i];
}
}
@@ -110,7 +115,7 @@ module.exports = {
addEmailPusher: function(address, data) {
return MatrixClientPeg.get().setPusher({
kind: 'email',
app_id: "m.email",
app_id: 'm.email',
pushkey: address,
app_display_name: 'Email Notifications',
device_display_name: address,
@@ -121,46 +126,46 @@ module.exports = {
},
getUrlPreviewsDisabled: function() {
var event = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
const event = MatrixClientPeg.get().getAccountData('org.matrix.preview_urls');
return (event && event.getContent().disable);
},
setUrlPreviewsDisabled: function(disabled) {
// FIXME: handle errors
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", {
disable: disabled
return MatrixClientPeg.get().setAccountData('org.matrix.preview_urls', {
disable: disabled,
});
},
getSyncedSettings: function() {
var event = MatrixClientPeg.get().getAccountData("im.vector.web.settings");
const event = MatrixClientPeg.get().getAccountData('im.vector.web.settings');
return event ? event.getContent() : {};
},
getSyncedSetting: function(type, defaultValue = null) {
var settings = this.getSyncedSettings();
return settings.hasOwnProperty(type) ? settings[type] : null;
const settings = this.getSyncedSettings();
return settings.hasOwnProperty(type) ? settings[type] : defaultValue;
},
setSyncedSetting: function(type, value) {
var settings = this.getSyncedSettings();
const settings = this.getSyncedSettings();
settings[type] = value;
// FIXME: handle errors
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", settings);
return MatrixClientPeg.get().setAccountData('im.vector.web.settings', settings);
},
getLocalSettings: function() {
var localSettingsString = localStorage.getItem('mx_local_settings') || '{}';
const localSettingsString = localStorage.getItem('mx_local_settings') || '{}';
return JSON.parse(localSettingsString);
},
getLocalSetting: function(type, defaultValue = null) {
var settings = this.getLocalSettings();
return settings.hasOwnProperty(type) ? settings[type] : null;
const settings = this.getLocalSettings();
return settings.hasOwnProperty(type) ? settings[type] : defaultValue;
},
setLocalSetting: function(type, value) {
var settings = this.getLocalSettings();
const settings = this.getLocalSettings();
settings[type] = value;
// FIXME: handle errors
localStorage.setItem('mx_local_settings', JSON.stringify(settings));
@@ -171,8 +176,8 @@ module.exports = {
if (MatrixClientPeg.get().isGuest()) return false;
if (localStorage.getItem(`mx_labs_feature_${feature}`) === null) {
for (var i = 0; i < this.LABS_FEATURES.length; i++) {
var f = this.LABS_FEATURES[i];
for (let i = 0; i < this.LABS_FEATURES.length; i++) {
const f = this.LABS_FEATURES[i];
if (f.id === feature) {
return f.default;
}
@@ -183,5 +188,5 @@ module.exports = {
setFeatureEnabled: function(feature: string, enabled: boolean) {
localStorage.setItem(`mx_labs_feature_${feature}`, enabled);
}
},
};

View File

@@ -15,6 +15,7 @@ limitations under the License.
*/
var MatrixClientPeg = require("./MatrixClientPeg");
import { _t } from './languageHandler';
module.exports = {
usersTypingApartFromMe: function(room) {
@@ -56,18 +57,18 @@ module.exports = {
if (whoIsTyping.length == 0) {
return '';
} else if (whoIsTyping.length == 1) {
return whoIsTyping[0].name + ' is typing';
return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name});
}
const names = whoIsTyping.map(function(m) {
return m.name;
});
if (othersCount) {
const other = ' other' + (othersCount > 1 ? 's' : '');
return names.slice(0, limit - 1).join(', ') + ' and ' +
othersCount + other + ' are typing';
if (othersCount==1) {
return _t('%(names)s and one other are typing', {names: names.slice(0, limit - 1).join(', ')});
} else if (othersCount>1) {
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
} else {
const lastPerson = names.pop();
return names.join(', ') + ' and ' + lastPerson + ' are typing';
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
}
}
};

View File

@@ -15,6 +15,7 @@ limitations under the License.
*/
var React = require("react");
import { _t } from '../../../languageHandler';
var sdk = require('../../../index');
var MatrixClientPeg = require("../../../MatrixClientPeg");
@@ -78,33 +79,33 @@ module.exports = React.createClass({
_renderDeviceInfo: function() {
var device = this.state.device;
if (!device) {
return (<i>unknown device</i>);
return (<i>{ _t('unknown device') }</i>);
}
var verificationStatus = (<b>NOT verified</b>);
var verificationStatus = (<b>{ _t('NOT verified') }</b>);
if (device.isBlocked()) {
verificationStatus = (<b>Blacklisted</b>);
verificationStatus = (<b>{ _t('Blacklisted') }</b>);
} else if (device.isVerified()) {
verificationStatus = "verified";
verificationStatus = _t('verified');
}
return (
<table>
<tbody>
<tr>
<td>Name</td>
<td>{ _t('Name') }</td>
<td>{ device.getDisplayName() }</td>
</tr>
<tr>
<td>Device ID</td>
<td>{ _t('Device ID') }</td>
<td><code>{ device.deviceId }</code></td>
</tr>
<tr>
<td>Verification</td>
<td>{ _t('Verification') }</td>
<td>{ verificationStatus }</td>
</tr>
<tr>
<td>Ed25519 fingerprint</td>
<td>{ _t('Ed25519 fingerprint') }</td>
<td><code>{device.getFingerprint()}</code></td>
</tr>
</tbody>
@@ -119,32 +120,32 @@ module.exports = React.createClass({
<table>
<tbody>
<tr>
<td>User ID</td>
<td>{ _t('User ID') }</td>
<td>{ event.getSender() }</td>
</tr>
<tr>
<td>Curve25519 identity key</td>
<td><code>{ event.getSenderKey() || <i>none</i> }</code></td>
<td>{ _t('Curve25519 identity key') }</td>
<td><code>{ event.getSenderKey() || <i>{ _t('none') }</i> }</code></td>
</tr>
<tr>
<td>Claimed Ed25519 fingerprint key</td>
<td><code>{ event.getKeysClaimed().ed25519 || <i>none</i> }</code></td>
<td>{ _t('Claimed Ed25519 fingerprint key') }</td>
<td><code>{ event.getKeysClaimed().ed25519 || <i>{ _t('none') }</i> }</code></td>
</tr>
<tr>
<td>Algorithm</td>
<td>{ event.getWireContent().algorithm || <i>unencrypted</i> }</td>
<td>{ _t('Algorithm') }</td>
<td>{ event.getWireContent().algorithm || <i>{ _t('unencrypted') }</i> }</td>
</tr>
{
event.getContent().msgtype === 'm.bad.encrypted' ? (
<tr>
<td>Decryption error</td>
<td>{ _t('Decryption error') }</td>
<td>{ event.getContent().body }</td>
</tr>
) : null
}
<tr>
<td>Session ID</td>
<td><code>{ event.getWireContent().session_id || <i>none</i> }</code></td>
<td>{ _t('Session ID') }</td>
<td><code>{ event.getWireContent().session_id || <i>{ _t('none') }</i> }</code></td>
</tr>
</tbody>
</table>
@@ -166,18 +167,18 @@ module.exports = React.createClass({
return (
<div className="mx_EncryptedEventDialog" onKeyDown={ this.onKeyDown }>
<div className="mx_Dialog_title">
End-to-end encryption information
{ _t('End-to-end encryption information') }
</div>
<div className="mx_Dialog_content">
<h4>Event information</h4>
<h4>{ _t('Event information') }</h4>
{this._renderEventInfo()}
<h4>Sender device information</h4>
<h4>{ _t('Sender device information') }</h4>
{this._renderDeviceInfo()}
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={ this.props.onFinished } autoFocus={ true }>
OK
{ _t('OK') }
</button>
{buttons}
</div>

View File

@@ -16,6 +16,7 @@ limitations under the License.
import FileSaver from 'file-saver';
import React from 'react';
import { _t } from '../../../languageHandler';
import * as Matrix from 'matrix-js-sdk';
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
@@ -52,11 +53,11 @@ export default React.createClass({
const passphrase = this.refs.passphrase1.value;
if (passphrase !== this.refs.passphrase2.value) {
this.setState({errStr: 'Passphrases must match'});
this.setState({errStr: _t('Passphrases must match')});
return false;
}
if (!passphrase) {
this.setState({errStr: 'Passphrase must not be empty'});
this.setState({errStr: _t('Passphrase must not be empty')});
return false;
}
@@ -109,24 +110,28 @@ export default React.createClass({
return (
<BaseDialog className='mx_exportE2eKeysDialog'
onFinished={this.props.onFinished}
title="Export room keys"
title={_t("Export room keys")}
>
<form onSubmit={this._onPassphraseFormSubmit}>
<div className="mx_Dialog_content">
<p>
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.
{ _t(
'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.',
) }
</p>
<p>
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.
{ _t(
'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.',
) }
</p>
<div className='error'>
{this.state.errStr}
@@ -135,7 +140,7 @@ export default React.createClass({
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase1'>
Enter passphrase
{_t("Enter passphrase")}
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
@@ -148,7 +153,7 @@ export default React.createClass({
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase2'>
Confirm passphrase
{_t("Confirm passphrase")}
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
@@ -161,11 +166,11 @@ export default React.createClass({
</div>
</div>
<div className='mx_Dialog_buttons'>
<input className='mx_Dialog_primary' type='submit' value='Export'
<input className='mx_Dialog_primary' type='submit' value={_t('Export')}
disabled={disableForm}
/>
<button onClick={this._onCancelClick} disabled={disableForm}>
Cancel
{_t("Cancel")}
</button>
</div>
</form>

View File

@@ -19,6 +19,7 @@ import React from 'react';
import * as Matrix from 'matrix-js-sdk';
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
function readFileAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
@@ -112,20 +113,23 @@ export default React.createClass({
return (
<BaseDialog className='mx_importE2eKeysDialog'
onFinished={this.props.onFinished}
title="Import room keys"
title={_t("Import room keys")}
>
<form onSubmit={this._onFormSubmit}>
<div className="mx_Dialog_content">
<p>
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.
{ _t(
'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.',
) }
</p>
<p>
The export file will be protected with a passphrase.
You should enter the passphrase here, to decrypt the
file.
{ _t(
'The export file will be protected with a passphrase. ' +
'You should enter the passphrase here, to decrypt the file.',
) }
</p>
<div className='error'>
{this.state.errStr}
@@ -134,7 +138,7 @@ export default React.createClass({
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='importFile'>
File to import
{_t("File to import")}
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
@@ -147,7 +151,7 @@ export default React.createClass({
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase'>
Enter passphrase
{_t("Enter passphrase")}
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
@@ -160,11 +164,11 @@ export default React.createClass({
</div>
</div>
<div className='mx_Dialog_buttons'>
<input className='mx_Dialog_primary' type='submit' value='Import'
<input className='mx_Dialog_primary' type='submit' value={_t('Import')}
disabled={!this.state.enableSubmit || disableForm}
/>
<button onClick={this._onCancelClick} disabled={disableForm}>
Cancel
{_t("Cancel")}
</button>
</div>
</form>

View File

@@ -1,3 +1,20 @@
/*
Copyright 2016 Aviral Dasgupta
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import type {Completion, SelectionRange} from './Autocompleter';

View File

@@ -1,3 +1,19 @@
/*
Copyright 2016 Aviral Dasgupta
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.
*/
// @flow
import type {Component} from 'react';

View File

@@ -1,8 +1,27 @@
/*
Copyright 2016 Aviral Dasgupta
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import Fuse from 'fuse.js';
import {TextualCompletion} from './Components';
// Warning: Since the description string will be translated in _t(result.description), all these strings below must be in i18n/strings/en_EN.json file
const COMMANDS = [
{
command: '/me',
@@ -43,10 +62,10 @@ const COMMANDS = [
command: '/ddg',
args: '<query>',
description: 'Searches DuckDuckGo for results',
}
},
];
let COMMAND_RE = /(^\/\w*)/g;
const COMMAND_RE = /(^\/\w*)/g;
let instance = null;
@@ -60,15 +79,15 @@ export default class CommandProvider extends AutocompleteProvider {
async getCompletions(query: string, selection: {start: number, end: number}) {
let completions = [];
let {command, range} = this.getCurrentCommand(query, selection);
const {command, range} = this.getCurrentCommand(query, selection);
if (command) {
completions = this.fuse.search(command[0]).map(result => {
completions = this.fuse.search(command[0]).map((result) => {
return {
completion: result.command + ' ',
component: (<TextualCompletion
title={result.command}
subtitle={result.args}
description={result.description}
description={ _t(result.description) }
/>),
range,
};
@@ -78,12 +97,11 @@ export default class CommandProvider extends AutocompleteProvider {
}
getName() {
return '*️⃣ Commands';
return '*️⃣ ' + _t('Commands');
}
static getInstance(): CommandProvider {
if (instance == null)
{instance = new CommandProvider();}
if (instance === null) instance = new CommandProvider();
return instance;
}

View File

@@ -1,5 +1,20 @@
/*
Copyright 2016 Aviral Dasgupta
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
/* These were earlier stateless functional components but had to be converted

View File

@@ -1,4 +1,22 @@
/*
Copyright 2016 Aviral Dasgupta
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import 'whatwg-fetch';
@@ -75,7 +93,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
}
getName() {
return '🔍 Results from DuckDuckGo';
return '🔍 ' + _t('Results from DuckDuckGo');
}
static getInstance(): DuckDuckGoProvider {

View File

@@ -1,4 +1,22 @@
/*
Copyright 2016 Aviral Dasgupta
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {emojioneList, shortnameToImage, shortnameToUnicode} from 'emojione';
import Fuse from 'fuse.js';
@@ -14,7 +32,7 @@ let instance = null;
export default class EmojiProvider extends AutocompleteProvider {
constructor() {
super(EMOJI_REGEX);
this.fuse = new Fuse(EMOJI_SHORTNAMES);
this.fuse = new Fuse(EMOJI_SHORTNAMES, {});
}
async getCompletions(query: string, selection: SelectionRange) {
@@ -39,7 +57,7 @@ export default class EmojiProvider extends AutocompleteProvider {
}
getName() {
return '😃 Emoji';
return '😃 ' + _t('Emoji');
}
static getInstance() {

View File

@@ -1,4 +1,22 @@
/*
Copyright 2016 Aviral Dasgupta
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import MatrixClientPeg from '../MatrixClientPeg';
import Fuse from 'fuse.js';
@@ -50,7 +68,7 @@ export default class RoomProvider extends AutocompleteProvider {
}
getName() {
return '💬 Rooms';
return '💬 ' + _t('Rooms');
}
static getInstance() {

View File

@@ -1,6 +1,23 @@
/*
Copyright 2016 Aviral Dasgupta
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import Q from 'q';
import Fuse from 'fuse.js';
import {PillCompletion} from './Components';
import sdk from '../index';
@@ -51,7 +68,7 @@ export default class UserProvider extends AutocompleteProvider {
}
getName() {
return '👥 Users';
return '👥 ' + _t('Users');
}
setUserList(users) {

View File

@@ -1,253 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* THIS FILE IS AUTO-GENERATED
* You can edit it you like, but your changes will be overwritten,
* so you'd just be trying to swim upstream like a salmon.
* You are not a salmon.
*
* To update it, run:
* ./reskindex.js -h header
*/
module.exports.components = {};
import structures$ContextualMenu from './components/structures/ContextualMenu';
structures$ContextualMenu && (module.exports.components['structures.ContextualMenu'] = structures$ContextualMenu);
import structures$CreateRoom from './components/structures/CreateRoom';
structures$CreateRoom && (module.exports.components['structures.CreateRoom'] = structures$CreateRoom);
import structures$FilePanel from './components/structures/FilePanel';
structures$FilePanel && (module.exports.components['structures.FilePanel'] = structures$FilePanel);
import structures$InteractiveAuth from './components/structures/InteractiveAuth';
structures$InteractiveAuth && (module.exports.components['structures.InteractiveAuth'] = structures$InteractiveAuth);
import structures$LoggedInView from './components/structures/LoggedInView';
structures$LoggedInView && (module.exports.components['structures.LoggedInView'] = structures$LoggedInView);
import structures$MatrixChat from './components/structures/MatrixChat';
structures$MatrixChat && (module.exports.components['structures.MatrixChat'] = structures$MatrixChat);
import structures$MessagePanel from './components/structures/MessagePanel';
structures$MessagePanel && (module.exports.components['structures.MessagePanel'] = structures$MessagePanel);
import structures$NotificationPanel from './components/structures/NotificationPanel';
structures$NotificationPanel && (module.exports.components['structures.NotificationPanel'] = structures$NotificationPanel);
import structures$RoomStatusBar from './components/structures/RoomStatusBar';
structures$RoomStatusBar && (module.exports.components['structures.RoomStatusBar'] = structures$RoomStatusBar);
import structures$RoomView from './components/structures/RoomView';
structures$RoomView && (module.exports.components['structures.RoomView'] = structures$RoomView);
import structures$ScrollPanel from './components/structures/ScrollPanel';
structures$ScrollPanel && (module.exports.components['structures.ScrollPanel'] = structures$ScrollPanel);
import structures$TimelinePanel from './components/structures/TimelinePanel';
structures$TimelinePanel && (module.exports.components['structures.TimelinePanel'] = structures$TimelinePanel);
import structures$UploadBar from './components/structures/UploadBar';
structures$UploadBar && (module.exports.components['structures.UploadBar'] = structures$UploadBar);
import structures$UserSettings from './components/structures/UserSettings';
structures$UserSettings && (module.exports.components['structures.UserSettings'] = structures$UserSettings);
import structures$login$ForgotPassword from './components/structures/login/ForgotPassword';
structures$login$ForgotPassword && (module.exports.components['structures.login.ForgotPassword'] = structures$login$ForgotPassword);
import structures$login$Login from './components/structures/login/Login';
structures$login$Login && (module.exports.components['structures.login.Login'] = structures$login$Login);
import structures$login$PostRegistration from './components/structures/login/PostRegistration';
structures$login$PostRegistration && (module.exports.components['structures.login.PostRegistration'] = structures$login$PostRegistration);
import structures$login$Registration from './components/structures/login/Registration';
structures$login$Registration && (module.exports.components['structures.login.Registration'] = structures$login$Registration);
import views$avatars$BaseAvatar from './components/views/avatars/BaseAvatar';
views$avatars$BaseAvatar && (module.exports.components['views.avatars.BaseAvatar'] = views$avatars$BaseAvatar);
import views$avatars$MemberAvatar from './components/views/avatars/MemberAvatar';
views$avatars$MemberAvatar && (module.exports.components['views.avatars.MemberAvatar'] = views$avatars$MemberAvatar);
import views$avatars$RoomAvatar from './components/views/avatars/RoomAvatar';
views$avatars$RoomAvatar && (module.exports.components['views.avatars.RoomAvatar'] = views$avatars$RoomAvatar);
import views$create_room$CreateRoomButton from './components/views/create_room/CreateRoomButton';
views$create_room$CreateRoomButton && (module.exports.components['views.create_room.CreateRoomButton'] = views$create_room$CreateRoomButton);
import views$create_room$Presets from './components/views/create_room/Presets';
views$create_room$Presets && (module.exports.components['views.create_room.Presets'] = views$create_room$Presets);
import views$create_room$RoomAlias from './components/views/create_room/RoomAlias';
views$create_room$RoomAlias && (module.exports.components['views.create_room.RoomAlias'] = views$create_room$RoomAlias);
import views$dialogs$BaseDialog from './components/views/dialogs/BaseDialog';
views$dialogs$BaseDialog && (module.exports.components['views.dialogs.BaseDialog'] = views$dialogs$BaseDialog);
import views$dialogs$ChatCreateOrReuseDialog from './components/views/dialogs/ChatCreateOrReuseDialog';
views$dialogs$ChatCreateOrReuseDialog && (module.exports.components['views.dialogs.ChatCreateOrReuseDialog'] = views$dialogs$ChatCreateOrReuseDialog);
import views$dialogs$ChatInviteDialog from './components/views/dialogs/ChatInviteDialog';
views$dialogs$ChatInviteDialog && (module.exports.components['views.dialogs.ChatInviteDialog'] = views$dialogs$ChatInviteDialog);
import views$dialogs$ConfirmRedactDialog from './components/views/dialogs/ConfirmRedactDialog';
views$dialogs$ConfirmRedactDialog && (module.exports.components['views.dialogs.ConfirmRedactDialog'] = views$dialogs$ConfirmRedactDialog);
import views$dialogs$ConfirmUserActionDialog from './components/views/dialogs/ConfirmUserActionDialog';
views$dialogs$ConfirmUserActionDialog && (module.exports.components['views.dialogs.ConfirmUserActionDialog'] = views$dialogs$ConfirmUserActionDialog);
import views$dialogs$DeactivateAccountDialog from './components/views/dialogs/DeactivateAccountDialog';
views$dialogs$DeactivateAccountDialog && (module.exports.components['views.dialogs.DeactivateAccountDialog'] = views$dialogs$DeactivateAccountDialog);
import views$dialogs$ErrorDialog from './components/views/dialogs/ErrorDialog';
views$dialogs$ErrorDialog && (module.exports.components['views.dialogs.ErrorDialog'] = views$dialogs$ErrorDialog);
import views$dialogs$InteractiveAuthDialog from './components/views/dialogs/InteractiveAuthDialog';
views$dialogs$InteractiveAuthDialog && (module.exports.components['views.dialogs.InteractiveAuthDialog'] = views$dialogs$InteractiveAuthDialog);
import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedToRegisterDialog';
views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog);
import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog';
views$dialogs$QuestionDialog && (module.exports.components['views.dialogs.QuestionDialog'] = views$dialogs$QuestionDialog);
import views$dialogs$SessionRestoreErrorDialog from './components/views/dialogs/SessionRestoreErrorDialog';
views$dialogs$SessionRestoreErrorDialog && (module.exports.components['views.dialogs.SessionRestoreErrorDialog'] = views$dialogs$SessionRestoreErrorDialog);
import views$dialogs$SetDisplayNameDialog from './components/views/dialogs/SetDisplayNameDialog';
views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog);
import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog';
views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog);
import views$dialogs$UnknownDeviceDialog from './components/views/dialogs/UnknownDeviceDialog';
views$dialogs$UnknownDeviceDialog && (module.exports.components['views.dialogs.UnknownDeviceDialog'] = views$dialogs$UnknownDeviceDialog);
import views$elements$AccessibleButton from './components/views/elements/AccessibleButton';
views$elements$AccessibleButton && (module.exports.components['views.elements.AccessibleButton'] = views$elements$AccessibleButton);
import views$elements$AddressSelector from './components/views/elements/AddressSelector';
views$elements$AddressSelector && (module.exports.components['views.elements.AddressSelector'] = views$elements$AddressSelector);
import views$elements$AddressTile from './components/views/elements/AddressTile';
views$elements$AddressTile && (module.exports.components['views.elements.AddressTile'] = views$elements$AddressTile);
import views$elements$DeviceVerifyButtons from './components/views/elements/DeviceVerifyButtons';
views$elements$DeviceVerifyButtons && (module.exports.components['views.elements.DeviceVerifyButtons'] = views$elements$DeviceVerifyButtons);
import views$elements$DirectorySearchBox from './components/views/elements/DirectorySearchBox';
views$elements$DirectorySearchBox && (module.exports.components['views.elements.DirectorySearchBox'] = views$elements$DirectorySearchBox);
import views$elements$Dropdown from './components/views/elements/Dropdown';
views$elements$Dropdown && (module.exports.components['views.elements.Dropdown'] = views$elements$Dropdown);
import views$elements$EditableText from './components/views/elements/EditableText';
views$elements$EditableText && (module.exports.components['views.elements.EditableText'] = views$elements$EditableText);
import views$elements$EditableTextContainer from './components/views/elements/EditableTextContainer';
views$elements$EditableTextContainer && (module.exports.components['views.elements.EditableTextContainer'] = views$elements$EditableTextContainer);
import views$elements$EmojiText from './components/views/elements/EmojiText';
views$elements$EmojiText && (module.exports.components['views.elements.EmojiText'] = views$elements$EmojiText);
import views$elements$MemberEventListSummary from './components/views/elements/MemberEventListSummary';
views$elements$MemberEventListSummary && (module.exports.components['views.elements.MemberEventListSummary'] = views$elements$MemberEventListSummary);
import views$elements$PowerSelector from './components/views/elements/PowerSelector';
views$elements$PowerSelector && (module.exports.components['views.elements.PowerSelector'] = views$elements$PowerSelector);
import views$elements$ProgressBar from './components/views/elements/ProgressBar';
views$elements$ProgressBar && (module.exports.components['views.elements.ProgressBar'] = views$elements$ProgressBar);
import views$elements$TintableSvg from './components/views/elements/TintableSvg';
views$elements$TintableSvg && (module.exports.components['views.elements.TintableSvg'] = views$elements$TintableSvg);
import views$elements$TruncatedList from './components/views/elements/TruncatedList';
views$elements$TruncatedList && (module.exports.components['views.elements.TruncatedList'] = views$elements$TruncatedList);
import views$elements$UserSelector from './components/views/elements/UserSelector';
views$elements$UserSelector && (module.exports.components['views.elements.UserSelector'] = views$elements$UserSelector);
import views$login$CaptchaForm from './components/views/login/CaptchaForm';
views$login$CaptchaForm && (module.exports.components['views.login.CaptchaForm'] = views$login$CaptchaForm);
import views$login$CasLogin from './components/views/login/CasLogin';
views$login$CasLogin && (module.exports.components['views.login.CasLogin'] = views$login$CasLogin);
import views$login$CountryDropdown from './components/views/login/CountryDropdown';
views$login$CountryDropdown && (module.exports.components['views.login.CountryDropdown'] = views$login$CountryDropdown);
import views$login$CustomServerDialog from './components/views/login/CustomServerDialog';
views$login$CustomServerDialog && (module.exports.components['views.login.CustomServerDialog'] = views$login$CustomServerDialog);
import views$login$InteractiveAuthEntryComponents from './components/views/login/InteractiveAuthEntryComponents';
views$login$InteractiveAuthEntryComponents && (module.exports.components['views.login.InteractiveAuthEntryComponents'] = views$login$InteractiveAuthEntryComponents);
import views$login$LoginFooter from './components/views/login/LoginFooter';
views$login$LoginFooter && (module.exports.components['views.login.LoginFooter'] = views$login$LoginFooter);
import views$login$LoginHeader from './components/views/login/LoginHeader';
views$login$LoginHeader && (module.exports.components['views.login.LoginHeader'] = views$login$LoginHeader);
import views$login$PasswordLogin from './components/views/login/PasswordLogin';
views$login$PasswordLogin && (module.exports.components['views.login.PasswordLogin'] = views$login$PasswordLogin);
import views$login$RegistrationForm from './components/views/login/RegistrationForm';
views$login$RegistrationForm && (module.exports.components['views.login.RegistrationForm'] = views$login$RegistrationForm);
import views$login$ServerConfig from './components/views/login/ServerConfig';
views$login$ServerConfig && (module.exports.components['views.login.ServerConfig'] = views$login$ServerConfig);
import views$messages$MAudioBody from './components/views/messages/MAudioBody';
views$messages$MAudioBody && (module.exports.components['views.messages.MAudioBody'] = views$messages$MAudioBody);
import views$messages$MFileBody from './components/views/messages/MFileBody';
views$messages$MFileBody && (module.exports.components['views.messages.MFileBody'] = views$messages$MFileBody);
import views$messages$MImageBody from './components/views/messages/MImageBody';
views$messages$MImageBody && (module.exports.components['views.messages.MImageBody'] = views$messages$MImageBody);
import views$messages$MVideoBody from './components/views/messages/MVideoBody';
views$messages$MVideoBody && (module.exports.components['views.messages.MVideoBody'] = views$messages$MVideoBody);
import views$messages$MessageEvent from './components/views/messages/MessageEvent';
views$messages$MessageEvent && (module.exports.components['views.messages.MessageEvent'] = views$messages$MessageEvent);
import views$messages$SenderProfile from './components/views/messages/SenderProfile';
views$messages$SenderProfile && (module.exports.components['views.messages.SenderProfile'] = views$messages$SenderProfile);
import views$messages$TextualBody from './components/views/messages/TextualBody';
views$messages$TextualBody && (module.exports.components['views.messages.TextualBody'] = views$messages$TextualBody);
import views$messages$TextualEvent from './components/views/messages/TextualEvent';
views$messages$TextualEvent && (module.exports.components['views.messages.TextualEvent'] = views$messages$TextualEvent);
import views$messages$UnknownBody from './components/views/messages/UnknownBody';
views$messages$UnknownBody && (module.exports.components['views.messages.UnknownBody'] = views$messages$UnknownBody);
import views$room_settings$AliasSettings from './components/views/room_settings/AliasSettings';
views$room_settings$AliasSettings && (module.exports.components['views.room_settings.AliasSettings'] = views$room_settings$AliasSettings);
import views$room_settings$ColorSettings from './components/views/room_settings/ColorSettings';
views$room_settings$ColorSettings && (module.exports.components['views.room_settings.ColorSettings'] = views$room_settings$ColorSettings);
import views$room_settings$UrlPreviewSettings from './components/views/room_settings/UrlPreviewSettings';
views$room_settings$UrlPreviewSettings && (module.exports.components['views.room_settings.UrlPreviewSettings'] = views$room_settings$UrlPreviewSettings);
import views$rooms$Autocomplete from './components/views/rooms/Autocomplete';
views$rooms$Autocomplete && (module.exports.components['views.rooms.Autocomplete'] = views$rooms$Autocomplete);
import views$rooms$AuxPanel from './components/views/rooms/AuxPanel';
views$rooms$AuxPanel && (module.exports.components['views.rooms.AuxPanel'] = views$rooms$AuxPanel);
import views$rooms$EntityTile from './components/views/rooms/EntityTile';
views$rooms$EntityTile && (module.exports.components['views.rooms.EntityTile'] = views$rooms$EntityTile);
import views$rooms$EventTile from './components/views/rooms/EventTile';
views$rooms$EventTile && (module.exports.components['views.rooms.EventTile'] = views$rooms$EventTile);
import views$rooms$LinkPreviewWidget from './components/views/rooms/LinkPreviewWidget';
views$rooms$LinkPreviewWidget && (module.exports.components['views.rooms.LinkPreviewWidget'] = views$rooms$LinkPreviewWidget);
import views$rooms$MemberDeviceInfo from './components/views/rooms/MemberDeviceInfo';
views$rooms$MemberDeviceInfo && (module.exports.components['views.rooms.MemberDeviceInfo'] = views$rooms$MemberDeviceInfo);
import views$rooms$MemberInfo from './components/views/rooms/MemberInfo';
views$rooms$MemberInfo && (module.exports.components['views.rooms.MemberInfo'] = views$rooms$MemberInfo);
import views$rooms$MemberList from './components/views/rooms/MemberList';
views$rooms$MemberList && (module.exports.components['views.rooms.MemberList'] = views$rooms$MemberList);
import views$rooms$MemberTile from './components/views/rooms/MemberTile';
views$rooms$MemberTile && (module.exports.components['views.rooms.MemberTile'] = views$rooms$MemberTile);
import views$rooms$MessageComposer from './components/views/rooms/MessageComposer';
views$rooms$MessageComposer && (module.exports.components['views.rooms.MessageComposer'] = views$rooms$MessageComposer);
import views$rooms$MessageComposerInput from './components/views/rooms/MessageComposerInput';
views$rooms$MessageComposerInput && (module.exports.components['views.rooms.MessageComposerInput'] = views$rooms$MessageComposerInput);
import views$rooms$MessageComposerInputOld from './components/views/rooms/MessageComposerInputOld';
views$rooms$MessageComposerInputOld && (module.exports.components['views.rooms.MessageComposerInputOld'] = views$rooms$MessageComposerInputOld);
import views$rooms$PresenceLabel from './components/views/rooms/PresenceLabel';
views$rooms$PresenceLabel && (module.exports.components['views.rooms.PresenceLabel'] = views$rooms$PresenceLabel);
import views$rooms$ReadReceiptMarker from './components/views/rooms/ReadReceiptMarker';
views$rooms$ReadReceiptMarker && (module.exports.components['views.rooms.ReadReceiptMarker'] = views$rooms$ReadReceiptMarker);
import views$rooms$RoomHeader from './components/views/rooms/RoomHeader';
views$rooms$RoomHeader && (module.exports.components['views.rooms.RoomHeader'] = views$rooms$RoomHeader);
import views$rooms$RoomList from './components/views/rooms/RoomList';
views$rooms$RoomList && (module.exports.components['views.rooms.RoomList'] = views$rooms$RoomList);
import views$rooms$RoomNameEditor from './components/views/rooms/RoomNameEditor';
views$rooms$RoomNameEditor && (module.exports.components['views.rooms.RoomNameEditor'] = views$rooms$RoomNameEditor);
import views$rooms$RoomPreviewBar from './components/views/rooms/RoomPreviewBar';
views$rooms$RoomPreviewBar && (module.exports.components['views.rooms.RoomPreviewBar'] = views$rooms$RoomPreviewBar);
import views$rooms$RoomSettings from './components/views/rooms/RoomSettings';
views$rooms$RoomSettings && (module.exports.components['views.rooms.RoomSettings'] = views$rooms$RoomSettings);
import views$rooms$RoomTile from './components/views/rooms/RoomTile';
views$rooms$RoomTile && (module.exports.components['views.rooms.RoomTile'] = views$rooms$RoomTile);
import views$rooms$RoomTopicEditor from './components/views/rooms/RoomTopicEditor';
views$rooms$RoomTopicEditor && (module.exports.components['views.rooms.RoomTopicEditor'] = views$rooms$RoomTopicEditor);
import views$rooms$SearchResultTile from './components/views/rooms/SearchResultTile';
views$rooms$SearchResultTile && (module.exports.components['views.rooms.SearchResultTile'] = views$rooms$SearchResultTile);
import views$rooms$SearchableEntityList from './components/views/rooms/SearchableEntityList';
views$rooms$SearchableEntityList && (module.exports.components['views.rooms.SearchableEntityList'] = views$rooms$SearchableEntityList);
import views$rooms$SimpleRoomHeader from './components/views/rooms/SimpleRoomHeader';
views$rooms$SimpleRoomHeader && (module.exports.components['views.rooms.SimpleRoomHeader'] = views$rooms$SimpleRoomHeader);
import views$rooms$TabCompleteBar from './components/views/rooms/TabCompleteBar';
views$rooms$TabCompleteBar && (module.exports.components['views.rooms.TabCompleteBar'] = views$rooms$TabCompleteBar);
import views$rooms$TopUnreadMessagesBar from './components/views/rooms/TopUnreadMessagesBar';
views$rooms$TopUnreadMessagesBar && (module.exports.components['views.rooms.TopUnreadMessagesBar'] = views$rooms$TopUnreadMessagesBar);
import views$rooms$UserTile from './components/views/rooms/UserTile';
views$rooms$UserTile && (module.exports.components['views.rooms.UserTile'] = views$rooms$UserTile);
import views$settings$AddPhoneNumber from './components/views/settings/AddPhoneNumber';
views$settings$AddPhoneNumber && (module.exports.components['views.settings.AddPhoneNumber'] = views$settings$AddPhoneNumber);
import views$settings$ChangeAvatar from './components/views/settings/ChangeAvatar';
views$settings$ChangeAvatar && (module.exports.components['views.settings.ChangeAvatar'] = views$settings$ChangeAvatar);
import views$settings$ChangeDisplayName from './components/views/settings/ChangeDisplayName';
views$settings$ChangeDisplayName && (module.exports.components['views.settings.ChangeDisplayName'] = views$settings$ChangeDisplayName);
import views$settings$ChangePassword from './components/views/settings/ChangePassword';
views$settings$ChangePassword && (module.exports.components['views.settings.ChangePassword'] = views$settings$ChangePassword);
import views$settings$DevicesPanel from './components/views/settings/DevicesPanel';
views$settings$DevicesPanel && (module.exports.components['views.settings.DevicesPanel'] = views$settings$DevicesPanel);
import views$settings$DevicesPanelEntry from './components/views/settings/DevicesPanelEntry';
views$settings$DevicesPanelEntry && (module.exports.components['views.settings.DevicesPanelEntry'] = views$settings$DevicesPanelEntry);
import views$settings$EnableNotificationsButton from './components/views/settings/EnableNotificationsButton';
views$settings$EnableNotificationsButton && (module.exports.components['views.settings.EnableNotificationsButton'] = views$settings$EnableNotificationsButton);
import views$voip$CallView from './components/views/voip/CallView';
views$voip$CallView && (module.exports.components['views.voip.CallView'] = views$voip$CallView);
import views$voip$IncomingCallBox from './components/views/voip/IncomingCallBox';
views$voip$IncomingCallBox && (module.exports.components['views.voip.IncomingCallBox'] = views$voip$IncomingCallBox);
import views$voip$VideoFeed from './components/views/voip/VideoFeed';
views$voip$VideoFeed && (module.exports.components['views.voip.VideoFeed'] = views$voip$VideoFeed);
import views$voip$VideoView from './components/views/voip/VideoView';
views$voip$VideoView && (module.exports.components['views.voip.VideoView'] = views$voip$VideoView);

View File

@@ -16,15 +16,15 @@ limitations under the License.
'use strict';
var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg");
var PresetValues = {
import React from 'react';
import { _t } from '../../languageHandler';
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
const PresetValues = {
PrivateChat: "private_chat",
PublicChat: "public_chat",
Custom: "custom",
};
var q = require('q');
var sdk = require('../../index');
module.exports = React.createClass({
displayName: 'CreateRoom',
@@ -231,7 +231,7 @@ module.exports = React.createClass({
if (curr_phase == this.phases.ERROR) {
error_box = (
<div className="mx_Error">
An error occured: {this.state.error_string}
{_t('An error occurred: %(error_string)s', {error_string: this.state.error_string})}
</div>
);
}
@@ -246,29 +246,29 @@ module.exports = React.createClass({
return (
<div className="mx_CreateRoom">
<SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/>
<SimpleRoomHeader title={_t("Create Room")} collapsedRhs={ this.props.collapsedRhs }/>
<div className="mx_CreateRoom_body">
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder="Name"/> <br />
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder="Topic"/> <br />
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder={_t('Name')}/> <br />
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder={_t('Topic')}/> <br />
<RoomAlias ref="alias" alias={this.state.alias} homeserver={ domain } onChange={this.onAliasChanged}/> <br />
<UserSelector ref="user_selector" selected_users={this.state.invited_users} onChange={this.onInviteChanged}/> <br />
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
<div>
<label>
<input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/>
Make this room private
{_t('Make this room private')}
</label>
</div>
<div>
<label>
<input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/>
Share message history with new users
{_t('Share message history with new users')}
</label>
</div>
<div className="mx_CreateRoom_encrypt">
<label>
<input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/>
Encrypt room
{_t('Encrypt room')}
</label>
</div>
<div>

View File

@@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var ReactDOM = require("react-dom");
import React from 'react';
var Matrix = require("matrix-js-sdk");
var sdk = require('../../index');
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
import Matrix from 'matrix-js-sdk';
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
import { _t, _tJsx } from '../../languageHandler';
/*
* Component which shows the filtered file using a TimelinePanel
@@ -59,6 +58,8 @@ var FilePanel = React.createClass({
var client = MatrixClientPeg.get();
var room = client.getRoom(roomId);
this.noRoom = !room;
if (room) {
var filter = new Matrix.Filter(client.credentials.userId);
filter.setDefinition(
@@ -82,13 +83,24 @@ var FilePanel = React.createClass({
console.error("Failed to get or create file panel filter", error);
}
);
}
else {
} else {
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
}
},
render: function() {
if (MatrixClientPeg.get().isGuest()) {
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
<div className="mx_RoomView_empty">
{_tJsx("You must <a>register</a> to use this functionality", /<a>(.*?)<\/a>/, (sub) => <a href="#/register" key="sub">{sub}</a>)}
</div>
</div>;
} else if (this.noRoom) {
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
<div className="mx_RoomView_empty">{_t("You must join the room to see its files")}</div>
</div>;
}
// wrap a TimelinePanel with the jump-to-event bits turned off.
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
var Loader = sdk.getComponent("elements.Spinner");
@@ -105,7 +117,7 @@ var FilePanel = React.createClass({
showUrlPreview = { false }
tileShape="file_grid"
opacity={ this.props.opacity }
empty="There are no visible files in this room"
empty={_t('There are no visible files in this room')}
/>
);
}

View File

@@ -19,8 +19,6 @@ const InteractiveAuth = Matrix.InteractiveAuth;
import React from 'react';
import sdk from '../../index';
import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
export default React.createClass({

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,11 +18,15 @@ limitations under the License.
import * as Matrix from 'matrix-js-sdk';
import React from 'react';
import UserSettingsStore from '../../UserSettingsStore';
import KeyCode from '../../KeyCode';
import Notifier from '../../Notifier';
import PageTypes from '../../PageTypes';
import CallMediaHandler from '../../CallMediaHandler';
import sdk from '../../index';
import dis from '../../dispatcher';
import sessionStore from '../../stores/SessionStore';
import MatrixClientPeg from '../../MatrixClientPeg';
/**
* This is what our MatrixChat shows when we are logged in. The precise view is
@@ -38,10 +43,13 @@ export default React.createClass({
propTypes: {
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
page_type: React.PropTypes.string.isRequired,
onRoomIdResolved: React.PropTypes.func,
onRoomCreated: React.PropTypes.func,
onUserSettingsClose: React.PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
teamToken: React.PropTypes.string,
// and lots and lots of other stuff.
@@ -62,6 +70,13 @@ export default React.createClass({
};
},
getInitialState: function() {
return {
// use compact timeline view
useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'),
};
},
componentWillMount: function() {
// stash the MatrixClient in case we log out before we are unmounted
this._matrixClient = this.props.matrixClient;
@@ -70,11 +85,35 @@ export default React.createClass({
// RoomView.getScrollState()
this._scrollStateMap = {};
CallMediaHandler.loadDevices();
document.addEventListener('keydown', this._onKeyDown);
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
this._matrixClient.on("accountData", this.onAccountData);
},
componentWillUnmount: function() {
document.removeEventListener('keydown', this._onKeyDown);
this._matrixClient.removeListener("accountData", this.onAccountData);
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
},
// Child components assume that the client peg will not be null, so give them some
// sort of assurance here by only allowing a re-render if the client is truthy.
//
// This is required because `LoggedInView` maintains its own state and if this state
// updates after the client peg has been made null (during logout), then it will
// attempt to re-render and the children will throw errors.
shouldComponentUpdate: function() {
return Boolean(MatrixClientPeg.get());
},
getScrollStateForRoom: function(roomId) {
@@ -88,6 +127,20 @@ export default React.createClass({
return this.refs.roomView.canResetTimeline();
},
_setStateFromSessionStore() {
this.setState({
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
});
},
onAccountData: function(event) {
if (event.getType() === "im.vector.web.settings") {
this.setState({
useCompactLayout: event.getContent().useCompactLayout,
});
}
},
_onKeyDown: function(ev) {
/*
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
@@ -108,7 +161,7 @@ export default React.createClass({
switch (ev.keyCode) {
case KeyCode.UP:
case KeyCode.DOWN:
if (ev.altKey) {
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
var action = ev.keyCode == KeyCode.UP ?
'view_prev_room' : 'view_next_room';
dis.dispatch({action: action});
@@ -118,13 +171,15 @@ export default React.createClass({
case KeyCode.PAGE_UP:
case KeyCode.PAGE_DOWN:
this._onScrollKeyPressed(ev);
handled = true;
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this._onScrollKeyPressed(ev);
handled = true;
}
break;
case KeyCode.HOME:
case KeyCode.END:
if (ev.ctrlKey) {
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this._onScrollKeyPressed(ev);
handled = true;
}
@@ -142,42 +197,44 @@ export default React.createClass({
if (this.refs.roomView) {
this.refs.roomView.handleScrollKey(ev);
}
else if (this.refs.roomDirectory) {
this.refs.roomDirectory.handleScrollKey(ev);
}
},
render: function() {
var LeftPanel = sdk.getComponent('structures.LeftPanel');
var RightPanel = sdk.getComponent('structures.RightPanel');
var RoomView = sdk.getComponent('structures.RoomView');
var UserSettings = sdk.getComponent('structures.UserSettings');
var CreateRoom = sdk.getComponent('structures.CreateRoom');
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
var HomePage = sdk.getComponent('structures.HomePage');
var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
var GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
var NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const LeftPanel = sdk.getComponent('structures.LeftPanel');
const RightPanel = sdk.getComponent('structures.RightPanel');
const RoomView = sdk.getComponent('structures.RoomView');
const UserSettings = sdk.getComponent('structures.UserSettings');
const CreateRoom = sdk.getComponent('structures.CreateRoom');
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
const HomePage = sdk.getComponent('structures.HomePage');
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
var page_element;
var right_panel = '';
let page_element;
let right_panel = '';
switch (this.props.page_type) {
case PageTypes.RoomView:
page_element = <RoomView
ref='roomView'
roomAddress={this.props.currentRoomAlias || this.props.currentRoomId}
autoJoin={this.props.autoJoin}
onRoomIdResolved={this.props.onRoomIdResolved}
onRegistered={this.props.onRegistered}
eventId={this.props.initialEventId}
thirdPartyInvite={this.props.thirdPartyInvite}
oobData={this.props.roomOobData}
highlightedEventId={this.props.highlightedEventId}
eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomAlias || this.props.currentRoomId}
key={this.props.currentRoomId || 'roomview'}
opacity={this.props.middleOpacity}
collapsedRhs={this.props.collapse_rhs}
ConferenceHandler={this.props.ConferenceHandler}
scrollStateMap={this._scrollStateMap}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.sideOpacity} />;
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.rightOpacity} />;
break;
case PageTypes.UserSettings:
@@ -189,7 +246,7 @@ export default React.createClass({
referralBaseUrl={this.props.config.referralBaseUrl}
teamToken={this.props.teamToken}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
break;
case PageTypes.CreateRoom:
@@ -197,42 +254,46 @@ export default React.createClass({
onRoomCreated={this.props.onRoomCreated}
collapsedRhs={this.props.collapse_rhs}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
break;
case PageTypes.RoomDirectory:
page_element = <RoomDirectory
collapsedRhs={this.props.collapse_rhs}
ref="roomDirectory"
config={this.props.config.roomDirectory}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
break;
case PageTypes.HomePage:
// If team server config is present, pass the teamServerURL. props.teamToken
// must also be set for the team page to be displayed, otherwise the
// welcomePageUrl is used (which might be undefined).
const teamServerUrl = this.props.config.teamServerConfig ?
this.props.config.teamServerConfig.teamServerURL : null;
page_element = <HomePage
collapsedRhs={this.props.collapse_rhs}
teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
teamServerUrl={teamServerUrl}
teamToken={this.props.teamToken}
/>
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>
homePageUrl={this.props.config.welcomePageUrl}
/>;
break;
case PageTypes.UserView:
page_element = null; // deliberately null for now
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.sideOpacity} />;
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
break;
}
const isGuest = this.props.matrixClient.isGuest();
var topBar;
if (this.props.hasNewVersion) {
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
releaseNotes={this.props.newVersionReleaseNotes}
/>;
}
else if (this.props.matrixClient.isGuest()) {
topBar = <GuestWarningBar />;
}
else if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
} else if (this.state.userHasGeneratedPassword) {
topBar = <PasswordNagBar />;
} else if (!isGuest && Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
topBar = <MatrixToolbar />;
}
@@ -240,6 +301,9 @@ export default React.createClass({
if (topBar) {
bodyClasses += ' mx_MatrixChat_toolbarShowing';
}
if (this.state.useCompactLayout) {
bodyClasses += ' mx_MatrixChat_useCompactLayout';
}
return (
<div className='mx_MatrixChat_wrapper'>
@@ -248,8 +312,7 @@ export default React.createClass({
<LeftPanel
selectedRoom={this.props.currentRoomId}
collapsed={this.props.collapse_lhs || false}
opacity={this.props.sideOpacity}
teamToken={this.props.teamToken}
opacity={this.props.leftOpacity}
/>
<main className='mx_MatrixChat_middlePanel'>
{page_element}

File diff suppressed because it is too large Load Diff

View File

@@ -84,6 +84,15 @@ module.exports = React.createClass({
// shape parameter to be passed to EventTiles
tileShape: React.PropTypes.string,
// show twelve hour timestamps
isTwelveHour: React.PropTypes.bool,
// show timestamps always
alwaysShowTimestamps: React.PropTypes.bool,
// hide redacted events as per old behaviour
hideRedactions: React.PropTypes.bool,
},
componentWillMount: function() {
@@ -230,8 +239,8 @@ module.exports = React.createClass({
},
_getEventTiles: function() {
var EventTile = sdk.getComponent('rooms.EventTile');
var DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventTile = sdk.getComponent('rooms.EventTile');
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
this.eventNodes = {};
@@ -279,20 +288,19 @@ module.exports = React.createClass({
this.currentGhostEventId = null;
}
var isMembershipChange = (e) =>
e.getType() === 'm.room.member'
&& (!e.getPrevContent() || e.getContent().membership !== e.getPrevContent().membership);
var isMembershipChange = (e) => e.getType() === 'm.room.member';
for (i = 0; i < this.props.events.length; i++) {
var mxEv = this.props.events[i];
var wantTile = true;
var eventId = mxEv.getId();
let mxEv = this.props.events[i];
let wantTile = true;
let eventId = mxEv.getId();
let readMarkerInMels = false;
if (!EventTile.haveTileForEvent(mxEv)) {
wantTile = false;
}
var last = (i == lastShownEventIndex);
let last = (i == lastShownEventIndex);
// Wrap consecutive member events in a ListSummary, ignore if redacted
if (isMembershipChange(mxEv) &&
@@ -311,7 +319,7 @@ module.exports = React.createClass({
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1}/></li>;
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
ret.push(dateSeparator);
}
@@ -334,6 +342,9 @@ module.exports = React.createClass({
let eventTiles = summarisedEvents.map(
(e) => {
if (e.getId() === this.props.readMarkerEventId) {
readMarkerInMels = true;
}
// In order to prevent DateSeparators from appearing in the expanded form
// of MemberEventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
@@ -352,12 +363,16 @@ module.exports = React.createClass({
<MemberEventListSummary
key={key}
events={summarisedEvents}
data-scroll-token={eventId}
onToggle={this._onWidgetLoad} // Update scroll state
>
{eventTiles}
</MemberEventListSummary>
);
if (readMarkerInMels) {
ret.push(this._getReadMarkerTile(visible));
}
continue;
}
@@ -388,6 +403,8 @@ module.exports = React.createClass({
isVisibleReadMarker = visible;
}
// XXX: there should be no need for a ghost tile - we should just use a
// a dispatch (user_activity_end) to start the RM animation.
if (eventId == this.currentGhostEventId) {
// if we're showing an animation, continue to show it.
ret.push(this._getReadMarkerGhostTile());
@@ -405,8 +422,8 @@ module.exports = React.createClass({
},
_getTilesForEvent: function(prevEvent, mxEv, last) {
var EventTile = sdk.getComponent('rooms.EventTile');
var DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventTile = sdk.getComponent('rooms.EventTile');
const DateSeparator = sdk.getComponent('messages.DateSeparator');
var ret = [];
// is this a continuation of the previous message?
@@ -444,11 +461,13 @@ module.exports = React.createClass({
// do we need a date separator since the last event?
if (this._wantsDateSeparator(prevEvent, eventDate)) {
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1}/></li>;
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
ret.push(dateSeparator);
continuation = false;
}
if (mxEv.isRedacted() && this.props.hideRedactions) return ret;
var eventId = mxEv.getId();
var highlight = (eventId == this.props.highlightedEventId);
@@ -460,11 +479,10 @@ module.exports = React.createClass({
if (this.props.manageReadReceipts) {
readReceipts = this._getReadReceiptsForEvent(mxEv);
}
ret.push(
<li key={eventId}
ref={this._collectEventNode.bind(this, eventId)}
data-scroll-token={scrollToken}>
data-scroll-tokens={scrollToken}>
<EventTile mxEvent={mxEv} continuation={continuation}
isRedacted={mxEv.isRedacted()}
onWidgetLoad={this._onWidgetLoad}
@@ -474,6 +492,7 @@ module.exports = React.createClass({
checkUnmounting={this._isUnmounting}
eventSendStatus={mxEv.status}
tileShape={this.props.tileShape}
isTwelveHour={this.props.isTwelveHour}
last={last} isSelectedEvent={highlight}/>
</li>
);
@@ -607,8 +626,13 @@ module.exports = React.createClass({
var style = this.props.hidden ? { display: 'none' } : {};
style.opacity = this.props.opacity;
var className = this.props.className + " mx_fadable";
if (this.props.alwaysShowTimestamps) {
className += " mx_MessagePanel_alwaysShowTimestamps";
}
return (
<ScrollPanel ref="scrollPanel" className={ this.props.className + " mx_fadable" }
<ScrollPanel ref="scrollPanel" className={ className }
onScroll={ this.props.onScroll }
onResize={ this.onResize }
onFillRequest={ this.props.onFillRequest }

View File

@@ -16,7 +16,7 @@ limitations under the License.
var React = require('react');
var ReactDOM = require("react-dom");
import { _t } from '../../languageHandler';
var Matrix = require("matrix-js-sdk");
var sdk = require('../../index');
var MatrixClientPeg = require("../../MatrixClientPeg");
@@ -37,7 +37,6 @@ var NotificationPanel = React.createClass({
var Loader = sdk.getComponent("elements.Spinner");
var timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
if (timelineSet) {
return (
<TimelinePanel key={"NotificationPanel_" + this.props.roomId}
@@ -48,7 +47,7 @@ var NotificationPanel = React.createClass({
showUrlPreview = { false }
opacity={ this.props.opacity }
tileShape="notif"
empty="You have no visible notifications"
empty={ _t('You have no visible notifications') }
/>
);
}

View File

@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var sdk = require('../../index');
var dis = require("../../dispatcher");
var WhoIsTyping = require("../../WhoIsTyping");
var MatrixClientPeg = require("../../MatrixClientPeg");
const MemberAvatar = require("../views/avatars/MemberAvatar");
import React from 'react';
import { _t, _tJsx } from '../../languageHandler';
import sdk from '../../index';
import WhoIsTyping from '../../WhoIsTyping';
import MatrixClientPeg from '../../MatrixClientPeg';
import MemberAvatar from '../views/avatars/MemberAvatar';
const HIDE_DEBOUNCE_MS = 10000;
const STATUS_BAR_HIDDEN = 0;
@@ -175,8 +175,8 @@ module.exports = React.createClass({
<div className="mx_RoomStatusBar_scrollDownIndicator"
onClick={ this.props.onScrollToBottomClick }>
<img src="img/scrolldown.svg" width="24" height="24"
alt="Scroll to bottom of page"
title="Scroll to bottom of page"/>
alt={ _t("Scroll to bottom of page") }
title={ _t("Scroll to bottom of page") }/>
</div>
);
}
@@ -250,10 +250,10 @@ module.exports = React.createClass({
<div className="mx_RoomStatusBar_connectionLostBar">
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ "/>
<div className="mx_RoomStatusBar_connectionLostBar_title">
Connectivity to the server has been lost.
{_t('Connectivity to the server has been lost.')}
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
Sent messages will be stored until your connection has returned.
{_t('Sent messages will be stored until your connection has returned.')}
</div>
</div>
);
@@ -266,7 +266,7 @@ module.exports = React.createClass({
<TabCompleteBar tabComplete={this.props.tabComplete} />
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
<TintableSvg src="img/eol.svg" width="22" height="16"/>
Auto-complete
{_t('Auto-complete')}
</div>
</div>
</div>
@@ -281,15 +281,13 @@ module.exports = React.createClass({
{ this.props.unsentMessageError }
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
<a className="mx_RoomStatusBar_resend_link"
onClick={ this.props.onResendAllClick }>
Resend all
</a> or <a
className="mx_RoomStatusBar_resend_link"
onClick={ this.props.onCancelAllClick }>
cancel all
</a> now. You can also select individual messages to
resend or cancel.
{_tJsx("<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
[/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/],
[
(sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={ this.props.onResendAllClick }>{sub}</a>,
(sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={ this.props.onCancelAllClick }>{sub}</a>,
]
)}
</div>
</div>
);
@@ -298,8 +296,8 @@ module.exports = React.createClass({
// unread count trumps who is typing since the unread count is only
// set when you've scrolled up
if (this.props.numUnreadMessages) {
var unreadMsgs = this.props.numUnreadMessages + " new message" +
(this.props.numUnreadMessages > 1 ? "s" : "");
// MUST use var name "count" for pluralization to kick in
var unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
return (
<div className="mx_RoomStatusBar_unreadMessagesBar"
@@ -324,7 +322,7 @@ module.exports = React.createClass({
if (this.props.hasActiveCall) {
return (
<div className="mx_RoomStatusBar_callBar">
<b>Active call</b>
<b>{_t('Active call')}</b>
</div>
);
}

View File

@@ -25,7 +25,9 @@ var ReactDOM = require("react-dom");
var q = require("q");
var classNames = require("classnames");
var Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler';
var UserSettingsStore = require('../../UserSettingsStore');
var MatrixClientPeg = require("../../MatrixClientPeg");
var ContentMessages = require("../../ContentMessages");
var Modal = require("../../Modal");
@@ -43,7 +45,9 @@ import KeyCode from '../../KeyCode';
import UserProvider from '../../autocomplete/UserProvider';
const DEBUG = false;
import RoomViewStore from '../../stores/RoomViewStore';
var DEBUG = false;
if (DEBUG) {
// using bind means that we get to keep useful line numbers in the console
@@ -57,16 +61,9 @@ module.exports = React.createClass({
propTypes: {
ConferenceHandler: React.PropTypes.any,
// Either a room ID or room alias for the room to display.
// If the room is being displayed as a result of the user clicking
// on a room alias, the alias should be supplied. Otherwise, a room
// ID should be supplied.
roomAddress: React.PropTypes.string.isRequired,
// If a room alias is passed to roomAddress, a function can be
// provided here that will be called with the ID of the room
// once it has been resolved.
onRoomIdResolved: React.PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
// An object representing a third party invite to join this room
// Fields:
@@ -124,6 +121,9 @@ module.exports = React.createClass({
roomId: null,
userId: null,
roomLoading: true,
peekLoading: false,
forwardingEvent: null,
editingRoomSettings: false,
uploadingRoomSettings: false,
numUnreadMessages: 0,
@@ -173,39 +173,25 @@ module.exports = React.createClass({
},
});
if (this.props.roomAddress[0] == '#') {
// we always look up the alias from the directory server:
// we want the room that the given alias is pointing to
// right now. We may have joined that alias before but there's
// no guarantee the alias hasn't subsequently been remapped.
MatrixClientPeg.get().getRoomIdForAlias(this.props.roomAddress).done((result) => {
if (this.props.onRoomIdResolved) {
this.props.onRoomIdResolved(result.room_id);
}
var room = MatrixClientPeg.get().getRoom(result.room_id);
this.setState({
room: room,
roomId: result.room_id,
userId: MatrixClientPeg.get().credentials.userId,
roomLoading: !room,
unsentMessageError: this._getUnsentMessageError(room),
}, this._onHaveRoom);
}, (err) => {
this.setState({
roomLoading: false,
roomLoadError: err,
});
});
} else {
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
this.setState({
roomId: this.props.roomAddress,
userId: MatrixClientPeg.get().credentials.userId,
room: room,
roomLoading: !room,
unsentMessageError: this._getUnsentMessageError(room),
}, this._onHaveRoom);
// Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true);
},
_onRoomViewStoreUpdate: function(initial) {
if (this.unmounted) {
return;
}
this.setState({
roomId: RoomViewStore.getRoomId(),
roomAlias: RoomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.isRoomLoading(),
roomLoadError: RoomViewStore.getRoomLoadError(),
joining: RoomViewStore.isJoining(),
}, () => {
this._onHaveRoom();
this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId));
});
},
_onHaveRoom: function() {
@@ -223,26 +209,29 @@ module.exports = React.createClass({
// NB. We peek if we are not in the room, although if we try to peek into
// a room in which we have a member event (ie. we've left) synapse will just
// send us the same data as we get in the sync (ie. the last events we saw).
var user_is_in_room = null;
if (this.state.room) {
user_is_in_room = this.state.room.hasMembershipState(
MatrixClientPeg.get().credentials.userId, 'join'
const room = MatrixClientPeg.get().getRoom(this.state.roomId);
let isUserJoined = null;
if (room) {
isUserJoined = room.hasMembershipState(
MatrixClientPeg.get().credentials.userId, 'join',
);
this._updateAutoComplete();
this.tabComplete.loadEntries(this.state.room);
this._updateAutoComplete(room);
this.tabComplete.loadEntries(room);
}
if (!user_is_in_room && this.state.roomId) {
if (!isUserJoined && !this.state.joining && this.state.roomId) {
if (this.props.autoJoin) {
this.onJoinButtonClicked();
} else if (this.state.roomId) {
console.log("Attempting to peek into room %s", this.state.roomId);
this.setState({
peekLoading: true,
});
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
this.setState({
room: room,
roomLoading: false,
peekLoading: false,
});
this._onRoomLoaded(room);
}, (err) => {
@@ -252,16 +241,19 @@ module.exports = React.createClass({
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
// This is fine: the room just isn't peekable (we assume).
this.setState({
roomLoading: false,
peekLoading: false,
});
} else {
throw err;
}
}).done();
}
} else if (user_is_in_room) {
} else if (isUserJoined) {
MatrixClientPeg.get().stopPeeking();
this._onRoomLoaded(this.state.room);
this.setState({
unsentMessageError: this._getUnsentMessageError(room),
});
this._onRoomLoaded(room);
}
},
@@ -274,6 +266,7 @@ module.exports = React.createClass({
this._updateConfCallNotification();
window.addEventListener('beforeunload', this.onPageUnload);
window.addEventListener('resize', this.onResize);
this.onResize();
@@ -297,10 +290,6 @@ module.exports = React.createClass({
},
componentWillReceiveProps: function(newProps) {
if (newProps.roomAddress != this.props.roomAddress) {
throw new Error("changing room on a RoomView is not supported");
}
if (newProps.eventId != this.props.eventId) {
// when we change focussed event id, hide the search results.
this.setState({searchResults: null});
@@ -356,10 +345,16 @@ module.exports = React.createClass({
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
}
window.removeEventListener('beforeunload', this.onPageUnload);
window.removeEventListener('resize', this.onResize);
document.removeEventListener("keydown", this.onKeyDown);
// Remove RoomStore listener
if (this._roomStoreToken) {
this._roomStoreToken.remove();
}
// cancel any pending calls to the rate_limited_funcs
this._updateRoomMembers.cancelPendingCall();
@@ -368,6 +363,17 @@ module.exports = React.createClass({
// Tinter.tint(); // reset colourscheme
},
onPageUnload(event) {
if (ContentMessages.getCurrentUploads().length > 0) {
return event.returnValue =
_t("You seem to be uploading files, are you sure you want to quit?");
} else if (this._getCallForRoom() && this.state.callState !== 'ended') {
return event.returnValue =
_t("You seem to be in a call, are you sure you want to quit?");
}
},
onKeyDown: function(ev) {
let handled = false;
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
@@ -447,6 +453,11 @@ module.exports = React.createClass({
showApps: payload.show ? true : false,
});
break;
case 'forward_event':
this.setState({
forwardingEvent: payload.content,
});
break;
}
},
@@ -514,7 +525,7 @@ module.exports = React.createClass({
this._updatePreviewUrlVisibility(room);
},
_warnAboutEncryption: function (room) {
_warnAboutEncryption: function(room) {
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
return;
}
@@ -525,14 +536,14 @@ module.exports = React.createClass({
if (!userHasUsedEncryption) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning!",
title: _t("Warning!"),
hasCancelButton: false,
description: (
<div>
<p>End-to-end encryption is in beta and may not be reliable.</p>
<p>You should <b>not</b> yet trust it to secure data.</p>
<p>Devices will <b>not</b> yet be able to decrypt history from before they joined the room.</p>
<p>Encrypted messages will not be visible on clients that do not yet implement encryption.</p>
<p>{ _t("End-to-end encryption is in beta and may not be reliable") }.</p>
<p>{ _t("You should not yet trust it to secure data") }.</p>
<p>{ _t("Devices will not yet be able to decrypt history from before they joined the room") }.</p>
<p>{ _t("Encrypted messages will not be visible on clients that do not yet implement encryption") }.</p>
</div>
),
});
@@ -595,20 +606,14 @@ module.exports = React.createClass({
},
onRoom: function(room) {
// This event is fired when the room is 'stored' by the JS SDK, which
// means it's now a fully-fledged room object ready to be used, so
// set it in our state and start using it (ie. init the timeline)
// This will happen if we start off viewing a room we're not joined,
// then join it whilst RoomView is looking at that room.
if (!this.state.room && room.roomId == this._joiningRoomId) {
this._joiningRoomId = undefined;
this.setState({
room: room,
joining: false,
});
this._onRoomLoaded(room);
if (!room || room.roomId !== this.state.roomId) {
return;
}
this.setState({
room: room,
}, () => {
this._onRoomLoaded(room);
});
},
updateTint: function() {
@@ -674,7 +679,7 @@ module.exports = React.createClass({
// refresh the tab complete list
this.tabComplete.loadEntries(this.state.room);
this._updateAutoComplete();
this._updateAutoComplete(this.state.room);
// if we are now a member of the room, where we were not before, that
// means we have finished joining a room we were previously peeking
@@ -691,10 +696,6 @@ module.exports = React.createClass({
// compatability workaround, let's not bother.
Rooms.setDMRoom(this.state.room.roomId, me.events.member.getSender()).done();
}
this.setState({
joining: false
});
}
}, 500),
@@ -703,10 +704,10 @@ module.exports = React.createClass({
if (!unsentMessages.length) return "";
for (const event of unsentMessages) {
if (!event.error || event.error.name !== "UnknownDeviceError") {
return "Some of your messages have not been sent.";
return _t("Some of your messages have not been sent.");
}
}
return "Message not sent due to unknown devices being present";
return _t("Message not sent due to unknown devices being present");
},
_getUnsentMessages: function(room) {
@@ -769,41 +770,62 @@ module.exports = React.createClass({
},
onJoinButtonClicked: function(ev) {
var self = this;
const cli = MatrixClientPeg.get();
var cli = MatrixClientPeg.get();
var display_name_promise = q();
// if this is the first room we're joining, check the user has a display name
// and if they don't, prompt them to set one.
// NB. This unfortunately does not re-use the ChangeDisplayName component because
// it doesn't behave quite as desired here (we want an input field here rather than
// content-editable, and we want a default).
if (cli.getRooms().filter((r) => {
return r.hasMembershipState(cli.credentials.userId, "join");
})) {
display_name_promise = cli.getProfileInfo(cli.credentials.userId).then((result) => {
if (!result.displayname) {
var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog');
var dialog_defer = q.defer();
Modal.createDialog(SetDisplayNameDialog, {
currentDisplayName: result.displayname,
onFinished: (submitted, newDisplayName) => {
if (submitted) {
cli.setDisplayName(newDisplayName).done(() => {
dialog_defer.resolve();
});
}
else {
dialog_defer.reject();
}
}
});
return dialog_defer.promise;
}
// If the user is a ROU, allow them to transition to a PWLU
if (cli && cli.isGuest()) {
// Join this room once the user has registered and logged in
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'join_room',
opts: { inviteSignUrl: signUrl },
},
});
// Don't peek whilst registering otherwise getPendingEventList complains
// Do this by indicating our intention to join
dis.dispatch({
action: 'will_join',
});
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
const close = Modal.createDialog(SetMxIdDialog, {
homeserverUrl: cli.getHomeserverUrl(),
onFinished: (submitted, credentials) => {
if (submitted) {
this.props.onRegistered(credentials);
} else {
dis.dispatch({
action: 'cancel_after_sync_prepared',
});
dis.dispatch({
action: 'cancel_join',
});
}
},
onDifferentServerClicked: (ev) => {
dis.dispatch({action: 'start_registration'});
close();
},
onLoginClick: (ev) => {
dis.dispatch({action: 'start_login'});
close();
},
}).close;
return;
}
display_name_promise.then(() => {
q().then(() => {
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
action: 'join_room',
opts: { inviteSignUrl: signUrl },
});
// if this is an invite and has the 'direct' hint set, mark it as a DM room now.
if (this.state.room) {
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
@@ -815,72 +837,7 @@ module.exports = React.createClass({
}
}
}
return q();
}).then(() => {
var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined;
return MatrixClientPeg.get().joinRoom(this.props.roomAddress,
{ inviteSignUrl: sign_url } );
}).then(function(resp) {
var roomId = resp.roomId;
// It is possible that there is no Room yet if state hasn't come down
// from /sync - joinRoom will resolve when the HTTP request to join succeeds,
// NOT when it comes down /sync. If there is no room, we'll keep the
// joining flag set until we see it.
// We'll need to initialise the timeline when joining, but due to
// the above, we can't do it here: we do it in onRoom instead,
// once we have a useable room object.
var room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
// wait for the room to turn up in onRoom.
self._joiningRoomId = roomId;
} else {
// we've got a valid room, but that might also just mean that
// it was peekable (so we had one before anyway). If we are
// not yet a member of the room, we will need to wait for that
// to happen, in onRoomStateMember.
var me = MatrixClientPeg.get().credentials.userId;
self.setState({
joining: !room.hasMembershipState(me, "join"),
room: room
});
}
}).catch(function(error) {
self.setState({
joining: false,
joinError: error
});
if (!error) return;
// https://matrix.org/jira/browse/SYN-659
// Need specific error message if joining a room is refused because the user is a guest and guest access is not allowed
if (
error.errcode == 'M_GUEST_ACCESS_FORBIDDEN' ||
(
error.errcode == 'M_FORBIDDEN' &&
MatrixClientPeg.get().isGuest()
)
) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Failed to join the room",
description: "This room is private or inaccessible to guests. You may be able to join if you register."
});
} else {
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to join room",
description: msg
});
}
}).done();
this.setState({
joining: true
});
},
@@ -932,11 +889,7 @@ module.exports = React.createClass({
uploadFile: function(file) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't upload files. Please register to upload."
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
@@ -954,8 +907,8 @@ module.exports = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to upload file " + file + " " + error);
Modal.createDialog(ErrorDialog, {
title: "Failed to upload file",
description: "Server may be unavailable, overloaded, or the file too big",
title: _t('Failed to upload file'),
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")),
});
});
},
@@ -1041,8 +994,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Search failed: " + error);
Modal.createDialog(ErrorDialog, {
title: "Search failed",
description: "Server may be unavailable, overloaded, or search timed out :("
title: _t("Search failed"),
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")),
});
}).finally(function() {
self.setState({
@@ -1077,12 +1030,12 @@ module.exports = React.createClass({
if (!this.state.searchResults.next_batch) {
if (this.state.searchResults.results.length == 0) {
ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">No results</h2>
<h2 className="mx_RoomView_topMarker">{ _t("No results") }</h2>
</li>
);
} else {
ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">No more results</h2>
<h2 className="mx_RoomView_topMarker">{ _t("No more results") }</h2>
</li>
);
}
@@ -1119,10 +1072,10 @@ module.exports = React.createClass({
// it. We should tell the js sdk to go and find out about
// it. But that's not an issue currently, as synapse only
// returns results for rooms we're joined to.
var roomName = room ? room.name : "Unknown room "+roomId;
var roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
ret.push(<li key={mxEv.getId() + "-room"}>
<h1>Room: { roomName }</h1>
<h1>{ _t("Room") }: { roomName }</h1>
</li>);
lastRoomId = roomId;
}
@@ -1168,7 +1121,7 @@ module.exports = React.createClass({
});
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to save settings",
title: _t("Failed to save settings"),
description: fails.map(function(result) { return result.reason; }).join("\n"),
});
// still editing room settings
@@ -1189,7 +1142,11 @@ module.exports = React.createClass({
onCancelClick: function() {
console.log("updateTint from onCancelClick");
this.updateTint();
this.setState({editingRoomSettings: false});
this.setState({
editingRoomSettings: false,
forwardingEvent: null,
});
dis.dispatch({action: 'focus_composer'});
},
onLeaveClick: function() {
@@ -1203,11 +1160,11 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
var errCode = err.errcode || "unknown error code";
var errCode = err.errcode || _t("unknown error code");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: `Failed to forget room (${errCode})`
title: _t("Error"),
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
});
});
},
@@ -1228,8 +1185,8 @@ module.exports = React.createClass({
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to reject invite",
description: msg
title: _t("Failed to reject invite"),
description: msg,
});
self.setState({
@@ -1263,6 +1220,7 @@ module.exports = React.createClass({
// jump down to the bottom of this room, where new events are arriving
jumpToLiveTimeline: function() {
this.refs.messagePanel.jumpToLiveTimeline();
dis.dispatch({action: 'focus_composer'});
},
// jump up to wherever our read marker is
@@ -1282,12 +1240,7 @@ module.exports = React.createClass({
return;
}
var pos = this.refs.messagePanel.getReadMarkerPosition();
// we want to show the bar if the read-marker is off the top of the
// screen.
var showBar = (pos < 0);
const showBar = this.refs.messagePanel.canJumpToReadMarker();
if (this.state.showTopUnreadMessagesBar != showBar) {
this.setState({showTopUnreadMessagesBar: showBar},
this.onChildResize);
@@ -1461,70 +1414,70 @@ module.exports = React.createClass({
}
},
_updateAutoComplete: function() {
_updateAutoComplete: function(room) {
const myUserId = MatrixClientPeg.get().credentials.userId;
const members = this.state.room.getJoinedMembers().filter(function(member) {
const members = room.getJoinedMembers().filter(function(member) {
if (member.userId !== myUserId) return true;
});
UserProvider.getInstance().setUserList(members);
},
render: function() {
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
var RoomSettings = sdk.getComponent("rooms.RoomSettings");
var AuxPanel = sdk.getComponent("rooms.AuxPanel");
var SearchBar = sdk.getComponent("rooms.SearchBar");
var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
var Loader = sdk.getComponent("elements.Spinner");
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
const RoomSettings = sdk.getComponent("rooms.RoomSettings");
const AuxPanel = sdk.getComponent("rooms.AuxPanel");
const SearchBar = sdk.getComponent("rooms.SearchBar");
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
if (!this.state.room) {
if (this.state.roomLoading) {
return (
<div className="mx_RoomView">
<Loader />
</div>
);
if (this.state.roomLoading || this.state.peekLoading) {
return (
<div className="mx_RoomView">
<Loader />
</div>
);
} else {
var inviterName = undefined;
if (this.props.oobData) {
inviterName = this.props.oobData.inviterName;
}
var invitedEmail = undefined;
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
else {
var inviterName = undefined;
if (this.props.oobData) {
inviterName = this.props.oobData.inviterName;
}
var invitedEmail = undefined;
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
// We have no room object for this room, only the ID.
// We've got to this room by following a link, possibly a third party invite.
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
return (
<div className="mx_RoomView">
<RoomHeader ref="header"
room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={ this.props.collapsedRhs }
// We have no room object for this room, only the ID.
// We've got to this room by following a link, possibly a third party invite.
var room_alias = this.state.room_alias;
return (
<div className="mx_RoomView">
<RoomHeader ref="header"
room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={ this.props.collapsedRhs }
/>
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
onForgetClick={ this.onForgetClick }
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
canPreview={ false } error={ this.state.roomLoadError }
roomAlias={room_alias}
spinner={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
/>
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
onForgetClick={ this.onForgetClick }
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
canPreview={ false } error={ this.state.roomLoadError }
roomAlias={room_alias}
spinner={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
/>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div>
);
}
<div className="mx_RoomView_messagePanel"></div>
</div>
);
}
}
var myUserId = MatrixClientPeg.get().credentials.userId;
@@ -1607,17 +1560,18 @@ module.exports = React.createClass({
/>;
}
var aux = null;
let aux = null;
let hideCancel = false;
if (this.state.editingRoomSettings) {
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSettingsSaveClick} onCancelClick={this.onCancelClick} room={this.state.room} />;
}
else if (this.state.uploadingRoomSettings) {
} else if (this.state.uploadingRoomSettings) {
aux = <Loader/>;
}
else if (this.state.searching) {
} else if (this.state.forwardingEvent !== null) {
aux = <ForwardMessage onCancelClick={this.onCancelClick} currentRoomId={this.state.room.roomId} mxEvent={this.state.forwardingEvent} />;
} else if (this.state.searching) {
hideCancel = true; // has own cancel
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress } onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch}/>;
}
else if (!myMember || myMember.membership !== "join") {
} else if (!myMember || myMember.membership !== "join") {
// We do have a room object for this room, but we're not currently in it.
// We may have a 3rd party invite to it.
var inviterName = undefined;
@@ -1628,6 +1582,7 @@ module.exports = React.createClass({
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
hideCancel = true;
aux = (
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={ this.onForgetClick }
@@ -1687,7 +1642,7 @@ module.exports = React.createClass({
if (call.type === "video") {
zoomButton = (
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title="Fill screen">
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={ _t("Fill screen") }>
<TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
</div>
);
@@ -1695,14 +1650,14 @@ module.exports = React.createClass({
videoMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
<TintableSvg src={call.isLocalVideoMuted() ? "img/video-unmute.svg" : "img/video-mute.svg"}
alt={call.isLocalVideoMuted() ? "Click to unmute video" : "Click to mute video"}
alt={call.isLocalVideoMuted() ? _t("Click to unmute video") : _t("Click to mute video")}
width="31" height="27"/>
</div>;
}
voiceMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
<TintableSvg src={call.isMicrophoneMuted() ? "img/voice-unmute.svg" : "img/voice-mute.svg"}
alt={call.isMicrophoneMuted() ? "Click to unmute audio" : "Click to mute audio"}
alt={call.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")}
width="21" height="26"/>
</div>;
@@ -1738,14 +1693,13 @@ module.exports = React.createClass({
}
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
var messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()}
manageReadReceipts={true}
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
manageReadMarkers={true}
hidden={hideMessagePanel}
highlightedEventId={this.props.highlightedEventId}
highlightedEventId={this.state.forwardingEvent ? this.state.forwardingEvent.getId() : this.props.highlightedEventId}
eventId={this.props.eventId}
eventPixelOffset={this.props.eventPixelOffset}
onScroll={ this.onMessageListScroll }
@@ -1778,17 +1732,15 @@ module.exports = React.createClass({
oobData={this.props.oobData}
editing={this.state.editingRoomSettings}
saving={this.state.uploadingRoomSettings}
inRoom={myMember && myMember.membership === 'join'}
collapsedRhs={ this.props.collapsedRhs }
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
onSaveClick={this.onSettingsSaveClick}
onCancelClick={this.onCancelClick}
onForgetClick={
(myMember && myMember.membership === "leave") ? this.onForgetClick : null
}
onLeaveClick={
(myMember && myMember.membership === "join") ? this.onLeaveClick : null
} />
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null}
onLeaveClick={(myMember && myMember.membership === "join") ? this.onLeaveClick : null}
/>
{ auxPanel }
{ topUnreadMessagesBar }
{ messagePanel }

View File

@@ -46,9 +46,13 @@ if (DEBUG_SCROLL) {
* It also provides a hook which allows parents to provide more list elements
* when we get close to the start or end of the list.
*
* Each child element should have a 'data-scroll-token'. This token is used to
* serialise the scroll state, and returned as the 'trackedScrollToken'
* attribute by getScrollState().
* Each child element should have a 'data-scroll-tokens'. This string of
* comma-separated tokens may contain a single token or many, where many indicates
* that the element contains elements that have scroll tokens themselves. The first
* token in 'data-scroll-tokens' is used to serialise the scroll state, and returned
* as the 'trackedScrollToken' attribute by getScrollState().
*
* IMPORTANT: INDIVIDUAL TOKENS WITHIN 'data-scroll-tokens' MUST NOT CONTAIN COMMAS.
*
* Some notes about the implementation:
*
@@ -349,8 +353,8 @@ module.exports = React.createClass({
// Subtract height of tile as if it were unpaginated
excessHeight -= tile.clientHeight;
// The tile may not have a scroll token, so guard it
if (tile.dataset.scrollToken) {
markerScrollToken = tile.dataset.scrollToken;
if (tile.dataset.scrollTokens) {
markerScrollToken = tile.dataset.scrollTokens.split(',')[0];
}
if (tile.clientHeight > excessHeight) {
break;
@@ -419,7 +423,8 @@ module.exports = React.createClass({
* scroll. false if we are tracking a particular child.
*
* string trackedScrollToken: undefined if stuckAtBottom is true; if it is
* false, the data-scroll-token of the child which we are tracking.
* false, the first token in data-scroll-tokens of the child which we are
* tracking.
*
* number pixelOffset: undefined if stuckAtBottom is true; if it is false,
* the number of pixels the bottom of the tracked child is above the
@@ -483,21 +488,25 @@ module.exports = React.createClass({
handleScrollKey: function(ev) {
switch (ev.keyCode) {
case KeyCode.PAGE_UP:
this.scrollRelative(-1);
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this.scrollRelative(-1);
}
break;
case KeyCode.PAGE_DOWN:
this.scrollRelative(1);
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this.scrollRelative(1);
}
break;
case KeyCode.HOME:
if (ev.ctrlKey) {
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this.scrollToTop();
}
break;
case KeyCode.END:
if (ev.ctrlKey) {
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this.scrollToBottom();
}
break;
@@ -547,8 +556,10 @@ module.exports = React.createClass({
var messages = this.refs.itemlist.children;
for (var i = messages.length-1; i >= 0; --i) {
var m = messages[i];
if (!m.dataset.scrollToken) continue;
if (m.dataset.scrollToken == scrollToken) {
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
// There might only be one scroll token
if (m.dataset.scrollTokens &&
m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) {
node = m;
break;
}
@@ -564,7 +575,7 @@ module.exports = React.createClass({
var boundingRect = node.getBoundingClientRect();
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
debuglog("ScrollPanel: scrolling to token '" + node.dataset.scrollToken + "'+" +
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
pixelOffset + " (delta: "+scrollDelta+")");
if(scrollDelta != 0) {
@@ -587,12 +598,12 @@ module.exports = React.createClass({
for (var i = messages.length-1; i >= 0; --i) {
var node = messages[i];
if (!node.dataset.scrollToken) continue;
if (!node.dataset.scrollTokens) continue;
var boundingRect = node.getBoundingClientRect();
newScrollState = {
stuckAtBottom: false,
trackedScrollToken: node.dataset.scrollToken,
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
};
// If the bottom of the panel intersects the ClientRect of node, use this node
@@ -604,7 +615,7 @@ module.exports = React.createClass({
break;
}
}
// This is only false if there were no nodes with `node.dataset.scrollToken` set.
// This is only false if there were no nodes with `node.dataset.scrollTokens` set.
if (newScrollState) {
this.scrollState = newScrollState;
debuglog("ScrollPanel: saved scroll state", this.scrollState);

View File

@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -22,12 +23,14 @@ var Matrix = require("matrix-js-sdk");
var EventTimeline = Matrix.EventTimeline;
var sdk = require('../../index');
import { _t } from '../../languageHandler';
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
var ObjectUtils = require('../../ObjectUtils');
var Modal = require("../../Modal");
var UserActivity = require("../../UserActivity");
var KeyCode = require('../../KeyCode');
import UserSettingsStore from '../../UserSettingsStore';
var PAGINATE_SIZE = 20;
var INITIAL_SIZE = 20;
@@ -102,9 +105,6 @@ var TimelinePanel = React.createClass({
},
statics: {
// a map from room id to read marker event ID
roomReadMarkerMap: {},
// a map from room id to read marker event timestamp
roomReadMarkerTsMap: {},
},
@@ -121,12 +121,18 @@ var TimelinePanel = React.createClass({
getInitialState: function() {
// XXX: we could track RM per TimelineSet rather than per Room.
// but for now we just do it per room for simplicity.
let initialReadMarker = null;
if (this.props.manageReadMarkers) {
var initialReadMarker =
TimelinePanel.roomReadMarkerMap[this.props.timelineSet.room.roomId]
|| this._getCurrentReadReceipt();
const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read');
if (readmarker) {
initialReadMarker = readmarker.getContent().event_id;
} else {
initialReadMarker = this._getCurrentReadReceipt();
}
}
const syncedSettings = UserSettingsStore.getSyncedSettings();
return {
events: [],
timelineLoading: true, // track whether our room timeline is loading
@@ -166,13 +172,26 @@ var TimelinePanel = React.createClass({
backPaginating: false,
forwardPaginating: false,
// cache of matrixClient.getSyncState() (but from the 'sync' event)
clientSyncState: MatrixClientPeg.get().getSyncState(),
// should the event tiles have twelve hour times
isTwelveHour: syncedSettings.showTwelveHourTimestamps,
// always show timestamps on event tiles?
alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps,
// hide redacted events as per old behaviour
hideRedactions: syncedSettings.hideRedactions,
};
},
componentWillMount: function() {
debuglog("TimelinePanel: mounting");
this.last_rr_sent_event_id = undefined;
this.lastRRSentEventId = undefined;
this.lastRMSentEventId = undefined;
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
@@ -180,6 +199,8 @@ var TimelinePanel = React.createClass({
MatrixClientPeg.get().on("Room.redaction", this.onRoomRedaction);
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
MatrixClientPeg.get().on("Room.accountData", this.onAccountData);
MatrixClientPeg.get().on("sync", this.onSync);
this._initTimeline(this.props);
},
@@ -247,6 +268,8 @@ var TimelinePanel = React.createClass({
client.removeListener("Room.redaction", this.onRoomRedaction);
client.removeListener("Room.receipt", this.onRoomReceipt);
client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
client.removeListener("Room.accountData", this.onAccountData);
client.removeListener("sync", this.onSync);
}
},
@@ -414,6 +437,7 @@ var TimelinePanel = React.createClass({
} else if(lastEv && this.getReadMarkerPosition() === 0) {
// we know we're stuckAtBottom, so we can advance the RM
// immediately, to save a later render cycle
this._setReadMarker(lastEv.getId(), lastEv.getTs(), true);
updatedState.readMarkerVisible = false;
updatedState.readMarkerEventId = lastEv.getId();
@@ -466,6 +490,25 @@ var TimelinePanel = React.createClass({
this._reloadEvents();
},
onAccountData: function(ev, room) {
if (this.unmounted) return;
// ignore events for other rooms
if (room !== this.props.timelineSet.room) return;
if (ev.getType() !== "m.fully_read") return;
// XXX: roomReadMarkerTsMap not updated here so it is now inconsistent. Replace
// this mechanism of determining where the RM is relative to the view-port with
// one supported by the server (the client needs more than an event ID).
this.setState({
readMarkerEventId: ev.getContent().event_id,
}, this.props.onReadMarkerUpdated);
},
onSync: function(state, prevState, data) {
this.setState({clientSyncState: state});
},
sendReadReceipt: function() {
if (!this.refs.messagePanel) return;
@@ -473,11 +516,14 @@ var TimelinePanel = React.createClass({
// This happens on user_activity_end which is delayed, and it's
// very possible have logged out within that timeframe, so check
// we still have a client.
if (!MatrixClientPeg.get()) return;
const cli = MatrixClientPeg.get();
// if no client or client is guest don't send RR or RM
if (!cli || cli.isGuest()) return;
var currentReadUpToEventId = this._getCurrentReadReceipt(true);
var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);
let shouldSendRR = true;
const currentRREventId = this._getCurrentReadReceipt(true);
const currentRREventIndex = this._indexForEventId(currentRREventId);
// We want to avoid sending out read receipts when we are looking at
// events in the past which are before the latest RR.
//
@@ -491,26 +537,60 @@ var TimelinePanel = React.createClass({
// RRs) - but that is a bit of a niche case. It will sort itself out when
// the user eventually hits the live timeline.
//
if (currentReadUpToEventId && currentReadUpToEventIndex === null &&
if (currentRREventId && currentRREventIndex === null &&
this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
return;
shouldSendRR = false;
}
var lastReadEventIndex = this._getLastDisplayedEventIndex({
ignoreOwn: true
const lastReadEventIndex = this._getLastDisplayedEventIndex({
ignoreOwn: true,
});
if (lastReadEventIndex === null) return;
if (lastReadEventIndex === null) {
shouldSendRR = false;
}
let lastReadEvent = this.state.events[lastReadEventIndex];
shouldSendRR = shouldSendRR &&
// Only send a RR if the last read event is ahead in the timeline relative to
// the current RR event.
lastReadEventIndex > currentRREventIndex &&
// Only send a RR if the last RR set != the one we would send
this.lastRRSentEventId != lastReadEvent.getId();
var lastReadEvent = this.state.events[lastReadEventIndex];
// Only send a RM if the last RM sent != the one we would send
const shouldSendRM =
this.lastRMSentEventId != this.state.readMarkerEventId;
// we also remember the last read receipt we sent to avoid spamming the
// same one at the server repeatedly
if (lastReadEventIndex > currentReadUpToEventIndex
&& this.last_rr_sent_event_id != lastReadEvent.getId()) {
this.last_rr_sent_event_id = lastReadEvent.getId();
MatrixClientPeg.get().sendReadReceipt(lastReadEvent).catch(() => {
if (shouldSendRR || shouldSendRM) {
if (shouldSendRR) {
this.lastRRSentEventId = lastReadEvent.getId();
} else {
lastReadEvent = null;
}
this.lastRMSentEventId = this.state.readMarkerEventId;
debuglog('TimelinePanel: Sending Read Markers for ',
this.props.timelineSet.room.roomId,
'rm', this.state.readMarkerEventId,
lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',
);
MatrixClientPeg.get().setRoomReadMarkers(
this.props.timelineSet.room.roomId,
this.state.readMarkerEventId,
lastReadEvent, // Could be null, in which case no RR is sent
).catch((e) => {
// /read_markers API is not implemented on this HS, fallback to just RR
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
return MatrixClientPeg.get().sendReadReceipt(
lastReadEvent,
).catch(() => {
this.lastRRSentEventId = undefined;
});
}
// it failed, so allow retries next time the user is active
this.last_rr_sent_event_id = undefined;
this.lastRRSentEventId = undefined;
this.lastRMSentEventId = undefined;
});
// do a quick-reset of our unreadNotificationCount to avoid having
@@ -706,7 +786,7 @@ var TimelinePanel = React.createClass({
// the messagePanel doesn't know where the read marker is.
// if we know the timestamp of the read marker, make a guess based on that.
var rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.timelineSet.roomId];
const rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.timelineSet.room.roomId];
if (rmTs && this.state.events.length > 0) {
if (rmTs < this.state.events[0].getTs()) {
return -1;
@@ -718,6 +798,19 @@ var TimelinePanel = React.createClass({
return null;
},
canJumpToReadMarker: function() {
// 1. Do not show jump bar if neither the RM nor the RR are set.
// 2. Only show jump bar if RR !== RM. If they are the same, there are only fully
// read messages and unread messages. We already have a badge count and the bottom
// bar to jump to "live" when we have unread messages.
// 3. We want to show the bar if the read-marker is off the top of the screen.
// 4. Also, if pos === null, the event might not be paginated - show the unread bar
const pos = this.getReadMarkerPosition();
return this.state.readMarkerEventId !== null && // 1.
this.state.readMarkerEventId !== this._getCurrentReadReceipt() && // 2.
(pos < 0 || pos === null); // 3., 4.
},
/**
* called by the parent component when PageUp/Down/etc is pressed.
*
@@ -728,7 +821,9 @@ var TimelinePanel = React.createClass({
// jump to the live timeline on ctrl-end, rather than the end of the
// timeline window.
if (ev.ctrlKey && ev.keyCode == KeyCode.END) {
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey &&
ev.keyCode == KeyCode.END)
{
this.jumpToLiveTimeline();
} else {
this.refs.messagePanel.handleScrollKey(ev);
@@ -807,6 +902,9 @@ var TimelinePanel = React.createClass({
var onError = (error) => {
this.setState({timelineLoading: false});
console.error(
`Error loading timeline panel at ${eventId}: ${error}`,
);
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -825,14 +923,11 @@ var TimelinePanel = React.createClass({
});
};
}
var message = "Tried to load a specific point in this room's timeline, but ";
if (error.errcode == 'M_FORBIDDEN') {
message += "you do not have permission to view the message in question.";
} else {
message += "was unable to find it.";
}
var message = (error.errcode == 'M_FORBIDDEN')
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.")
: _t("Tried to load a specific point in this room's timeline, but was unable to find it.");
Modal.createDialog(ErrorDialog, {
title: "Failed to load timeline position",
title: _t("Failed to load timeline position"),
description: message,
onFinished: onFinished,
});
@@ -956,16 +1051,12 @@ var TimelinePanel = React.createClass({
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
var roomId = this.props.timelineSet.room.roomId;
if (TimelinePanel.roomReadMarkerMap[roomId] == eventId) {
// don't update the state (and cause a re-render) if there is
// no change to the RM.
// don't update the state (and cause a re-render) if there is
// no change to the RM.
if (eventId === this.state.readMarkerEventId) {
return;
}
// ideally we'd sync these via the server, but for now just stash them
// in a map.
TimelinePanel.roomReadMarkerMap[roomId] = eventId;
// in order to later figure out if the read marker is
// above or below the visible timeline, we stash the timestamp.
TimelinePanel.roomReadMarkerTsMap[roomId] = eventTs;
@@ -974,6 +1065,7 @@ var TimelinePanel = React.createClass({
return;
}
// Do the local echo of the RM
// run the render cycle before calling the callback, so that
// getReadMarkerPosition() returns the right thing.
this.setState({
@@ -1022,26 +1114,34 @@ var TimelinePanel = React.createClass({
// of paginating our way through the entire history of the room.
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
// If the state is PREPARED, we're still waiting for the js-sdk to sync with
// the HS and fetch the latest events, so we are effectively forward paginating.
const forwardPaginating = (
this.state.forwardPaginating || this.state.clientSyncState == 'PREPARED'
);
return (
<MessagePanel ref="messagePanel"
hidden={ this.props.hidden }
backPaginating={ this.state.backPaginating }
forwardPaginating={ this.state.forwardPaginating }
events={ this.state.events }
highlightedEventId={ this.props.highlightedEventId }
readMarkerEventId={ this.state.readMarkerEventId }
readMarkerVisible={ this.state.readMarkerVisible }
suppressFirstDateSeparator={ this.state.canBackPaginate }
showUrlPreview = { this.props.showUrlPreview }
manageReadReceipts = { this.props.manageReadReceipts }
ourUserId={ MatrixClientPeg.get().credentials.userId }
stickyBottom={ stickyBottom }
onScroll={ this.onMessageListScroll }
onFillRequest={ this.onMessageListFillRequest }
onUnfillRequest={ this.onMessageListUnfillRequest }
opacity={ this.props.opacity }
className={ this.props.className }
tileShape={ this.props.tileShape }
hidden={ this.props.hidden }
hideRedactions={ this.state.hideRedactions }
backPaginating={ this.state.backPaginating }
forwardPaginating={ forwardPaginating }
events={ this.state.events }
highlightedEventId={ this.props.highlightedEventId }
readMarkerEventId={ this.state.readMarkerEventId }
readMarkerVisible={ this.state.readMarkerVisible }
suppressFirstDateSeparator={ this.state.canBackPaginate }
showUrlPreview = { this.props.showUrlPreview }
manageReadReceipts = { this.props.manageReadReceipts }
ourUserId={ MatrixClientPeg.get().credentials.userId }
stickyBottom={ stickyBottom }
onScroll={ this.onMessageListScroll }
onFillRequest={ this.onMessageListFillRequest }
onUnfillRequest={ this.onMessageListUnfillRequest }
opacity={ this.props.opacity }
isTwelveHour={ this.state.isTwelveHour }
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
className={ this.props.className }
tileShape={ this.props.tileShape }
/>
);
},

View File

@@ -18,6 +18,7 @@ var React = require('react');
var ContentMessages = require('../../ContentMessages');
var dis = require('../../dispatcher');
var filesize = require('filesize');
import { _t } from '../../languageHandler';
module.exports = React.createClass({displayName: 'UploadBar',
propTypes: {
@@ -81,10 +82,8 @@ module.exports = React.createClass({displayName: 'UploadBar',
uploadedSize = uploadedSize.replace(/ .*/, '');
}
var others;
if (uploads.length > 1) {
others = ' and ' + (uploads.length - 1) + ' other' + (uploads.length > 2 ? 's' : '');
}
// MUST use var name 'count' for pluralization to kick in
var uploadText = _t("Uploading %(filename)s and %(count)s others", {filename: upload.fileName, count: (uploads.length - 1)});
return (
<div className="mx_UploadBar">
@@ -98,7 +97,7 @@ module.exports = React.createClass({displayName: 'UploadBar',
<div className="mx_UploadBar_uploadBytes">
{ uploadedSize } / { totalSize }
</div>
<div className="mx_UploadBar_uploadFilename">Uploading {upload.fileName}{others}</div>
<div className="mx_UploadBar_uploadFilename">{uploadText}</div>
</div>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
import { _t } from '../../../languageHandler';
var sdk = require('../../../index');
var Modal = require("../../../Modal");
var MatrixClientPeg = require('../../../MatrixClientPeg');
@@ -54,7 +55,7 @@ module.exports = React.createClass({
progress: "sent_email"
});
}, (err) => {
this.showErrorDialog("Failed to send email: " + err.message);
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
this.setState({
progress: null
});
@@ -78,30 +79,33 @@ module.exports = React.createClass({
ev.preventDefault();
if (!this.state.email) {
this.showErrorDialog("The email address linked to your account must be entered.");
this.showErrorDialog(_t('The email address linked to your account must be entered.'));
}
else if (!this.state.password || !this.state.password2) {
this.showErrorDialog("A new password must be entered.");
this.showErrorDialog(_t('A new password must be entered.'));
}
else if (this.state.password !== this.state.password2) {
this.showErrorDialog("New passwords must match each other.");
this.showErrorDialog(_t('New passwords must match each other.'));
}
else {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning",
title: _t('Warning!'),
description:
<div>
Resetting password will currently reset any end-to-end encryption keys on all devices,
making encrypted chat history unreadable, unless you first export your room keys
and re-import them afterwards.
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">will be improved</a>.
{ _t(
'Resetting password will currently reset any ' +
'end-to-end encryption keys on all devices, ' +
'making encrypted chat history unreadable, ' +
'unless you first export your room keys and re-import ' +
'them afterwards. In future this will be improved.'
) }
</div>,
button: "Continue",
button: _t('Continue'),
extraButtons: [
<button className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
Export E2E room keys
{ _t('Export E2E room keys') }
</button>
],
onFinished: (confirmed) => {
@@ -150,7 +154,7 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: title,
description: body
description: body,
});
},
@@ -168,22 +172,20 @@ module.exports = React.createClass({
else if (this.state.progress === "sent_email") {
resetPasswordJsx = (
<div>
An email has been sent to {this.state.email}. Once you&#39;ve followed
the link it contains, click below.
{ _t('An email has been sent to') } {this.state.email}. { _t('Once you&#39;ve followed the link it contains, click below') }.
<br />
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
value="I have verified my email address" />
value={ _t('I have verified my email address') } />
</div>
);
}
else if (this.state.progress === "complete") {
resetPasswordJsx = (
<div>
<p>Your password has been reset.</p>
<p>You have been logged out of all devices and will no longer receive push notifications.
To re-enable notifications, sign in again on each device.</p>
<p>{ _t('Your password has been reset') }.</p>
<p>{ _t('You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device') }.</p>
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
value="Return to login screen" />
value={ _t('Return to login screen') } />
</div>
);
}
@@ -191,7 +193,7 @@ module.exports = React.createClass({
resetPasswordJsx = (
<div>
<div className="mx_Login_prompt">
To reset your password, enter the email address linked to your account:
{ _t('To reset your password, enter the email address linked to your account') }:
</div>
<div>
<form onSubmit={this.onSubmitForm}>
@@ -199,21 +201,21 @@ module.exports = React.createClass({
name="reset_email" // define a name so browser's password autofill gets less confused
value={this.state.email}
onChange={this.onInputChanged.bind(this, "email")}
placeholder="Email address" autoFocus />
placeholder={ _t('Email address') } autoFocus />
<br />
<input className="mx_Login_field" ref="pass" type="password"
name="reset_password"
value={this.state.password}
onChange={this.onInputChanged.bind(this, "password")}
placeholder="New password" />
placeholder={ _t('New password') } />
<br />
<input className="mx_Login_field" ref="pass" type="password"
name="reset_password_confirm"
value={this.state.password2}
onChange={this.onInputChanged.bind(this, "password2")}
placeholder="Confirm your new password" />
placeholder={ _t('Confirm your new password') } />
<br />
<input className="mx_Login_submit" type="submit" value="Send Reset Email" />
<input className="mx_Login_submit" type="submit" value={ _t('Send Reset Email') } />
</form>
<ServerConfig ref="serverConfig"
withToggleButton={true}
@@ -227,10 +229,10 @@ module.exports = React.createClass({
<div className="mx_Login_error">
</div>
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
Return to login
{_t('Return to login screen')}
</a>
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
Create a new account
{ _t('Create an account') }
</a>
<LoginFooter />
</div>

View File

@@ -17,13 +17,13 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var sdk = require('../../../index');
var Login = require("../../../Login");
var PasswordLogin = require("../../views/login/PasswordLogin");
var CasLogin = require("../../views/login/CasLogin");
var ServerConfig = require("../../views/login/ServerConfig");
import React from 'react';
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Login from '../../../Login';
// For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;
/**
* A wire component which glues together login UI components and Login logic
@@ -67,6 +67,7 @@ module.exports = React.createClass({
username: "",
phoneCountry: null,
phoneNumber: "",
currentFlow: "m.login.password",
};
},
@@ -126,26 +127,31 @@ module.exports = React.createClass({
},
onPhoneNumberChanged: function(phoneNumber) {
this.setState({ phoneNumber: phoneNumber });
},
// Validate the phone number entered
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
this.setState({ errorText: _t('The phone number entered looks invalid') });
return;
}
onHsUrlChanged: function(newHsUrl) {
var self = this;
this.setState({
enteredHomeserverUrl: newHsUrl,
errorText: null, // reset err messages
}, function() {
self._initLoginLogic(newHsUrl);
phoneNumber: phoneNumber,
errorText: null,
});
},
onIsUrlChanged: function(newIsUrl) {
onServerConfigChange: function(config) {
var self = this;
this.setState({
enteredIdentityServerUrl: newIsUrl,
let newState = {
errorText: null, // reset err messages
}, function() {
self._initLoginLogic(null, newIsUrl);
};
if (config.hsUrl !== undefined) {
newState.enteredHomeserverUrl = config.hsUrl;
}
if (config.isUrl !== undefined) {
newState.enteredIdentityServerUrl = config.isUrl;
}
this.setState(newState, function() {
self._initLoginLogic(config.hsUrl || null, config.isUrl);
});
},
@@ -161,25 +167,28 @@ module.exports = React.createClass({
});
this._loginLogic = loginLogic;
loginLogic.getFlows().then(function(flows) {
// old behaviour was to always use the first flow without presenting
// options. This works in most cases (we don't have a UI for multiple
// logins so let's skip that for now).
loginLogic.chooseFlow(0);
}, function(err) {
self._setStateFromError(err, false);
}).finally(function() {
self.setState({
busy: false
});
});
this.setState({
enteredHomeserverUrl: hsUrl,
enteredIdentityServerUrl: isUrl,
busy: true,
loginIncorrect: false,
});
loginLogic.getFlows().then(function(flows) {
// old behaviour was to always use the first flow without presenting
// options. This works in most cases (we don't have a UI for multiple
// logins so let's skip that for now).
loginLogic.chooseFlow(0);
self.setState({
currentFlow: self._getCurrentFlowStep(),
});
}, function(err) {
self._setStateFromError(err, false);
}).finally(function() {
self.setState({
busy: false,
});
});
},
_getCurrentFlowStep: function() {
@@ -204,8 +213,8 @@ module.exports = React.createClass({
errCode = "HTTP " + err.httpStatus;
}
let errorText = "Error: Problem communicating with the given homeserver " +
(errCode ? "(" + errCode + ")" : "");
let errorText = _t("Error: Problem communicating with the given homeserver.") +
(errCode ? " (" + errCode + ")" : "");
if (err.cors === 'rejected') {
if (window.location.protocol === 'https:' &&
@@ -213,14 +222,19 @@ module.exports = React.createClass({
!this.state.enteredHomeserverUrl.startsWith("http")))
{
errorText = <span>
Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar.
Either use HTTPS or <a href='https://www.google.com/search?&q=enable%20unsafe%20scripts'>enable unsafe scripts</a>
{ _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
"Either use HTTPS or <a>enable unsafe scripts</a>.",
/<a>(.*?)<\/a>/,
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
)}
</span>;
}
else {
errorText = <span>
Can't connect to homeserver - please check your connectivity and ensure
your <a href={ this.state.enteredHomeserverUrl }>homeserver's SSL certificate</a> is trusted.
{ _tJsx("Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
/<a>(.*?)<\/a>/,
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
)}
</span>;
}
}
@@ -231,6 +245,7 @@ module.exports = React.createClass({
componentForStep: function(step) {
switch (step) {
case 'm.login.password':
const PasswordLogin = sdk.getComponent('login.PasswordLogin');
return (
<PasswordLogin
onSubmit={this.onPasswordLogin}
@@ -245,6 +260,7 @@ module.exports = React.createClass({
/>
);
case 'm.login.cas':
const CasLogin = sdk.getComponent('login.CasLogin');
return (
<CasLogin onSubmit={this.onCasLogin} />
);
@@ -254,24 +270,24 @@ module.exports = React.createClass({
}
return (
<div>
Sorry, this homeserver is using a login which is not
recognised ({step})
{ _t('Sorry, this homeserver is using a login which is not recognised ')}({step})
</div>
);
}
},
render: function() {
var Loader = sdk.getComponent("elements.Spinner");
var LoginHeader = sdk.getComponent("login.LoginHeader");
var LoginFooter = sdk.getComponent("login.LoginFooter");
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
const Loader = sdk.getComponent("elements.Spinner");
const LoginHeader = sdk.getComponent("login.LoginHeader");
const LoginFooter = sdk.getComponent("login.LoginFooter");
const ServerConfig = sdk.getComponent("login.ServerConfig");
const loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
var loginAsGuestJsx;
if (this.props.enableGuest) {
loginAsGuestJsx =
<a className="mx_Login_create" onClick={this._onLoginAsGuestClick} href="#">
Login as guest
{ _t('Login as guest')}
</a>;
}
@@ -279,7 +295,7 @@ module.exports = React.createClass({
if (this.props.onCancelClick) {
returnToAppJsx =
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
Return to app
{ _t('Return to app')}
</a>;
}
@@ -288,24 +304,23 @@ module.exports = React.createClass({
<div className="mx_Login_box">
<LoginHeader />
<div>
<h2>Sign in
<h2>{ _t('Sign in')}
{ loader }
</h2>
{ this.componentForStep(this._getCurrentFlowStep()) }
{ this.componentForStep(this.state.currentFlow) }
<ServerConfig ref="serverConfig"
withToggleButton={true}
customHsUrl={this.props.customHsUrl}
customIsUrl={this.props.customIsUrl}
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onHsUrlChanged={this.onHsUrlChanged}
onIsUrlChanged={this.onIsUrlChanged}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}/>
<div className="mx_Login_error">
{ this.state.errorText }
</div>
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
Create a new account
{ _t('Create an account')}
</a>
{ loginAsGuestJsx }
{ returnToAppJsx }

View File

@@ -16,9 +16,10 @@ limitations under the License.
'use strict';
var React = require('react');
var sdk = require('../../../index');
var MatrixClientPeg = require('../../../MatrixClientPeg');
import React from 'react';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'PostRegistration',
@@ -49,7 +50,7 @@ module.exports = React.createClass({
});
}, function(error) {
self.setState({
errorString: "Failed to fetch avatar URL",
errorString: _t("Failed to fetch avatar URL"),
busy: false
});
});
@@ -64,12 +65,12 @@ module.exports = React.createClass({
<div className="mx_Login_box">
<LoginHeader />
<div className="mx_Login_profile">
Set a display name:
{ _t('Set a display name:') }
<ChangeDisplayName />
Upload an avatar:
{ _t('Upload an avatar:') }
<ChangeAvatar
initialAvatarUrl={this.state.avatarUrl} />
<button onClick={this.props.onComplete}>Continue</button>
<button onClick={this.props.onComplete}>{ _t('Continue') }</button>
{this.state.errorString}
</div>
</div>

View File

@@ -21,12 +21,11 @@ import q from 'q';
import React from 'react';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import ServerConfig from '../../views/login/ServerConfig';
import MatrixClientPeg from '../../../MatrixClientPeg';
import RegistrationForm from '../../views/login/RegistrationForm';
import CaptchaForm from '../../views/login/CaptchaForm';
import RtsClient from '../../../RtsClient';
import { _t } from '../../../languageHandler';
const MIN_PASSWORD_LENGTH = 6;
@@ -98,7 +97,7 @@ module.exports = React.createClass({
this.props.teamServerConfig.teamServerURL &&
!this._rtsClient
) {
this._rtsClient = new RtsClient(this.props.teamServerConfig.teamServerURL);
this._rtsClient = this.props.rtsClient || new RtsClient(this.props.teamServerConfig.teamServerURL);
this.setState({
teamServerBusy: true,
@@ -123,18 +122,17 @@ module.exports = React.createClass({
}
},
onHsUrlChanged: function(newHsUrl) {
this.setState({
hsUrl: newHsUrl,
onServerConfigChange: function(config) {
let newState = {};
if (config.hsUrl !== undefined) {
newState.hsUrl = config.hsUrl;
}
if (config.isUrl !== undefined) {
newState.isUrl = config.isUrl;
}
this.setState(newState, function() {
this._replaceClient();
});
this._replaceClient();
},
onIsUrlChanged: function(newIsUrl) {
this.setState({
isUrl: newIsUrl,
});
this._replaceClient();
},
_replaceClient: function() {
@@ -163,7 +161,7 @@ module.exports = React.createClass({
msisdn_available |= flow.stages.indexOf('m.login.msisdn') > -1;
}
if (!msisdn_available) {
msg = "This server does not support authentication with a phone number";
msg = _t('This server does not support authentication with a phone number.');
}
}
this.setState({
@@ -222,7 +220,6 @@ module.exports = React.createClass({
}
trackPromise.then((teamToken) => {
console.info('Team token promise',teamToken);
this.props.onLoggedIn({
userId: response.user_id,
deviceId: response.device_id,
@@ -261,29 +258,29 @@ module.exports = React.createClass({
var errMsg;
switch (errCode) {
case "RegistrationForm.ERR_PASSWORD_MISSING":
errMsg = "Missing password.";
errMsg = _t('Missing password.');
break;
case "RegistrationForm.ERR_PASSWORD_MISMATCH":
errMsg = "Passwords don't match.";
errMsg = _t('Passwords don\'t match.');
break;
case "RegistrationForm.ERR_PASSWORD_LENGTH":
errMsg = `Password too short (min ${MIN_PASSWORD_LENGTH}).`;
errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: MIN_PASSWORD_LENGTH});
break;
case "RegistrationForm.ERR_EMAIL_INVALID":
errMsg = "This doesn't look like a valid email address";
errMsg = _t('This doesn\'t look like a valid email address.');
break;
case "RegistrationForm.ERR_PHONE_NUMBER_INVALID":
errMsg = "This doesn't look like a valid phone number";
errMsg = _t('This doesn\'t look like a valid phone number.');
break;
case "RegistrationForm.ERR_USERNAME_INVALID":
errMsg = "User names may only contain letters, numbers, dots, hyphens and underscores.";
errMsg = _t('User names may only contain letters, numbers, dots, hyphens and underscores.');
break;
case "RegistrationForm.ERR_USERNAME_BLANK":
errMsg = "You need to enter a user name";
errMsg = _t('You need to enter a user name.');
break;
default:
console.error("Unknown error code: %s", errCode);
errMsg = "An unknown error occurred.";
errMsg = _t('An unknown error occurred.');
break;
}
this.setState({
@@ -390,8 +387,7 @@ module.exports = React.createClass({
customIsUrl={this.props.customIsUrl}
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onHsUrlChanged={this.onHsUrlChanged}
onIsUrlChanged={this.onIsUrlChanged}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}
/>
</div>
@@ -402,7 +398,7 @@ module.exports = React.createClass({
if (this.props.onCancelClick) {
returnToAppJsx = (
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
Return to app
{_t('Return to app')}
</a>
);
}
@@ -415,10 +411,10 @@ module.exports = React.createClass({
this.state.teamSelected.domain + "/icon.png" :
null}
/>
<h2>Create an account</h2>
<h2>{_t('Create an account')}</h2>
{registerBody}
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
I already have an account
{_t('I already have an account')}
</a>
{returnToAppJsx}
<LoginFooter />

View File

@@ -32,6 +32,7 @@ module.exports = React.createClass({
urls: React.PropTypes.array, // [highest_priority, ... , lowest_priority]
width: React.PropTypes.number,
height: React.PropTypes.number,
// XXX resizeMethod not actually used.
resizeMethod: React.PropTypes.string,
defaultToInitialLetter: React.PropTypes.bool // true to add default url
},

View File

@@ -59,7 +59,9 @@ module.exports = React.createClass({
ContentRepo.getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(),
props.oobData.avatarUrl,
props.width, props.height, props.resizeMethod
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod
), // highest priority
this.getRoomAvatarUrl(props),
this.getOneToOneAvatar(props),
@@ -74,7 +76,9 @@ module.exports = React.createClass({
return props.room.getAvatarUrl(
MatrixClientPeg.get().getHomeserverUrl(),
props.width, props.height, props.resizeMethod,
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod,
false
);
},
@@ -103,14 +107,18 @@ module.exports = React.createClass({
}
return theOtherGuy.getAvatarUrl(
MatrixClientPeg.get().getHomeserverUrl(),
props.width, props.height, props.resizeMethod,
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod,
false
);
} else if (userIds.length == 1) {
return mlist[userIds[0]].getAvatarUrl(
MatrixClientPeg.get().getHomeserverUrl(),
props.width, props.height, props.resizeMethod,
false
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod,
false
);
} else {
return null;

View File

@@ -16,8 +16,8 @@ limitations under the License.
'use strict';
var React = require('react');
import React from 'react';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'CreateRoomButton',
propTypes: {
@@ -36,7 +36,7 @@ module.exports = React.createClass({
render: function() {
return (
<button className="mx_CreateRoomButton" onClick={this.onClick}>Create Room</button>
<button className="mx_CreateRoomButton" onClick={this.onClick}>{_t("Create Room")}</button>
);
}
});

View File

@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
import { _t } from '../../../languageHandler';
var Presets = {
PrivateChat: "private_chat",
@@ -46,9 +47,9 @@ module.exports = React.createClass({
render: function() {
return (
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
<option value={this.Presets.PrivateChat}>Private Chat</option>
<option value={this.Presets.PublicChat}>Public Chat</option>
<option value={this.Presets.Custom}>Custom</option>
<option value={this.Presets.PrivateChat}>{_t("Private Chat")}</option>
<option value={this.Presets.PublicChat}>{_t("Public Chat")}</option>
<option value={this.Presets.Custom}>{_t("Custom")}</option>
</select>
);
}

View File

@@ -15,6 +15,7 @@ limitations under the License.
*/
var React = require('react');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'RoomAlias',
@@ -94,7 +95,7 @@ module.exports = React.createClass({
render: function() {
return (
<input type="text" className="mx_RoomAlias" placeholder="Alias (optional)"
<input type="text" className="mx_RoomAlias" placeholder={_t("Alias (optional)")}
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
value={this.props.alias}/>
);

View File

@@ -67,7 +67,7 @@ export default React.createClass({
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
return (
<div onKeyDown={this._onKeyDown} className={this.props.className}>
<AccessibleButton onClick={this._onCancelClick}

View File

@@ -16,36 +16,30 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import DMRoomMap from '../../../utils/DMRoomMap';
import AccessibleButton from '../elements/AccessibleButton';
import Unread from '../../../Unread';
import classNames from 'classnames';
import createRoom from '../../../createRoom';
export default class ChatCreateOrReuseDialog extends React.Component {
constructor(props) {
super(props);
this.onNewDMClick = this.onNewDMClick.bind(this);
this.onRoomTileClick = this.onRoomTileClick.bind(this);
this.state = {
tiles: [],
profile: {
displayName: null,
avatarUrl: null,
},
profileError: null,
};
}
onNewDMClick() {
createRoom({dmUserId: this.props.userId});
this.props.onFinished(true);
}
onRoomTileClick(roomId) {
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
this.props.onFinished(true);
}
render() {
componentWillMount() {
const client = MatrixClientPeg.get();
const dmRoomMap = new DMRoomMap(client);
@@ -70,40 +64,123 @@ export default class ChatCreateOrReuseDialog extends React.Component {
highlight={highlight}
isInvite={me.membership == "invite"}
onClick={this.onRoomTileClick}
/>
/>,
);
}
}
const labelClasses = classNames({
mx_MemberInfo_createRoom_label: true,
mx_RoomTile_name: true,
this.setState({
tiles: tiles,
});
const startNewChat = <AccessibleButton
className="mx_MemberInfo_createRoom"
onClick={this.onNewDMClick}
>
<div className="mx_RoomTile_avatar">
<img src="img/create-big.svg" width="26" height="26" />
</div>
<div className={labelClasses}><i>Start new chat</i></div>
</AccessibleButton>;
if (tiles.length === 0) {
this.setState({
busyProfile: true,
});
MatrixClientPeg.get().getProfileInfo(this.props.userId).done((resp) => {
const profile = {
displayName: resp.displayname,
avatarUrl: null,
};
if (resp.avatar_url) {
profile.avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(
resp.avatar_url, 48, 48, "crop",
);
}
this.setState({
busyProfile: false,
profile: profile,
});
}, (err) => {
console.error(
'Unable to get profile for user ' + this.props.userId + ':',
err,
);
this.setState({
busyProfile: false,
profileError: err,
});
});
}
}
onRoomTileClick(roomId) {
this.props.onExistingRoomSelected(roomId);
}
render() {
let title = '';
let content = null;
if (this.state.tiles.length > 0) {
// Show the existing rooms with a "+" to add a new dm
title = _t('Create a new chat or reuse an existing one');
const labelClasses = classNames({
mx_MemberInfo_createRoom_label: true,
mx_RoomTile_name: true,
});
const startNewChat = <AccessibleButton
className="mx_MemberInfo_createRoom"
onClick={this.props.onNewDMClick}
>
<div className="mx_RoomTile_avatar">
<img src="img/create-big.svg" width="26" height="26" />
</div>
<div className={labelClasses}><i>{ _t("Start new chat") }</i></div>
</AccessibleButton>;
content = <div className="mx_Dialog_content">
{ _t('You already have existing direct chats with this user:') }
<div className="mx_ChatCreateOrReuseDialog_tiles">
{ this.state.tiles }
{ startNewChat }
</div>
</div>;
} else {
// Show the avatar, name and a button to confirm that a new chat is requested
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const Spinner = sdk.getComponent('elements.Spinner');
title = _t('Start chatting');
let profile = null;
if (this.state.busyProfile) {
profile = <Spinner />;
} else if (this.state.profileError) {
profile = <div className="error">
Unable to load profile information for { this.props.userId }
</div>;
} else {
profile = <div className="mx_ChatCreateOrReuseDialog_profile">
<BaseAvatar
name={this.state.profile.displayName || this.props.userId}
url={this.state.profile.avatarUrl}
width={48} height={48}
/>
<div className="mx_ChatCreateOrReuseDialog_profile_name">
{this.state.profile.displayName || this.props.userId}
</div>
</div>;
}
content = <div>
<div className="mx_Dialog_content">
<p>
{ _t('Click on the button below to start chatting!') }
</p>
{ profile }
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.props.onNewDMClick}>
{ _t('Start Chatting') }
</button>
</div>
</div>;
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className='mx_ChatCreateOrReuseDialog'
onFinished={() => {
this.props.onFinished(false)
}}
title='Create a new chat or reuse an existing one'
onFinished={ this.props.onFinished.bind(false) }
title={title}
>
<div className="mx_Dialog_content">
You already have existing direct chats with this user:
<div className="mx_ChatCreateOrReuseDialog_tiles">
{tiles}
{startNewChat}
</div>
</div>
{ content }
</BaseDialog>
);
}
@@ -111,5 +188,8 @@ export default class ChatCreateOrReuseDialog extends React.Component {
ChatCreateOrReuseDialog.propTyps = {
userId: React.PropTypes.string.isRequired,
// Called when clicking outside of the dialog
onFinished: React.PropTypes.func.isRequired,
onNewDMClick: React.PropTypes.func.isRequired,
onExistingRoomSelected: React.PropTypes.func.isRequired,
};

View File

@@ -15,25 +15,23 @@ limitations under the License.
*/
import React from 'react';
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
import createRoom from '../../../createRoom';
import MatrixClientPeg from '../../../MatrixClientPeg';
import DMRoomMap from '../../../utils/DMRoomMap';
import rate_limited_func from '../../../ratelimitedfunc';
import dis from '../../../dispatcher';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
import q from 'q';
import Fuse from 'fuse.js';
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
module.exports = React.createClass({
displayName: "ChatInviteDialog",
propTypes: {
title: React.PropTypes.string,
title: React.PropTypes.string.isRequired,
description: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.string,
@@ -43,17 +41,13 @@ module.exports = React.createClass({
roomId: React.PropTypes.string,
button: React.PropTypes.string,
focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired
onFinished: React.PropTypes.func.isRequired,
},
getDefaultProps: function() {
return {
title: "Start a chat",
description: "Who would you like to communicate with?",
value: "",
placeholder: "Email, name or matrix ID",
button: "Start Chat",
focus: true
focus: true,
};
},
@@ -61,12 +55,20 @@ module.exports = React.createClass({
return {
error: false,
// List of AddressTile.InviteAddressType objects represeting
// List of AddressTile.InviteAddressType objects representing
// the list of addresses we're going to invite
inviteList: [],
// List of AddressTile.InviteAddressType objects represeting
// the set of autocompletion results for the current search
// Whether a search is ongoing
busy: false,
// An error message generated during the user directory search
searchError: null,
// Whether the server supports the user_directory API
serverSupportsUserDirectory: true,
// The query being searched for
query: "",
// List of AddressTile.InviteAddressType objects representing
// the set of auto-completion results for the current search
// query.
queryList: [],
};
@@ -77,20 +79,6 @@ module.exports = React.createClass({
// Set the cursor at the end of the text input
this.refs.textinput.value = this.props.value;
}
// Create a Fuse instance for fuzzy searching this._userList
this._fuse = new Fuse(
// Use an empty list at first that will later be populated
// (see this._updateUserList)
[],
{
shouldSort: true,
location: 0, // The index of the query in the test string
distance: 5, // The distance away from location the query can be
// 0.0 = exact match, 1.0 = match anything
threshold: 0.3,
}
);
this._updateUserList();
},
onButtonClick: function() {
@@ -112,16 +100,25 @@ module.exports = React.createClass({
// A Direct Message room already exists for this user, so select a
// room from a list that is similar to the one in MemberInfo panel
const ChatCreateOrReuseDialog = sdk.getComponent(
"views.dialogs.ChatCreateOrReuseDialog"
"views.dialogs.ChatCreateOrReuseDialog",
);
Modal.createDialog(ChatCreateOrReuseDialog, {
userId: userId,
onFinished: (success) => {
if (success) {
this.props.onFinished(true, inviteList[0]);
}
// else show this ChatInviteDialog again
}
this.props.onFinished(success);
},
onNewDMClick: () => {
dis.dispatch({
action: 'start_chat',
user_id: userId,
});
},
onExistingRoomSelected: (roomId) => {
dis.dispatch({
action: 'view_room',
user_id: roomId,
});
},
});
} else {
this._startChat(inviteList);
@@ -148,15 +145,15 @@ module.exports = React.createClass({
} else if (e.keyCode === 38) { // up arrow
e.stopPropagation();
e.preventDefault();
this.addressSelector.moveSelectionUp();
if (this.addressSelector) this.addressSelector.moveSelectionUp();
} else if (e.keyCode === 40) { // down arrow
e.stopPropagation();
e.preventDefault();
this.addressSelector.moveSelectionDown();
if (this.addressSelector) this.addressSelector.moveSelectionDown();
} else if (this.state.queryList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab
e.stopPropagation();
e.preventDefault();
this.addressSelector.chooseSelection();
if (this.addressSelector) this.addressSelector.chooseSelection();
} else if (this.refs.textinput.value.length === 0 && this.state.inviteList.length && e.keyCode === 8) { // backspace
e.stopPropagation();
e.preventDefault();
@@ -178,72 +175,37 @@ module.exports = React.createClass({
},
onQueryChanged: function(ev) {
const query = ev.target.value;
let queryList = [];
if (query.length < 2) {
return;
}
const query = ev.target.value.toLowerCase();
if (this.queryChangedDebouncer) {
clearTimeout(this.queryChangedDebouncer);
}
this.queryChangedDebouncer = setTimeout(() => {
// Only do search if there is something to search
if (query.length > 0 && query != '@') {
// Weighted keys prefer to match userIds when first char is @
this._fuse.options.keys = [{
name: 'displayName',
weight: query[0] === '@' ? 0.1 : 0.9,
},{
name: 'userId',
weight: query[0] === '@' ? 0.9 : 0.1,
}];
queryList = this._fuse.search(query).map((user) => {
// Return objects, structure of which is defined
// by InviteAddressType
return {
addressType: 'mx',
address: user.userId,
displayName: user.displayName,
avatarMxc: user.avatarUrl,
isKnown: true,
}
});
// If the query is a valid address, add an entry for that
// This is important, otherwise there's no way to invite
// a perfectly valid address if there are close matches.
const addrType = getAddressType(query);
if (addrType !== null) {
queryList.unshift({
addressType: addrType,
address: query,
isKnown: false,
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
if (addrType == 'email') {
this._lookupThreepid(addrType, query).done();
}
// Only do search if there is something to search
if (query.length > 0 && query != '@' && query.length >= 2) {
this.queryChangedDebouncer = setTimeout(() => {
if (this.state.serverSupportsUserDirectory) {
this._doUserDirectorySearch(query);
} else {
this._doLocalSearch(query);
}
}
}, QUERY_USER_DIRECTORY_DEBOUNCE_MS);
} else {
this.setState({
queryList: queryList,
error: false,
}, () => {
this.addressSelector.moveSelectionTop();
queryList: [],
query: "",
searchError: null,
});
}, 200);
}
},
onDismissed: function(index) {
var self = this;
return function() {
return () => {
var inviteList = self.state.inviteList.slice();
inviteList.splice(index, 1);
self.setState({
inviteList: inviteList,
queryList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
};
@@ -262,10 +224,103 @@ module.exports = React.createClass({
this.setState({
inviteList: inviteList,
queryList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
},
_doUserDirectorySearch: function(query) {
this.setState({
busy: true,
query,
searchError: null,
});
MatrixClientPeg.get().searchUserDirectory({
term: query,
}).then((resp) => {
this._processResults(resp.results, query);
}).catch((err) => {
console.error('Error whilst searching user directory: ', err);
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
if (err.errcode === 'M_UNRECOGNIZED') {
this.setState({
serverSupportsUserDirectory: false,
});
// Do a local search immediately
this._doLocalSearch(query);
}
}).done(() => {
this.setState({
busy: false,
});
});
},
_doLocalSearch: function(query) {
this.setState({
query,
searchError: null,
});
const results = [];
MatrixClientPeg.get().getUsers().forEach((user) => {
if (user.userId.toLowerCase().indexOf(query) === -1 &&
user.displayName.toLowerCase().indexOf(query) === -1
) {
return;
}
// Put results in the format of the new API
results.push({
user_id: user.userId,
display_name: user.displayName,
avatar_url: user.avatarUrl,
});
});
this._processResults(results, query);
},
_processResults: function(results, query) {
const queryList = [];
results.forEach((user) => {
if (user.user_id === MatrixClientPeg.get().credentials.userId) {
return;
}
// Return objects, structure of which is defined
// by InviteAddressType
queryList.push({
addressType: 'mx',
address: user.user_id,
displayName: user.display_name,
avatarMxc: user.avatar_url,
isKnown: true,
});
});
// If the query is a valid address, add an entry for that
// This is important, otherwise there's no way to invite
// a perfectly valid address if there are close matches.
const addrType = getAddressType(query);
if (addrType !== null) {
queryList.unshift({
addressType: addrType,
address: query,
isKnown: false,
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
if (addrType == 'email') {
this._lookupThreepid(addrType, query).done();
}
}
this.setState({
queryList,
error: false,
}, () => {
if (this.addressSelector) this.addressSelector.moveSelectionTop();
});
},
_getDirectMessageRooms: function(addr) {
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
const dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
@@ -284,11 +339,7 @@ module.exports = React.createClass({
_startChat: function(addrs) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't invite users. Please register."
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
@@ -308,8 +359,8 @@ module.exports = React.createClass({
console.error(err.stack);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to invite",
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
return null;
})
@@ -321,8 +372,8 @@ module.exports = React.createClass({
console.error(err.stack);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to invite user",
title: _t("Failed to invite user"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
return null;
})
@@ -342,8 +393,8 @@ module.exports = React.createClass({
console.error(err.stack);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to invite",
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
return null;
})
@@ -354,18 +405,6 @@ module.exports = React.createClass({
this.props.onFinished(true, addrTexts);
},
_updateUserList: new rate_limited_func(function() {
// Get all the users
this._userList = MatrixClientPeg.get().getUsers();
// Remove current user
const meIx = this._userList.findIndex((u) => {
return u.userId === MatrixClientPeg.get().credentials.userId;
});
this._userList.splice(meIx, 1);
this._fuse.set(this._userList);
}, 500),
_isOnInviteList: function(uid) {
for (let i = 0; i < this.state.inviteList.length; i++) {
if (
@@ -401,7 +440,7 @@ module.exports = React.createClass({
if (errorList.length > 0) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to invite the following users to the " + room.name + " room:",
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
description: errorList.join(", "),
});
}
@@ -433,6 +472,7 @@ module.exports = React.createClass({
this.setState({
inviteList: inviteList,
queryList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return inviteList;
@@ -468,7 +508,7 @@ module.exports = React.createClass({
displayName: res.displayname,
avatarMxc: res.avatar_url,
isKnown: true,
}]
}],
});
});
},
@@ -500,23 +540,27 @@ module.exports = React.createClass({
placeholder={this.props.placeholder}
defaultValue={this.props.value}
autoFocus={this.props.focus}>
</textarea>
</textarea>,
);
var error;
var addressSelector;
let error;
let addressSelector;
if (this.state.error) {
error = <div className="mx_ChatInviteDialog_error">You have entered an invalid contact. Try using their Matrix ID or email address.</div>;
error = <div className="mx_ChatInviteDialog_error">{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}</div>;
} else if (this.state.searchError) {
error = <div className="mx_ChatInviteDialog_error">{this.state.searchError}</div>;
} else if (
this.state.query.length > 0 &&
this.state.queryList.length === 0 &&
!this.state.busy
) {
error = <div className="mx_ChatInviteDialog_error">{_t("No results")}</div>;
} else {
const addressSelectorHeader = <div className="mx_ChatInviteDialog_addressSelectHeader">
Searching known users
</div>;
addressSelector = (
<AddressSelector ref={(ref) => {this.addressSelector = ref;}}
addressList={ this.state.queryList }
onSelected={ this.onSelected }
truncateAt={ TRUNCATE_QUERY_LIST }
header={ addressSelectorHeader }
/>
);
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import classnames from 'classnames';
import { _t } from '../../../languageHandler';
/*
* A dialog for confirming a redaction.
@@ -42,7 +43,7 @@ export default React.createClass({
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const title = "Confirm Redaction";
const title = _t("Confirm Removal");
const confirmButtonClass = classnames({
'mx_Dialog_primary': true,
@@ -55,16 +56,16 @@ export default React.createClass({
title={title}
>
<div className="mx_Dialog_content">
Are you sure you wish to redact (delete) this event?
Note that if you redact a room name or topic change, it could undo the change.
{_t("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.")}
</div>
<div className="mx_Dialog_buttons">
<button className={confirmButtonClass} onClick={this.onOk}>
Redact
{_t("Remove")}
</button>
<button onClick={this.onCancel}>
Cancel
{_t("Cancel")}
</button>
</div>
</BaseDialog>

View File

@@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classnames from 'classnames';
/*
@@ -69,7 +70,7 @@ export default React.createClass({
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
const title = this.props.action + " this person?";
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
const confirmButtonClass = classnames({
'mx_Dialog_primary': true,
'danger': this.props.danger,
@@ -82,7 +83,7 @@ export default React.createClass({
<form onSubmit={this.onOk}>
<input className="mx_ConfirmUserActionDialog_reasonField"
ref={this._collectReasonField}
placeholder="Reason"
placeholder={ _t("Reason") }
autoFocus={true}
/>
</form>
@@ -111,7 +112,7 @@ export default React.createClass({
</button>
<button onClick={this.onCancel}>
Cancel
{ _t("Cancel") }
</button>
</div>
</BaseDialog>

View File

@@ -20,6 +20,7 @@ import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import * as Lifecycle from '../../../Lifecycle';
import Velocity from 'velocity-vector';
import { _t } from '../../../languageHandler';
export default class DeactivateAccountDialog extends React.Component {
constructor(props, context) {
@@ -56,10 +57,10 @@ export default class DeactivateAccountDialog extends React.Component {
Lifecycle.onLoggedOut();
this.props.onFinished(false);
}, (err) => {
let errStr = 'Unknown error';
let errStr = _t('Unknown error');
// https://matrix.org/jira/browse/SYN-744
if (err.httpStatus == 401 || err.httpStatus == 403) {
errStr = 'Incorrect password';
errStr = _t('Incorrect password');
Velocity(this._passwordField, "callout.shake", 300);
}
this.setState({
@@ -85,29 +86,29 @@ export default class DeactivateAccountDialog extends React.Component {
passwordBoxClass = 'error';
}
const okLabel = this.state.busy ? <Loader /> : 'Deactivate Account';
const okLabel = this.state.busy ? <Loader /> : _t('Deactivate Account');
const okEnabled = this.state.confirmButtonEnabled && !this.state.busy;
let cancelButton = null;
if (!this.state.busy) {
cancelButton = <button onClick={this._onCancel} autoFocus={true}>
Cancel
{_t("Cancel")}
</button>;
}
return (
<div className="mx_DeactivateAccountDialog">
<div className="mx_Dialog_title danger">
Deactivate Account
{_t("Deactivate Account")}
</div>
<div className="mx_Dialog_content">
<p>This will make your account permanently unusable. You will not be able to re-register the same user ID.</p>
<p>{_t("This will make your account permanently unusable. You will not be able to re-register the same user ID.")}</p>
<p>This action is irreversible.</p>
<p>{_t("This action is irreversible.")}</p>
<p>To continue, please enter your password.</p>
<p>{_t("To continue, please enter your password.")}</p>
<p>Password:</p>
<p>{_t("Password")}:</p>
<input
type="password"
onChange={this._onPasswordFieldChange}

View File

@@ -0,0 +1,77 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import { _t } from '../../../languageHandler';
export default function DeviceVerifyDialog(props) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const key = FormattingUtils.formatCryptoKey(props.device.getFingerprint());
const body = (
<div>
<p>
{_t("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:")}
</p>
<div className="mx_UserSettings_cryptoSection">
<ul>
<li><label>{_t("Device name")}:</label> <span>{ props.device.getDisplayName() }</span></li>
<li><label>{_t("Device ID")}:</label> <span><code>{ props.device.deviceId}</code></span></li>
<li><label>{_t("Device key")}:</label> <span><code><b>{ key }</b></code></span></li>
</ul>
</div>
<p>
{_t("If it matches, press the verify button below. " +
"If it doesn't, then someone else is intercepting this device " +
"and you probably want to press the blacklist button instead.")}
</p>
<p>
{_t("In future this verification process will be more sophisticated.")}
</p>
</div>
);
function onFinished(confirm) {
if (confirm) {
MatrixClientPeg.get().setDeviceVerified(
props.userId, props.device.deviceId, true,
);
}
props.onFinished(confirm);
}
return (
<QuestionDialog
title={_t("Verify device")}
description={body}
button={_t("I verify that the keys match")}
onFinished={onFinished}
/>
);
}
DeviceVerifyDialog.propTypes = {
userId: React.PropTypes.string.isRequired,
device: React.PropTypes.object.isRequired,
onFinished: React.PropTypes.func.isRequired,
};

View File

@@ -27,6 +27,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default React.createClass({
displayName: 'ErrorDialog',
@@ -43,24 +44,30 @@ export default React.createClass({
getDefaultProps: function() {
return {
title: "Error",
description: "An error has occurred.",
button: "OK",
focus: true,
title: null,
description: null,
button: null,
};
},
componentDidMount: function() {
if (this.props.focus) {
this.refs.button.focus();
}
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
title={this.props.title}>
title={this.props.title || _t('Error')}>
<div className="mx_Dialog_content">
{this.props.description}
{this.props.description || _t('An error has occurred.')}
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={this.props.focus}>
{this.props.button}
<button ref="button" className="mx_Dialog_primary" onClick={this.props.onFinished}>
{this.props.button || _t('OK')}
</button>
</div>
</BaseDialog>

View File

@@ -15,11 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Matrix from 'matrix-js-sdk';
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
@@ -46,12 +45,6 @@ export default React.createClass({
title: React.PropTypes.string,
},
getDefaultProps: function() {
return {
title: "Authentication",
};
},
getInitialState: function() {
return {
authError: null,
@@ -85,7 +78,7 @@ export default React.createClass({
<AccessibleButton onClick={this._onDismissClick}
className="mx_UserSettings_button"
>
Dismiss
{_t("Dismiss")}
</AccessibleButton>
</div>
);
@@ -105,7 +98,7 @@ export default React.createClass({
return (
<BaseDialog className="mx_InteractiveAuthDialog"
onFinished={this.props.onFinished}
title={this.state.authError ? 'Error' : this.props.title}
title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))}
>
{content}
</BaseDialog>

View File

@@ -26,6 +26,7 @@ limitations under the License.
import React from 'react';
import dis from '../../../dispatcher';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'NeedToRegisterDialog',
@@ -38,13 +39,6 @@ module.exports = React.createClass({
onFinished: React.PropTypes.func.isRequired,
},
getDefaultProps: function() {
return {
title: "Registration required",
description: "A registered account is required for this action",
};
},
onRegisterClicked: function() {
dis.dispatch({
action: "start_upgrade_registration",
@@ -59,17 +53,17 @@ module.exports = React.createClass({
return (
<BaseDialog className="mx_NeedToRegisterDialog"
onFinished={this.props.onFinished}
title={this.props.title}
title={this.props.title || _t('Registration required')}
>
<div className="mx_Dialog_content">
{this.props.description}
{this.props.description || _t('A registered account is required for this action')}
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={true}>
Cancel
{_t("Cancel")}
</button>
<button onClick={this.onRegisterClicked}>
Register
{_t("Register")}
</button>
</div>
</BaseDialog>

View File

@@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default React.createClass({
displayName: 'QuestionDialog',
@@ -33,7 +34,6 @@ export default React.createClass({
title: "",
description: "",
extraButtons: null,
button: "OK",
focus: true,
hasCancelButton: true,
};
@@ -51,7 +51,7 @@ export default React.createClass({
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const cancelButton = this.props.hasCancelButton ? (
<button onClick={this.onCancel}>
Cancel
{_t("Cancel")}
</button>
) : null;
return (
@@ -64,7 +64,7 @@ export default React.createClass({
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
{this.props.button}
{this.props.button || _t('OK')}
</button>
{this.props.extraButtons}
{cancelButton}

View File

@@ -18,6 +18,7 @@ import React from 'react';
import sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t, _tJsx } from '../../../languageHandler';
export default React.createClass({
@@ -43,29 +44,32 @@ export default React.createClass({
if (SdkConfig.get().bug_report_endpoint_url) {
bugreport = (
<p>Otherwise, <a onClick={this._sendBugReport} href='#'>
click here</a> to send a bug report.
<p>
{_tJsx(
"Otherwise, <a>click here</a> to send a bug report.",
/<a>(.*?)<\/a>/, (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{sub}</a>,
)}
</p>
);
}
return (
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
title='Unable to restore session'>
title={_t('Unable to restore session')}>
<div className="mx_Dialog_content">
<p>We encountered an error trying to restore your previous session. If
you continue, you will need to log in again, and encrypted chat
history will be unreadable.</p>
<p>{_t("We encountered an error trying to restore your previous session. If " +
"you continue, you will need to log in again, and encrypted chat " +
"history will be unreadable.")}</p>
<p>If you have previously used a more recent version of Riot, your session
may be incompatible with this version. Close this window and return
to the more recent version.</p>
<p>{_t("If you have previously used a more recent version of Riot, your session " +
"may be incompatible with this version. Close this window and return " +
"to the more recent version.")}</p>
{bugreport}
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this._continueClicked}>
Continue anyway
{_t("Continue anyway")}
</button>
</div>
</BaseDialog>

View File

@@ -1,87 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
/**
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default React.createClass({
displayName: 'SetDisplayNameDialog',
propTypes: {
onFinished: React.PropTypes.func.isRequired,
currentDisplayName: React.PropTypes.string,
},
getInitialState: function() {
if (this.props.currentDisplayName) {
return { value: this.props.currentDisplayName };
}
if (MatrixClientPeg.get().isGuest()) {
return { value : "Guest " + MatrixClientPeg.get().getUserIdLocalpart() };
}
else {
return { value : MatrixClientPeg.get().getUserIdLocalpart() };
}
},
componentDidMount: function() {
this.refs.input_value.select();
},
onValueChange: function(ev) {
this.setState({
value: ev.target.value
});
},
onFormSubmit: function(ev) {
ev.preventDefault();
this.props.onFinished(true, this.state.value);
return false;
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className="mx_SetDisplayNameDialog"
onFinished={this.props.onFinished}
title="Set a Display Name"
>
<div className="mx_Dialog_content">
Your display name is how you'll appear to others when you speak in rooms.<br/>
What would you like it to be?
</div>
<form onSubmit={this.onFormSubmit}>
<div className="mx_Dialog_content">
<input type="text" ref="input_value" value={this.state.value}
autoFocus={true} onChange={this.onValueChange} size="30"
className="mx_SetDisplayNameDialog_input"
/>
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary" type="submit" value="Set" />
</div>
</form>
</BaseDialog>
);
},
});

View File

@@ -0,0 +1,294 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import q from 'q';
import React from 'react';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import classnames from 'classnames';
import KeyCode from '../../../KeyCode';
import { _t, _tJsx } from '../../../languageHandler';
// The amount of time to wait for further changes to the input username before
// sending a request to the server
const USERNAME_CHECK_DEBOUNCE_MS = 250;
/**
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default React.createClass({
displayName: 'SetMxIdDialog',
propTypes: {
onFinished: React.PropTypes.func.isRequired,
// Called when the user requests to register with a different homeserver
onDifferentServerClicked: React.PropTypes.func.isRequired,
// Called if the user wants to switch to login instead
onLoginClick: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
// The entered username
username: '',
// Indicate ongoing work on the username
usernameBusy: false,
// Indicate error with username
usernameError: '',
// Assume the homeserver supports username checking until "M_UNRECOGNIZED"
usernameCheckSupport: true,
// Whether the auth UI is currently being used
doingUIAuth: false,
// Indicate error with auth
authError: '',
};
},
componentDidMount: function() {
this.refs.input_value.select();
this._matrixClient = MatrixClientPeg.get();
},
onValueChange: function(ev) {
this.setState({
username: ev.target.value,
usernameBusy: true,
usernameError: '',
}, () => {
if (!this.state.username || !this.state.usernameCheckSupport) {
this.setState({
usernameBusy: false,
});
return;
}
// Debounce the username check to limit number of requests sent
if (this._usernameCheckTimeout) {
clearTimeout(this._usernameCheckTimeout);
}
this._usernameCheckTimeout = setTimeout(() => {
this._doUsernameCheck().finally(() => {
this.setState({
usernameBusy: false,
});
});
}, USERNAME_CHECK_DEBOUNCE_MS);
});
},
onKeyUp: function(ev) {
if (ev.keyCode === KeyCode.ENTER) {
this.onSubmit();
}
},
onSubmit: function(ev) {
this.setState({
doingUIAuth: true,
});
},
_doUsernameCheck: function() {
// Check if username is available
return this._matrixClient.isUsernameAvailable(this.state.username).then(
(isAvailable) => {
if (isAvailable) {
this.setState({usernameError: ''});
}
},
(err) => {
// Indicate whether the homeserver supports username checking
const newState = {
usernameCheckSupport: err.errcode !== "M_UNRECOGNIZED",
};
console.error('Error whilst checking username availability: ', err);
switch (err.errcode) {
case "M_USER_IN_USE":
newState.usernameError = _t('Username not available');
break;
case "M_INVALID_USERNAME":
newState.usernameError = _t(
'Username invalid: %(errMessage)s',
{ errMessage: err.message},
);
break;
case "M_UNRECOGNIZED":
// This homeserver doesn't support username checking, assume it's
// fine and rely on the error appearing in registration step.
newState.usernameError = '';
break;
case undefined:
newState.usernameError = _t('Something went wrong!');
break;
default:
newState.usernameError = _t(
'An error occurred: %(error_string)s',
{ error_string: err.message },
);
break;
}
this.setState(newState);
},
);
},
_generatePassword: function() {
return Math.random().toString(36).slice(2);
},
_makeRegisterRequest: function(auth) {
// Not upgrading - changing mxids
const guestAccessToken = null;
if (!this._generatedPassword) {
this._generatedPassword = this._generatePassword();
}
return this._matrixClient.register(
this.state.username,
this._generatedPassword,
undefined, // session id: included in the auth dict already
auth,
{},
guestAccessToken,
);
},
_onUIAuthFinished: function(success, response) {
this.setState({
doingUIAuth: false,
});
if (!success) {
this.setState({ authError: response.message });
return;
}
// XXX Implement RTS /register here
const teamToken = null;
this.props.onFinished(true, {
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: this._matrixClient.getHomeserverUrl(),
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
password: this._generatedPassword,
teamToken: teamToken,
});
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
const Spinner = sdk.getComponent('elements.Spinner');
let auth;
if (this.state.doingUIAuth) {
auth = <InteractiveAuth
matrixClient={this._matrixClient}
makeRequest={this._makeRegisterRequest}
onAuthFinished={this._onUIAuthFinished}
inputs={{}}
poll={true}
/>;
}
const inputClasses = classnames({
"mx_SetMxIdDialog_input": true,
"error": Boolean(this.state.usernameError),
});
let usernameIndicator = null;
let usernameBusyIndicator = null;
if (this.state.usernameBusy) {
usernameBusyIndicator = <Spinner w="24" h="24"/>;
} else {
const usernameAvailable = this.state.username &&
this.state.usernameCheckSupport && !this.state.usernameError;
const usernameIndicatorClasses = classnames({
"error": Boolean(this.state.usernameError),
"success": usernameAvailable,
});
usernameIndicator = <div className={usernameIndicatorClasses}>
{ usernameAvailable ? _t('Username available') : this.state.usernameError }
</div>;
}
let authErrorIndicator = null;
if (this.state.authError) {
authErrorIndicator = <div className="error">
{ this.state.authError }
</div>;
}
const canContinue = this.state.username &&
!this.state.usernameError &&
!this.state.usernameBusy;
return (
<BaseDialog className="mx_SetMxIdDialog"
onFinished={this.props.onFinished}
title="To get started, please pick a username!"
>
<div className="mx_Dialog_content">
<div className="mx_SetMxIdDialog_input_group">
<input type="text" ref="input_value" value={this.state.username}
autoFocus={true}
onChange={this.onValueChange}
onKeyUp={this.onKeyUp}
size="30"
className={inputClasses}
/>
{ usernameBusyIndicator }
</div>
{ usernameIndicator }
<p>
{ _tJsx(
'This will be your account name on the <span></span> ' +
'homeserver, or you can pick a <a>different server</a>.',
[
/<span><\/span>/,
/<a>(.*?)<\/a>/,
],
[
(sub) => <span>{this.props.homeserverUrl}</span>,
(sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{sub}</a>,
],
)}
</p>
<p>
{ _tJsx(
'If you already have a Matrix account you can <a>log in</a> instead.',
/<a>(.*?)<\/a>/,
[(sub) => <a href="#" onClick={this.props.onLoginClick}>{sub}</a>],
)}
</p>
{ auth }
{ authErrorIndicator }
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary"
type="submit"
value={_t("Continue")}
onClick={this.onSubmit}
disabled={!canContinue}
/>
</div>
</BaseDialog>
);
},
});

View File

@@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default React.createClass({
displayName: 'TextInputDialog',
@@ -36,7 +37,6 @@ export default React.createClass({
title: "",
value: "",
description: "",
button: "OK",
focus: true,
};
},
@@ -73,7 +73,7 @@ export default React.createClass({
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onCancel}>
Cancel
{ _t("Cancel") }
</button>
<button className="mx_Dialog_primary" onClick={this.onOk}>
{this.props.button}

View File

@@ -16,10 +16,10 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import MatrixClientPeg from '../../../MatrixClientPeg';
import GeminiScrollbar from 'react-gemini-scrollbar';
import Resend from '../../../Resend';
import { _t } from '../../../languageHandler';
function DeviceListEntry(props) {
const {userId, device} = props;
@@ -120,17 +120,17 @@ export default React.createClass({
if (blacklistUnverified) {
warning = (
<h4>
You are currently blacklisting unverified devices; to send
messages to these devices you must verify them.
{_t("You are currently blacklisting unverified devices; to send " +
"messages to these devices you must verify them.")}
</h4>
);
} else {
warning = (
<div>
<p>
We recommend you go through the verification process
for each device to confirm they belong to their legitimate owner,
but you can resend the message without verifying if you prefer.
{_t("We recommend you go through the verification process " +
"for each device to confirm they belong to their legitimate owner, " +
"but you can resend the message without verifying if you prefer.")}
</p>
</div>
);
@@ -145,14 +145,14 @@ export default React.createClass({
console.log("UnknownDeviceDialog closed by escape");
this.props.onFinished();
}}
title='Room contains unknown devices'
title={_t('Room contains unknown devices')}
>
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
<h4>
This room contains devices that you haven't seen before.
{_t('"%(RoomName)s" contains devices that you haven\'t seen before.', {RoomName: this.props.room.name})}
</h4>
{ warning }
Unknown devices:
{_t("Unknown devices")}:
<UnknownDeviceList devices={this.props.devices} />
</GeminiScrollbar>
@@ -162,7 +162,7 @@ export default React.createClass({
this.props.onFinished();
Resend.resendUnsentEvents(this.props.room);
}}>
Send anyway
{_t("Send anyway")}
</button>
<button className="mx_Dialog_primary" autoFocus={ true }
onClick={() => {

View File

@@ -32,6 +32,8 @@ export default function AccessibleButton(props) {
};
restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
restProps.className = (restProps.className ? restProps.className + " " : "") +
"mx_AccessibleButton";
return React.createElement(element, restProps, children);
}

View File

@@ -0,0 +1,84 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher';
import sdk from '../../../index';
export default React.createClass({
displayName: 'RoleButton',
propTypes: {
size: PropTypes.string,
tooltip: PropTypes.bool,
action: PropTypes.string.isRequired,
mouseOverAction: PropTypes.string,
label: PropTypes.string.isRequired,
iconPath: PropTypes.string.isRequired,
},
getDefaultProps: function() {
return {
size: "25",
tooltip: false,
};
},
getInitialState: function() {
return {
showTooltip: false,
};
},
_onClick: function(ev) {
ev.stopPropagation();
dis.dispatch({action: this.props.action});
},
_onMouseEnter: function() {
if (this.props.tooltip) this.setState({showTooltip: true});
if (this.props.mouseOverAction) {
dis.dispatch({action: this.props.mouseOverAction});
}
},
_onMouseLeave: function() {
this.setState({showTooltip: false});
},
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let tooltip;
if (this.state.showTooltip) {
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
tooltip = <RoomTooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
}
return (
<AccessibleButton className="mx_RoleButton"
onClick={this._onClick}
onMouseEnter={this._onMouseEnter}
onMouseLeave={this._onMouseLeave}
>
<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />
{tooltip}
</AccessibleButton>
);
}
});

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -138,7 +139,7 @@ export default React.createClass({
onClick={this.onClick.bind(this, i)}
onMouseEnter={this.onMouseEnter.bind(this, i)}
onMouseLeave={this.onMouseLeave}
key={this.props.addressList[i].userId}
key={this.props.addressList[i].addressType + "/" + this.props.addressList[i].address}
ref={(ref) => { this.addressListElement = ref; }}
>
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />

View File

@@ -16,12 +16,11 @@ limitations under the License.
'use strict';
var React = require('react');
var classNames = require('classnames');
var sdk = require("../../../index");
var Invite = require("../../../Invite");
var MatrixClientPeg = require("../../../MatrixClientPeg");
var Avatar = require('../../../Avatar');
import React from 'react';
import classNames from 'classnames';
import sdk from "../../../index";
import MatrixClientPeg from "../../../MatrixClientPeg";
import { _t } from '../../../languageHandler';
// React PropType definition for an object describing
// an address that can be invited to a room (which
@@ -142,7 +141,7 @@ export default React.createClass({
});
info = (
<div className={unknownClasses}>Unknown Address</div>
<div className={unknownClasses}>{_t("Unknown Address")}</div>
);
}

View File

@@ -0,0 +1,40 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
const CreateRoomButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_create_room"
mouseOverAction={props.callout ? "callout_create_room" : null}
label={ _t("Create new room") }
iconPath="img/icons-create-room.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
CreateRoomButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default CreateRoomButton;

View File

@@ -18,6 +18,7 @@ import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default React.createClass({
displayName: 'DeviceVerifyButtons',
@@ -50,42 +51,10 @@ export default React.createClass({
},
onVerifyClick: function() {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Verify device",
description: (
<div>
<p>
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:
</p>
<div className="mx_UserSettings_cryptoSection">
<ul>
<li><label>Device name:</label> <span>{ this.state.device.getDisplayName() }</span></li>
<li><label>Device ID:</label> <span><code>{ this.state.device.deviceId}</code></span></li>
<li><label>Device key:</label> <span><code><b>{ this.state.device.getFingerprint() }</b></code></span></li>
</ul>
</div>
<p>
If it matches, press the verify button below.
If it doesnt, then someone else is intercepting this device
and you probably want to press the blacklist button instead.
</p>
<p>
In future this verification process will be more sophisticated.
</p>
</div>
),
button: "I verify that the keys match",
onFinished: confirm=>{
if (confirm) {
MatrixClientPeg.get().setDeviceVerified(
this.props.userId, this.state.device.deviceId, true
);
}
},
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
Modal.createDialog(DeviceVerifyDialog, {
userId: this.props.userId,
device: this.state.device,
});
},
@@ -114,14 +83,14 @@ export default React.createClass({
blacklistButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblacklist"
onClick={this.onUnblacklistClick}>
Unblacklist
{_t("Unblacklist")}
</button>
);
} else {
blacklistButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_blacklist"
onClick={this.onBlacklistClick}>
Blacklist
{_t("Blacklist")}
</button>
);
}
@@ -130,14 +99,14 @@ export default React.createClass({
verifyButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
onClick={this.onUnverifyClick}>
Unverify
{_t("Unverify")}
</button>
);
} else {
verifyButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
onClick={this.onVerifyClick}>
Verify...
{_t("Verify...")}
</button>
);
}

View File

@@ -93,7 +93,7 @@ export default class DirectorySearchBox extends React.Component {
className="mx_DirectorySearchBox_input"
ref={this._collectInput}
onChange={this._onChange} onKeyUp={this._onKeyUp}
placeholder={this.props.placeholder}
placeholder={this.props.placeholder} autoFocus
/>
{join_button}
<span className="mx_DirectorySearchBox_clear_wrapper">

View File

@@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import classnames from 'classnames';
import AccessibleButton from './AccessibleButton';
import { _t } from '../../../languageHandler';
class MenuOption extends React.Component {
constructor(props) {
@@ -114,8 +115,11 @@ export default class Dropdown extends React.Component {
}
componentWillReceiveProps(nextProps) {
if (!nextProps.children || nextProps.children.length === 0) {
return;
}
this._reindexChildren(nextProps.children);
const firstChild = React.Children.toArray(nextProps.children)[0];
const firstChild = nextProps.children[0];
this.setState({
highlightedOption: firstChild ? firstChild.key : null,
});
@@ -149,10 +153,12 @@ export default class Dropdown extends React.Component {
}
_onInputClick(ev) {
this.setState({
expanded: !this.state.expanded,
});
ev.preventDefault();
if (!this.state.expanded) {
this.setState({
expanded: true,
});
ev.preventDefault();
}
}
_onMenuOptionClick(dropdownKey) {
@@ -248,13 +254,10 @@ export default class Dropdown extends React.Component {
</MenuOption>
);
});
if (!this.state.searchQuery) {
options.push(
<div key="_searchprompt" className="mx_Dropdown_searchPrompt">
Type to search...
</div>
);
if (options.length === 0) {
return [<div key="0" className="mx_Dropdown_option">
{_t("No results")}
</div>];
}
return options;
}
@@ -267,16 +270,20 @@ export default class Dropdown extends React.Component {
let menu;
if (this.state.expanded) {
currentValue = <input type="text" className="mx_Dropdown_option"
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
onKeyUp={this._onInputKeyUp}
onChange={this._onInputChange}
value={this.state.searchQuery}
/>;
if (this.props.searchEnabled) {
currentValue = <input type="text" className="mx_Dropdown_option"
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
onKeyUp={this._onInputKeyUp}
onChange={this._onInputChange}
value={this.state.searchQuery}
/>;
}
menu = <div className="mx_Dropdown_menu" style={menuStyle}>
{this._getMenuOptions()}
</div>;
} else {
}
if (!currentValue) {
const selectedChild = this.props.getShortOption ?
this.props.getShortOption(this.props.value) :
this.childrenByKey[this.props.value];
@@ -313,6 +320,7 @@ Dropdown.propTypes = {
onOptionChange: React.PropTypes.func.isRequired,
// Called when the value of the search field changes
onSearchChange: React.PropTypes.func,
searchEnabled: React.PropTypes.bool,
// Function that, given the key of an option, returns
// a node representing that option to be displayed in the
// box itself as the currently-selected option (ie. as

View File

@@ -0,0 +1,39 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
const HomeButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_home_page"
label={ _t("Home") }
iconPath="img/icons-home.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
HomeButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default HomeButton;

View File

@@ -0,0 +1,120 @@
/*
Copyright 2017 Marcel Radzio (MTRNord)
Copyright 2017 Vector Creations Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import UserSettingsStore from '../../../UserSettingsStore';
import * as languageHandler from '../../../languageHandler';
function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
if (language.value.toUpperCase() == query.toUpperCase()) return true;
return false;
}
export default class LanguageDropdown extends React.Component {
constructor(props) {
super(props);
this._onSearchChange = this._onSearchChange.bind(this);
this.state = {
searchQuery: '',
langs: null,
}
}
componentWillMount() {
languageHandler.getAllLanguagesFromJson().then((langs) => {
langs.sort(function(a, b){
if(a.label < b.label) return -1;
if(a.label > b.label) return 1;
return 0;
});
this.setState({langs});
}).catch(() => {
this.setState({langs: ['en']});
}).done();
if (!this.props.value) {
// If no value is given, we start with the first
// country selected, but our parent component
// doesn't know this, therefore we do this.
const _localSettings = UserSettingsStore.getLocalSettings();
if (_localSettings.hasOwnProperty('language')) {
this.props.onOptionChange(_localSettings.language);
}else {
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
this.props.onOptionChange(language);
}
}
}
_onSearchChange(search) {
this.setState({
searchQuery: search,
});
}
render() {
if (this.state.langs === null) {
const Spinner = sdk.getComponent('elements.Spinner');
return <Spinner />;
}
const Dropdown = sdk.getComponent('elements.Dropdown');
let displayedLanguages;
if (this.state.searchQuery) {
displayedLanguages = this.state.langs.filter((lang) => {
return languageMatchesSearchQuery(this.state.searchQuery, lang);
});
} else {
displayedLanguages = this.state.langs;
}
const options = displayedLanguages.map((language) => {
return <div key={language.value}>
{language.label}
</div>;
});
// default value here too, otherwise we need to handle null / undefined
// values between mounting and the initial value propgating
let value = null;
const _localSettings = UserSettingsStore.getLocalSettings();
if (_localSettings.hasOwnProperty('language')) {
value = this.props.value || _localSettings.language;
} else {
const language = navigator.language || navigator.userLanguage;
value = this.props.value || language;
}
return <Dropdown className={this.props.className}
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
searchEnabled={true} value={value}
>
{options}
</Dropdown>
}
}
LanguageDropdown.propTypes = {
className: React.PropTypes.string,
onOptionChange: React.PropTypes.func.isRequired,
value: React.PropTypes.string,
};

View File

@@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
const MemberAvatar = require('../avatars/MemberAvatar.js');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'MemberEventListSummary',
@@ -110,9 +112,13 @@ module.exports = React.createClass({
return null;
}
const EmojiText = sdk.getComponent('elements.EmojiText');
return (
<span className="mx_TextualEvent mx_MemberEventListSummary_summary">
{summaries.join(", ")}
<EmojiText>
{summaries.join(", ")}
</EmojiText>
</span>
);
},
@@ -203,28 +209,146 @@ module.exports = React.createClass({
* @param {boolean} plural whether there were multiple users undergoing the same
* transition.
* @param {number} repeats the number of times the transition was repeated in a row.
* @returns {string} the written English equivalent of the transition.
* @returns {string} the written Human Readable equivalent of the transition.
*/
_getDescriptionForTransition(t, plural, repeats) {
const beConjugated = plural ? "were" : "was";
const invitation = "their invitation" + (plural || (repeats > 1) ? "s" : "");
// The empty interpolations 'severalUsers' and 'oneUser'
// are there only to show translators to non-English languages
// that the verb is conjugated to plural or singular Subject.
let res = null;
const map = {
"joined": "joined",
"left": "left",
"joined_and_left": "joined and left",
"left_and_joined": "left and rejoined",
"invite_reject": "rejected " + invitation,
"invite_withdrawal": "had " + invitation + " withdrawn",
"invited": beConjugated + " invited",
"banned": beConjugated + " banned",
"unbanned": beConjugated + " unbanned",
"kicked": beConjugated + " kicked",
};
switch(t) {
case "joined":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)sjoined", { severalUsers: "" })
: _t("%(oneUser)sjoined", { oneUser: "" });
}
if (Object.keys(map).includes(t)) {
res = map[t] + (repeats > 1 ? " " + repeats + " times" : "" );
break;
case "left":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)sleft %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)sleft %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)sleft", { severalUsers: "" })
: _t("%(oneUser)sleft", { oneUser: "" });
} break;
case "joined_and_left":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)sjoined and left %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)sjoined and left %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)sjoined and left", { severalUsers: "" })
: _t("%(oneUser)sjoined and left", { oneUser: "" });
}
break;
case "left_and_joined":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)sleft and rejoined %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)sleft and rejoined %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" })
: _t("%(oneUser)sleft and rejoined", { oneUser: "" });
} break;
break;
case "invite_reject":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)srejected their invitations %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)srejected their invitation %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)srejected their invitations", { severalUsers: "" })
: _t("%(oneUser)srejected their invitation", { oneUser: "" });
}
break;
case "invite_withdrawal":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)shad their invitations withdrawn %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)shad their invitation withdrawn %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" })
: _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" });
}
break;
case "invited":
if (repeats > 1) {
res = (plural)
? _t("were invited %(repeats)s times", { repeats: repeats })
: _t("was invited %(repeats)s times", { repeats: repeats });
} else {
res = (plural)
? _t("were invited")
: _t("was invited");
}
break;
case "banned":
if (repeats > 1) {
res = (plural)
? _t("were banned %(repeats)s times", { repeats: repeats })
: _t("was banned %(repeats)s times", { repeats: repeats });
} else {
res = (plural)
? _t("were banned")
: _t("was banned");
}
break;
case "unbanned":
if (repeats > 1) {
res = (plural)
? _t("were unbanned %(repeats)s times", { repeats: repeats })
: _t("was unbanned %(repeats)s times", { repeats: repeats });
} else {
res = (plural)
? _t("were unbanned")
: _t("was unbanned");
}
break;
case "kicked":
if (repeats > 1) {
res = (plural)
? _t("were kicked %(repeats)s times", { repeats: repeats })
: _t("was kicked %(repeats)s times", { repeats: repeats });
} else {
res = (plural)
? _t("were kicked")
: _t("was kicked");
}
break;
case "changed_name":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)schanged their name %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)schanged their name %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)schanged their name", { severalUsers: "" })
: _t("%(oneUser)schanged their name", { oneUser: "" });
}
break;
case "changed_avatar":
if (repeats > 1) {
res = (plural)
? _t("%(severalUsers)schanged their avatar %(repeats)s times", { severalUsers: "", repeats: repeats })
: _t("%(oneUser)schanged their avatar %(repeats)s times", { oneUser: "", repeats: repeats });
} else {
res = (plural)
? _t("%(severalUsers)schanged their avatar", { severalUsers: "" })
: _t("%(oneUser)schanged their avatar", { oneUser: "" });
}
break;
}
return res;
@@ -252,11 +376,12 @@ module.exports = React.createClass({
return items[0];
} else if (remaining) {
items = items.slice(0, itemLimit);
const other = " other" + (remaining > 1 ? "s" : "");
return items.join(', ') + ' and ' + remaining + other;
return (remaining > 1)
? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } )
: _t("%(items)s and one other", { items: items.join(', ') });
} else {
const lastItem = items.pop();
return items.join(', ') + ' and ' + lastItem;
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
}
},
@@ -267,7 +392,7 @@ module.exports = React.createClass({
);
});
return (
<span className="mx_MemberEventListSummary_avatars">
<span className="mx_MemberEventListSummary_avatars" onClick={ this._toggleSummary }>
{avatars}
</span>
);
@@ -289,7 +414,24 @@ module.exports = React.createClass({
switch (e.mxEvent.getContent().membership) {
case 'invite': return 'invited';
case 'ban': return 'banned';
case 'join': return 'joined';
case 'join':
if (e.mxEvent.getPrevContent().membership === 'join') {
if (e.mxEvent.getContent().displayname !==
e.mxEvent.getPrevContent().displayname)
{
return 'changed_name';
}
else if (e.mxEvent.getContent().avatar_url !==
e.mxEvent.getPrevContent().avatar_url)
{
return 'changed_avatar';
}
// console.log("MELS ignoring duplicate membership join event");
return null;
}
else {
return 'joined';
}
case 'leave':
if (e.mxEvent.getSender() === e.mxEvent.getStateKey()) {
switch (e.mxEvent.getPrevContent().membership) {
@@ -350,6 +492,7 @@ module.exports = React.createClass({
render: function() {
const eventsToRender = this.props.events;
const eventIds = eventsToRender.map(e => e.getId()).join(',');
const fewEvents = eventsToRender.length < this.props.threshold;
const expanded = this.state.expanded || fewEvents;
@@ -360,7 +503,7 @@ module.exports = React.createClass({
if (fewEvents) {
return (
<div className="mx_MemberEventListSummary">
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
{expandedEvents}
</div>
);
@@ -418,7 +561,7 @@ module.exports = React.createClass({
);
return (
<div className="mx_MemberEventListSummary">
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
{toggleButton}
{summaryContainer}
{expanded ? <div className="mx_MemberEventListSummary_line">&nbsp;</div> : null}

View File

@@ -16,18 +16,12 @@ limitations under the License.
'use strict';
var React = require('react');
var roles = {
0: 'User',
50: 'Moderator',
100: 'Admin',
};
import React from 'react';
import * as Roles from '../../../Roles';
import { _t } from '../../../languageHandler';
var LEVEL_ROLE_MAP = {};
var reverseRoles = {};
Object.keys(roles).forEach(function(key) {
reverseRoles[roles[key]] = key;
});
module.exports = React.createClass({
displayName: 'PowerSelector',
@@ -49,9 +43,16 @@ module.exports = React.createClass({
getInitialState: function() {
return {
custom: (roles[this.props.value] === undefined),
custom: (LEVEL_ROLE_MAP[this.props.value] === undefined),
};
},
componentWillMount: function() {
LEVEL_ROLE_MAP = Roles.levelRoleMap();
Object.keys(LEVEL_ROLE_MAP).forEach(function(key) {
reverseRoles[LEVEL_ROLE_MAP[key]] = key;
});
},
onSelectChange: function(event) {
this.setState({ custom: event.target.value === "Custom" });
@@ -99,22 +100,34 @@ module.exports = React.createClass({
selectValue = "Custom";
}
else {
selectValue = roles[this.props.value] || "Custom";
selectValue = LEVEL_ROLE_MAP[this.props.value] || "Custom";
}
var select;
if (this.props.disabled) {
select = <span>{ selectValue }</span>;
}
else {
// Each level must have a definition in LEVEL_ROLE_MAP
const levels = [0, 50, 100];
let options = levels.map((level) => {
return {
value: LEVEL_ROLE_MAP[level],
// Give a userDefault (users_default in the power event) of 0 but
// because level !== undefined, this should never be used.
text: Roles.textualPowerLevel(level, 0),
}
});
options.push({ value: "Custom", text: _t("Custom level") });
options = options.map((op) => {
return <option value={op.value} key={op.value}>{op.text}</option>;
});
select =
<select ref="select"
value={ this.props.controlled ? selectValue : undefined }
defaultValue={ !this.props.controlled ? selectValue : undefined }
onChange={ this.onSelectChange }>
<option value="User">User (0)</option>
<option value="Moderator">Moderator (50)</option>
<option value="Admin">Admin (100)</option>
<option value="Custom">Custom level</option>
{ options }
</select>;
}

View File

@@ -0,0 +1,40 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
const RoomDirectoryButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_room_directory"
mouseOverAction={props.callout ? "callout_room_directory" : null}
label={ _t("Room directory") }
iconPath="img/icons-directory.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
RoomDirectoryButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default RoomDirectoryButton;

View File

@@ -0,0 +1,39 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
const SettingsButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_user_settings"
label={ _t("Settings") }
iconPath="img/icons-settings.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
SettingsButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default SettingsButton;

View File

@@ -0,0 +1,40 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
const StartChatButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_create_chat"
mouseOverAction={props.callout ? "callout_start_chat" : null}
label={ _t("Start chat") }
iconPath="img/icons-people.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
StartChatButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default StartChatButton;

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'TruncatedList',
@@ -33,7 +34,7 @@ module.exports = React.createClass({
truncateAt: 2,
createOverflowElement: function(overflowCount, totalCount) {
return (
<div>And {overflowCount} more...</div>
<div>{_t("And %(count)s more...", {count: overflowCount})}</div>
);
}
};

View File

@@ -16,7 +16,8 @@ limitations under the License.
'use strict';
var React = require('react');
import React from 'react';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'UserSelector',
@@ -59,9 +60,9 @@ module.exports = React.createClass({
return <li key={user_id}>{user_id} - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>;
})}
</ul>
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder="ex. @bob:example.com"/>
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder={_t("ex. @bob:example.com")}/>
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">
Add User
{_t("Add User")}
</button>
</div>
);

View File

@@ -16,7 +16,9 @@ limitations under the License.
'use strict';
var React = require('react');
import React from 'react';
import { _t } from '../../../languageHandler';
var DIV_ID = 'mx_recaptcha';
/**
@@ -117,7 +119,7 @@ module.exports = React.createClass({
return (
<div ref="recaptchaContainer">
This Home Server would like to make sure you are not a robot
{_t("This Home Server would like to make sure you are not a robot")}
<br/>
<div id={DIV_ID}></div>
{error}

View File

@@ -16,7 +16,8 @@ limitations under the License.
'use strict';
var React = require('react');
import React from 'react';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'CasLogin',
@@ -28,7 +29,7 @@ module.exports = React.createClass({
render: function() {
return (
<div>
<button onClick={this.props.onSubmit}>Sign in with CAS</button>
<button onClick={this.props.onSubmit}>{_t("Sign in with CAS")}</button>
</div>
);
}

View File

@@ -19,7 +19,6 @@ import React from 'react';
import sdk from '../../../index';
import { COUNTRIES } from '../../../phonenumber';
import { charactersToImageNode } from '../../../HtmlUtils';
const COUNTRIES_BY_ISO2 = new Object(null);
for (const c of COUNTRIES) {
@@ -27,22 +26,27 @@ for (const c of COUNTRIES) {
}
function countryMatchesSearchQuery(query, country) {
// Remove '+' if present (when searching for a prefix)
if (query[0] === '+') {
query = query.slice(1);
}
if (country.name.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
if (country.iso2 == query.toUpperCase()) return true;
if (country.prefix == query) return true;
if (country.prefix.indexOf(query) !== -1) return true;
return false;
}
const MAX_DISPLAYED_ROWS = 2;
export default class CountryDropdown extends React.Component {
constructor(props) {
super(props);
this._onSearchChange = this._onSearchChange.bind(this);
this._onOptionChange = this._onOptionChange.bind(this);
this._getShortOption = this._getShortOption.bind(this);
this.state = {
searchQuery: '',
}
};
}
componentWillMount() {
@@ -50,7 +54,7 @@ export default class CountryDropdown extends React.Component {
// If no value is given, we start with the first
// country selected, but our parent component
// doesn't know this, therefore we do this.
this.props.onOptionChange(COUNTRIES[0].iso2);
this.props.onOptionChange(COUNTRIES[0]);
}
}
@@ -60,14 +64,26 @@ export default class CountryDropdown extends React.Component {
});
}
_onOptionChange(iso2) {
this.props.onOptionChange(COUNTRIES_BY_ISO2[iso2]);
}
_flagImgForIso2(iso2) {
// Unicode Regional Indicator Symbol letter 'A'
const RIS_A = 0x1F1E6;
const ASCII_A = 65;
return charactersToImageNode(iso2,
RIS_A + (iso2.charCodeAt(0) - ASCII_A),
RIS_A + (iso2.charCodeAt(1) - ASCII_A),
);
return <img src={`flags/${iso2}.png`}/>;
}
_getShortOption(iso2) {
if (!this.props.isSmall) {
return undefined;
}
let countryPrefix;
if (this.props.showPrefix) {
countryPrefix = '+' + COUNTRIES_BY_ISO2[iso2].prefix;
}
return <span>
{ this._flagImgForIso2(iso2) }
{ countryPrefix }
</span>;
}
render() {
@@ -93,14 +109,10 @@ export default class CountryDropdown extends React.Component {
displayedCountries = COUNTRIES;
}
if (displayedCountries.length > MAX_DISPLAYED_ROWS) {
displayedCountries = displayedCountries.slice(0, MAX_DISPLAYED_ROWS);
}
const options = displayedCountries.map((country) => {
return <div key={country.iso2}>
{this._flagImgForIso2(country.iso2)}
{country.name}
{country.name} <span>(+{country.prefix})</span>
</div>;
});
@@ -108,18 +120,21 @@ export default class CountryDropdown extends React.Component {
// values between mounting and the initial value propgating
const value = this.props.value || COUNTRIES[0].iso2;
return <Dropdown className={this.props.className}
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
menuWidth={298} getShortOption={this._flagImgForIso2}
value={value}
return <Dropdown className={this.props.className + " left_aligned"}
onOptionChange={this._onOptionChange} onSearchChange={this._onSearchChange}
menuWidth={298} getShortOption={this._getShortOption}
value={value} searchEnabled={true}
>
{options}
</Dropdown>
</Dropdown>;
}
}
CountryDropdown.propTypes = {
className: React.PropTypes.string,
isSmall: React.PropTypes.bool,
// if isSmall, show +44 in the selected value
showPrefix: React.PropTypes.bool,
onOptionChange: React.PropTypes.func.isRequired,
value: React.PropTypes.string,
};

View File

@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require("react");
import React from 'react';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'CustomServerDialog',
@@ -23,24 +24,24 @@ module.exports = React.createClass({
return (
<div className="mx_ErrorDialog">
<div className="mx_Dialog_title">
Custom Server Options
{_t("Custom Server Options")}
</div>
<div className="mx_Dialog_content">
<span>
You can use the custom server options to sign into other Matrix
servers by specifying a different Home server URL.
{_t("You can use the custom server options to sign into other Matrix " +
"servers by specifying a different Home server URL.")}
<br/>
This allows you to use this app with an existing Matrix account on
a different home server.
{_t("This allows you to use this app with an existing Matrix account on " +
"a different home server.")}
<br/>
<br/>
You can also set a custom identity server but this will typically prevent
interaction with users based on email address.
{_t("You can also set a custom identity server but this will typically prevent " +
"interaction with users based on email address.")}
</span>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.props.onFinished} autoFocus={true}>
Dismiss
{_t("Dismiss")}
</button>
</div>
</div>

View File

@@ -20,6 +20,7 @@ import url from 'url';
import classnames from 'classnames';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
/* This file contains a collection of components which are used by the
* InteractiveAuth to prompt the user to enter the information needed
@@ -128,8 +129,8 @@ export const PasswordAuthEntry = React.createClass({
return (
<div>
<p>To continue, please enter your password.</p>
<p>Password:</p>
<p>{_t("To continue, please enter your password.")}</p>
<p>{_t("Password:")}</p>
<form onSubmit={this._onSubmit}>
<input
ref="passwordField"
@@ -255,8 +256,8 @@ export const EmailIdentityAuthEntry = React.createClass({
} else {
return (
<div>
<p>An email has been sent to <i>{this.props.inputs.emailAddress}</i></p>
<p>Please check your email to continue registration.</p>
<p>{_t("An email has been sent to")} <i>{this.props.inputs.emailAddress}</i></p>
<p>{_t("Please check your email to continue registration.")}</p>
</div>
);
}
@@ -348,7 +349,7 @@ export const MsisdnAuthEntry = React.createClass({
});
} else {
this.setState({
errorText: "Token incorrect",
errorText: _t("Token incorrect"),
});
}
}).catch((e) => {
@@ -369,8 +370,8 @@ export const MsisdnAuthEntry = React.createClass({
});
return (
<div>
<p>A text message has been sent to +<i>{this._msisdn}</i></p>
<p>Please enter the code it contains:</p>
<p>{_t("A text message has been sent to")} +<i>{this._msisdn}</i></p>
<p>{_t("Please enter the code it contains:")}</p>
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
<form onSubmit={this._onFormSubmit}>
<input type="text"
@@ -379,7 +380,7 @@ export const MsisdnAuthEntry = React.createClass({
onChange={this._onTokenChange}
/>
<br />
<input type="submit" value="Submit"
<input type="submit" value={_t("Submit")}
className={submitClasses}
disabled={!enableSubmit}
/>
@@ -439,7 +440,7 @@ export const FallbackAuthEntry = React.createClass({
render: function() {
return (
<div>
<a onClick={this._onShowFallbackClick}>Start authentication</a>
<a onClick={this._onShowFallbackClick}>{_t("Start authentication")}</a>
<div className="error">
{this.props.errorText}
</div>

View File

@@ -16,7 +16,8 @@ limitations under the License.
'use strict';
var React = require('react');
import { _t } from '../../../languageHandler';
import React from 'react';
module.exports = React.createClass({
displayName: 'LoginFooter',
@@ -24,8 +25,8 @@ module.exports = React.createClass({
render: function() {
return (
<div className="mx_Login_links">
<a href="https://matrix.org">powered by Matrix</a>
<a href="https://matrix.org">{_t("powered by Matrix")}</a>
</div>
);
}
},
});

View File

@@ -16,64 +16,58 @@ limitations under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {field_input_incorrect} from '../../../UiEffects';
/**
* A pure UI component which displays a username/password form.
*/
module.exports = React.createClass({displayName: 'PasswordLogin',
propTypes: {
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
onForgotPasswordClick: React.PropTypes.func, // fn()
initialUsername: React.PropTypes.string,
initialPhoneCountry: React.PropTypes.string,
initialPhoneNumber: React.PropTypes.string,
initialPassword: React.PropTypes.string,
onUsernameChanged: React.PropTypes.func,
onPhoneCountryChanged: React.PropTypes.func,
onPhoneNumberChanged: React.PropTypes.func,
onPasswordChanged: React.PropTypes.func,
loginIncorrect: React.PropTypes.bool,
},
class PasswordLogin extends React.Component {
static defaultProps = {
onUsernameChanged: function() {},
onPasswordChanged: function() {},
onPhoneCountryChanged: function() {},
onPhoneNumberChanged: function() {},
initialUsername: "",
initialPhoneCountry: "",
initialPhoneNumber: "",
initialPassword: "",
loginIncorrect: false,
hsDomain: "",
}
getDefaultProps: function() {
return {
onUsernameChanged: function() {},
onPasswordChanged: function() {},
onPhoneCountryChanged: function() {},
onPhoneNumberChanged: function() {},
initialUsername: "",
initialPhoneCountry: "",
initialPhoneNumber: "",
initialPassword: "",
loginIncorrect: false,
};
},
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
username: this.props.initialUsername,
password: this.props.initialPassword,
phoneCountry: this.props.initialPhoneCountry,
phoneNumber: this.props.initialPhoneNumber,
loginType: PasswordLogin.LOGIN_FIELD_MXID,
};
},
componentWillMount: function() {
this.onSubmitForm = this.onSubmitForm.bind(this);
this.onUsernameChanged = this.onUsernameChanged.bind(this);
this.onLoginTypeChange = this.onLoginTypeChange.bind(this);
this.onPhoneCountryChanged = this.onPhoneCountryChanged.bind(this);
this.onPhoneNumberChanged = this.onPhoneNumberChanged.bind(this);
this.onPasswordChanged = this.onPasswordChanged.bind(this);
}
componentWillMount() {
this._passwordField = null;
},
}
componentWillReceiveProps: function(nextProps) {
componentWillReceiveProps(nextProps) {
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
field_input_incorrect(this._passwordField);
}
},
}
onSubmitForm: function(ev) {
onSubmitForm(ev) {
ev.preventDefault();
this.props.onSubmit(
this.state.username,
@@ -81,35 +75,95 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
this.state.phoneNumber,
this.state.password,
);
},
}
onUsernameChanged: function(ev) {
onUsernameChanged(ev) {
this.setState({username: ev.target.value});
this.props.onUsernameChanged(ev.target.value);
},
}
onPhoneCountryChanged: function(country) {
this.setState({phoneCountry: country});
this.props.onPhoneCountryChanged(country);
},
onLoginTypeChange(loginType) {
this.setState({
loginType: loginType,
username: "" // Reset because email and username use the same state
});
}
onPhoneNumberChanged: function(ev) {
onPhoneCountryChanged(country) {
this.setState({
phoneCountry: country.iso2,
phonePrefix: country.prefix,
});
this.props.onPhoneCountryChanged(country.iso2);
}
onPhoneNumberChanged(ev) {
this.setState({phoneNumber: ev.target.value});
this.props.onPhoneNumberChanged(ev.target.value);
},
}
onPasswordChanged: function(ev) {
onPasswordChanged(ev) {
this.setState({password: ev.target.value});
this.props.onPasswordChanged(ev.target.value);
},
}
render: function() {
renderLoginField(loginType) {
switch(loginType) {
case PasswordLogin.LOGIN_FIELD_EMAIL:
return <input
className="mx_Login_field mx_Login_email"
key="email_input"
type="text"
name="username" // make it a little easier for browser's remember-password
onChange={this.onUsernameChanged}
placeholder="joe@example.com"
value={this.state.username}
autoFocus
/>;
case PasswordLogin.LOGIN_FIELD_MXID:
return <input
className="mx_Login_field mx_Login_username"
key="username_input"
type="text"
name="username" // make it a little easier for browser's remember-password
onChange={this.onUsernameChanged}
placeholder={_t('User name')}
value={this.state.username}
autoFocus
/>;
case PasswordLogin.LOGIN_FIELD_PHONE:
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
return <div className="mx_Login_phoneSection">
<CountryDropdown
className="mx_Login_phoneCountry mx_Login_field_prefix"
ref="phone_country"
onOptionChange={this.onPhoneCountryChanged}
value={this.state.phoneCountry}
isSmall={true}
showPrefix={true}
/>
<input
className="mx_Login_phoneNumberField mx_Login_field mx_Login_field_has_prefix"
ref="phoneNumber"
key="phone_input"
type="text"
name="phoneNumber"
onChange={this.onPhoneNumberChanged}
placeholder={_t("Mobile phone number")}
value={this.state.phoneNumber}
autoFocus
/>
</div>;
}
}
render() {
var forgotPasswordJsx;
if (this.props.onForgotPasswordClick) {
forgotPasswordJsx = (
<a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#">
Forgot your password?
{ _t('Forgot your password?') }
</a>
);
}
@@ -119,38 +173,54 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
error: this.props.loginIncorrect,
});
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
const Dropdown = sdk.getComponent('elements.Dropdown');
const loginField = this.renderLoginField(this.state.loginType);
return (
<div>
<form onSubmit={this.onSubmitForm}>
<input className="mx_Login_field mx_Login_username" type="text"
name="username" // make it a little easier for browser's remember-password
value={this.state.username} onChange={this.onUsernameChanged}
placeholder="Email or user name" autoFocus />
or
<div className="mx_Login_phoneSection">
<CountryDropdown ref="phone_country" onOptionChange={this.onPhoneCountryChanged}
className="mx_Login_phoneCountry"
value={this.state.phoneCountry}
/>
<input type="text" ref="phoneNumber"
onChange={this.onPhoneNumberChanged}
placeholder="Mobile phone number"
className="mx_Login_phoneNumberField mx_Login_field"
value={this.state.phoneNumber}
name="phoneNumber"
/>
<div className="mx_Login_type_container">
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
<Dropdown
className="mx_Login_type_dropdown"
value={this.state.loginType}
onOptionChange={this.onLoginTypeChange}>
<span key={PasswordLogin.LOGIN_FIELD_MXID}>{ _t('my Matrix ID') }</span>
<span key={PasswordLogin.LOGIN_FIELD_EMAIL}>{ _t('Email address') }</span>
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>{ _t('Phone') }</span>
</Dropdown>
</div>
<br />
{loginField}
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
name="password"
value={this.state.password} onChange={this.onPasswordChanged}
placeholder="Password" />
placeholder={ _t('Password') } />
<br />
{forgotPasswordJsx}
<input className="mx_Login_submit" type="submit" value="Sign in" />
<input className="mx_Login_submit" type="submit" value={ _t('Sign in') } />
</form>
</div>
);
}
});
}
PasswordLogin.LOGIN_FIELD_EMAIL = "login_field_email";
PasswordLogin.LOGIN_FIELD_MXID = "login_field_mxid";
PasswordLogin.LOGIN_FIELD_PHONE = "login_field_phone";
PasswordLogin.propTypes = {
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
onForgotPasswordClick: React.PropTypes.func, // fn()
initialUsername: React.PropTypes.string,
initialPhoneCountry: React.PropTypes.string,
initialPhoneNumber: React.PropTypes.string,
initialPassword: React.PropTypes.string,
onUsernameChanged: React.PropTypes.func,
onPhoneCountryChanged: React.PropTypes.func,
onPhoneNumberChanged: React.PropTypes.func,
onPasswordChanged: React.PropTypes.func,
loginIncorrect: React.PropTypes.bool,
};
module.exports = PasswordLogin;

View File

@@ -21,6 +21,7 @@ import sdk from '../../../index';
import Email from '../../../email';
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
const FIELD_EMAIL = 'field_email';
const FIELD_PHONE_COUNTRY = 'field_phone_country';
@@ -100,27 +101,26 @@ module.exports = React.createClass({
if (this.refs.email.value == '') {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning",
title: _t("Warning!"),
description:
<div>
If you don't specify an email address, you won't be able to reset your password.<br/>
Are you sure?
{_t("If you don't specify an email address, you won't be able to reset your password. " +
"Are you sure?")}
</div>,
button: "Continue",
button: _t("Continue"),
onFinished: function(confirmed) {
if (confirmed) {
self._doSubmit();
self._doSubmit(ev);
}
},
});
}
else {
self._doSubmit();
} else {
self._doSubmit(ev);
}
}
},
_doSubmit: function() {
_doSubmit: function(ev) {
let email = this.refs.email.value.trim();
var promise = this.props.onRegisterClick({
username: this.refs.username.value.trim() || this.props.guestUsername,
@@ -270,7 +270,8 @@ module.exports = React.createClass({
_onPhoneCountryChange(newVal) {
this.setState({
phoneCountry: newVal,
phoneCountry: newVal.iso2,
phonePrefix: newVal.prefix,
});
},
@@ -280,7 +281,7 @@ module.exports = React.createClass({
const emailSection = (
<div>
<input type="text" ref="email"
autoFocus={true} placeholder="Email address (optional)"
autoFocus={true} placeholder={_t("Email address (optional)")}
defaultValue={this.props.defaultEmail}
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_EMAIL);}}
@@ -303,7 +304,7 @@ module.exports = React.createClass({
} else if (this.state.selectedTeam) {
belowEmailSection = (
<p className="mx_Login_support">
You are registering with {this.state.selectedTeam.name}
{_t("You are registering with %(SelectedTeamName)s", {SelectedTeamName: this.state.selectedTeam.name})}
</p>
);
}
@@ -313,14 +314,19 @@ module.exports = React.createClass({
const phoneSection = (
<div className="mx_Login_phoneSection">
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
className="mx_Login_phoneCountry"
className="mx_Login_phoneCountry mx_Login_field_prefix"
value={this.state.phoneCountry}
isSmall={true}
showPrefix={true}
/>
<input type="text" ref="phoneNumber"
placeholder="Mobile phone number (optional)"
placeholder={_t("Mobile phone number (optional)")}
defaultValue={this.props.defaultPhoneNumber}
className={this._classForField(
FIELD_PHONE_NUMBER, 'mx_Login_phoneNumberField', 'mx_Login_field'
FIELD_PHONE_NUMBER,
'mx_Login_phoneNumberField',
'mx_Login_field',
'mx_Login_field_has_prefix'
)}
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
value={self.state.phoneNumber}
@@ -329,12 +335,12 @@ module.exports = React.createClass({
);
const registerButton = (
<input className="mx_Login_submit" type="submit" value="Register" />
<input className="mx_Login_submit" type="submit" value={_t("Register")} />
);
let placeholderUserName = "User name";
let placeholderUserName = _t("User name");
if (this.props.guestUsername) {
placeholderUserName += " (default: " + this.props.guestUsername + ")";
placeholderUserName += " " + _t("(default: %(userName)s)", {userName: this.props.guestUsername});
}
return (
@@ -349,15 +355,15 @@ module.exports = React.createClass({
onBlur={function() {self.validateField(FIELD_USERNAME);}} />
<br />
{ this.props.guestUsername ?
<div className="mx_Login_fieldLabel">Setting a user name will create a fresh account</div> : null
<div className="mx_Login_fieldLabel">{_t("Setting a user name will create a fresh account")}</div> : null
}
<input type="password" ref="password"
className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_PASSWORD);}}
placeholder="Password" defaultValue={this.props.defaultPassword} />
placeholder={_t("Password")} defaultValue={this.props.defaultPassword} />
<br />
<input type="password" ref="passwordConfirm"
placeholder="Confirm password"
placeholder={_t("Confirm password")}
className={this._classForField(FIELD_PASSWORD_CONFIRM, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM);}}
defaultValue={this.props.defaultPassword} />

View File

@@ -19,6 +19,7 @@ limitations under the License.
var React = require('react');
var Modal = require('../../../Modal');
var sdk = require('../../../index');
import { _t } from '../../../languageHandler';
/**
* A pure UI component which displays the HS and IS to use.
@@ -27,8 +28,7 @@ module.exports = React.createClass({
displayName: 'ServerConfig',
propTypes: {
onHsUrlChanged: React.PropTypes.func,
onIsUrlChanged: React.PropTypes.func,
onServerConfigChange: React.PropTypes.func,
// default URLs are defined in config.json (or the hardcoded defaults)
// they are used if the user has not overridden them with a custom URL.
@@ -50,8 +50,7 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
onHsUrlChanged: function() {},
onIsUrlChanged: function() {},
onServerConfigChange: function() {},
customHsUrl: "",
customIsUrl: "",
withToggleButton: false,
@@ -75,7 +74,10 @@ module.exports = React.createClass({
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() {
var hsUrl = this.state.hs_url.trim().replace(/\/$/, "");
if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
this.props.onHsUrlChanged(hsUrl);
this.props.onServerConfigChange({
hsUrl : this.state.hs_url,
isUrl : this.state.is_url,
});
});
});
},
@@ -85,7 +87,10 @@ module.exports = React.createClass({
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() {
var isUrl = this.state.is_url.trim().replace(/\/$/, "");
if (isUrl === "") isUrl = this.props.defaultIsUrl;
this.props.onIsUrlChanged(isUrl);
this.props.onServerConfigChange({
hsUrl : this.state.hs_url,
isUrl : this.state.is_url,
});
});
});
},
@@ -102,12 +107,16 @@ module.exports = React.createClass({
configVisible: visible
});
if (!visible) {
this.props.onHsUrlChanged(this.props.defaultHsUrl);
this.props.onIsUrlChanged(this.props.defaultIsUrl);
this.props.onServerConfigChange({
hsUrl : this.props.defaultHsUrl,
isUrl : this.props.defaultIsUrl,
});
}
else {
this.props.onHsUrlChanged(this.state.hs_url);
this.props.onIsUrlChanged(this.state.is_url);
this.props.onServerConfigChange({
hsUrl : this.state.hs_url,
isUrl : this.state.is_url,
});
}
},
@@ -123,19 +132,19 @@ module.exports = React.createClass({
var toggleButton;
if (this.props.withToggleButton) {
toggleButton = (
<div style={{ textAlign: 'center' }}>
<div className="mx_ServerConfig_selector">
<input className="mx_Login_radio" id="basic" name="configVisible" type="radio"
checked={!this.state.configVisible}
onChange={this.onServerConfigVisibleChange.bind(this, false)} />
<label className="mx_Login_label" htmlFor="basic">
Default server
{_t("Default server")}
</label>
&nbsp;&nbsp;
<input className="mx_Login_radio" id="advanced" name="configVisible" type="radio"
checked={this.state.configVisible}
onChange={this.onServerConfigVisibleChange.bind(this, true)} />
<label className="mx_Login_label" htmlFor="advanced">
Custom server
{_t("Custom server")}
</label>
</div>
);
@@ -147,7 +156,7 @@ module.exports = React.createClass({
<div style={serverConfigStyle}>
<div className="mx_ServerConfig">
<label className="mx_Login_label mx_ServerConfig_hslabel" htmlFor="hsurl">
Home server URL
{_t("Home server URL")}
</label>
<input className="mx_Login_field" id="hsurl" type="text"
placeholder={this.props.defaultHsUrl}
@@ -155,7 +164,7 @@ module.exports = React.createClass({
value={this.state.hs_url}
onChange={this.onHomeserverChanged} />
<label className="mx_Login_label mx_ServerConfig_islabel" htmlFor="isurl">
Identity server URL
{_t("Identity server URL")}
</label>
<input className="mx_Login_field" id="isurl" type="text"
placeholder={this.props.defaultIsUrl}
@@ -163,7 +172,7 @@ module.exports = React.createClass({
value={this.state.is_url}
onChange={this.onIdentityServerChanged} />
<a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>
What does this mean?
{_t("What does this mean?")}
</a>
</div>
</div>

View File

@@ -20,8 +20,8 @@ import React from 'react';
import MFileBody from './MFileBody';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler';
export default class MAudioBody extends React.Component {
constructor(props) {
@@ -77,7 +77,7 @@ export default class MAudioBody extends React.Component {
return (
<span className="mx_MAudioBody" ref="body">
<img src="img/warning.svg" width="16" height="16"/>
Error decrypting audio
{_t("Error decrypting audio")}
</span>
);
}

View File

@@ -20,10 +20,10 @@ import React from 'react';
import filesize from 'filesize';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {decryptFile} from '../../../utils/DecryptFile';
import Tinter from '../../../Tinter';
import request from 'browser-request';
import q from 'q';
import Modal from '../../../Modal';
@@ -202,7 +202,7 @@ module.exports = React.createClass({
* @return {string} the human readable link text for the attachment.
*/
presentableTextForFile: function(content) {
var linkText = 'Attachment';
var linkText = _t("Attachment");
if (content.body && content.body.length > 0) {
// The content body should be the name of the file including a
// file extension.
@@ -261,7 +261,7 @@ module.exports = React.createClass({
const content = this.props.mxEvent.getContent();
const text = this.presentableTextForFile(content);
const isEncrypted = content.file !== undefined;
const fileName = content.body && content.body.length > 0 ? content.body : "Attachment";
const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
const contentUrl = this._getContentUrl();
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -283,7 +283,8 @@ module.exports = React.createClass({
}).catch((err) => {
console.warn("Unable to decrypt attachment: ", err);
Modal.createDialog(ErrorDialog, {
description: "Error decrypting attachment"
title: _t("Error"),
description: _t("Error decrypting attachment"),
});
}).finally(() => {
decrypting = false;
@@ -295,7 +296,7 @@ module.exports = React.createClass({
<span className="mx_MFileBody" ref="body">
<div className="mx_MImageBody_download">
<a href="javascript:void(0)" onClick={decrypt}>
Decrypt {text}
{ _t("Decrypt %(text)s", { text: text }) }
</a>
</div>
</span>
@@ -314,7 +315,7 @@ module.exports = React.createClass({
// We can't provide a Content-Disposition header like we would for HTTP.
download: fileName,
target: "_blank",
textContent: "Download " + text,
textContent: _t("Download %(text)s", { text: text }),
}, "*");
};
@@ -346,7 +347,7 @@ module.exports = React.createClass({
return (
<span className="mx_MFileBody">
<div className="mx_MImageBody_download">
<a className="mx_ImageBody_downloadLink" href={contentUrl} target="_blank">
<a className="mx_ImageBody_downloadLink" href={contentUrl} download={fileName} target="_blank">
{ fileName }
</a>
<div className="mx_MImageBody_size">
@@ -360,9 +361,9 @@ module.exports = React.createClass({
return (
<span className="mx_MFileBody">
<div className="mx_MImageBody_download">
<a href={contentUrl} target="_blank" rel="noopener">
<a href={contentUrl} download={fileName} target="_blank" rel="noopener">
<img src={tintedDownloadImageURL} width="12" height="14" ref="downloadImage"/>
Download {text}
{ _t("Download %(text)s", { text: text }) }
</a>
</div>
</span>
@@ -371,7 +372,7 @@ module.exports = React.createClass({
} else {
var extra = text ? (': ' + text) : '';
return <span className="mx_MFileBody">
Invalid file{extra}
{ _t("Invalid file%(extra)s", { extra: extra }) }
</span>;
}
},

View File

@@ -26,6 +26,7 @@ import dis from '../../../dispatcher';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import q from 'q';
import UserSettingsStore from '../../../UserSettingsStore';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'MImageBody',
@@ -56,6 +57,7 @@ module.exports = React.createClass({
const ImageView = sdk.getComponent("elements.ImageView");
const params = {
src: httpUrl,
name: content.body && content.body.length > 0 ? content.body : _t('Attachment'),
mxEvent: this.props.mxEvent,
};
@@ -190,7 +192,7 @@ module.exports = React.createClass({
return (
<span className="mx_MImageBody" ref="body">
<img src="img/warning.svg" width="16" height="16"/>
Error decrypting image
{_t("Error decrypting image")}
</span>
);
}
@@ -237,13 +239,13 @@ module.exports = React.createClass({
} else if (content.body) {
return (
<span className="mx_MImageBody">
Image '{content.body}' cannot be displayed.
{_t("Image '%(Body)s' cannot be displayed.", {Body: content.body})}
</span>
);
} else {
return (
<span className="mx_MImageBody">
This image cannot be displayed.
{_t("This image cannot be displayed.")}
</span>
);
}

View File

@@ -19,11 +19,10 @@ limitations under the License.
import React from 'react';
import MFileBody from './MFileBody';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Model from '../../../Modal';
import sdk from '../../../index';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import q from 'q';
import UserSettingsStore from '../../../UserSettingsStore';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'MVideoBody',
@@ -128,7 +127,7 @@ module.exports = React.createClass({
return (
<span className="mx_MVideoBody" ref="body">
<img src="img/warning.svg" width="16" height="16"/>
Error decrypting video
{_t("Error decrypting video")}
</span>
);
}

View File

@@ -0,0 +1,92 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { ContentRepo } from 'matrix-js-sdk';
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
module.exports = React.createClass({
displayName: 'RoomAvatarEvent',
propTypes: {
/* the MatrixEvent to show */
mxEvent: React.PropTypes.object.isRequired,
},
onAvatarClick: function(name) {
var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
var ImageView = sdk.getComponent("elements.ImageView");
var params = {
src: httpUrl,
name: name,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
},
render: function() {
var ev = this.props.mxEvent;
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
var name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
senderDisplayName: senderDisplayName,
roomName: room ? room.name : '',
});
if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
return (
<div className="mx_TextualEvent">
{ _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }
</div>
);
}
var url = ContentRepo.getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(),
ev.getContent().url,
14 * window.devicePixelRatio,
14 * window.devicePixelRatio,
'crop'
);
// it sucks that _tJsx doesn't support normal _t substitutions :((
return (
<div className="mx_RoomAvatarEvent">
{ _tJsx('$senderDisplayName changed the room avatar to <img/>',
[
/\$senderDisplayName/,
/<img\/>/,
],
[
(sub) => senderDisplayName,
(sub) =>
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
onClick={ this.onAvatarClick.bind(this, name) }>
<BaseAvatar width={14} height={14} url={ url }
name={ name } />
</AccessibleButton>,
]
)
}
</div>
);
},
});

View File

@@ -30,7 +30,7 @@ export default function SenderProfile(props) {
}
return (
<EmojiText className="mx_SenderProfile"
<EmojiText className="mx_SenderProfile" dir="auto"
onClick={props.onClick}>{`${name || ''} ${props.aux || ''}`}</EmojiText>
);
}

View File

@@ -28,6 +28,7 @@ import ScalarAuthClient from '../../../ScalarAuthClient';
import Modal from '../../../Modal';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
linkifyMatrix(linkify);
@@ -62,6 +63,19 @@ module.exports = React.createClass({
};
},
copyToClipboard: function(text) {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
const successful = document.execCommand('copy');
} catch (err) {
console.log('Unable to copy');
}
document.body.removeChild(textArea);
},
componentDidMount: function() {
this._unmounted = false;
@@ -80,6 +94,14 @@ module.exports = React.createClass({
}
}, 10);
}
// add event handlers to the 'copy code' buttons
const buttons = ReactDOM.findDOMNode(this).getElementsByClassName("mx_EventTile_copyButton");
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = (e) => {
const copyCode = buttons[i].parentNode.getElementsByTagName("code")[0];
this.copyToClipboard(copyCode.textContent);
};
}
}
},
@@ -132,7 +154,8 @@ module.exports = React.createClass({
links.push(node);
}
}
else if (node.tagName === "PRE" || node.tagName === "CODE") {
else if (node.tagName === "PRE" || node.tagName === "CODE" ||
node.tagName === "BLOCKQUOTE") {
continue;
}
else if (node.children && node.children.length) {
@@ -229,14 +252,14 @@ module.exports = React.createClass({
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
let integrationsUrl = SdkConfig.get().integrations_ui_url;
Modal.createDialog(QuestionDialog, {
title: "Add an Integration",
title: _t("Add an Integration"),
description:
<div>
You are about to be taken to a third-party site so you can
authenticate your account for use with {integrationsUrl}.<br/>
Do you wish to continue?
{_t("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: integrationsUrl })}
</div>,
button: "Continue",
button: _t("Continue"),
onFinished: function(confirmed) {
if (!confirmed) {
return;

View File

@@ -24,6 +24,11 @@ import sdk from '../../../index';
module.exports = React.createClass({
displayName: 'TextualEvent',
propTypes: {
/* the MatrixEvent to show */
mxEvent: React.PropTypes.object.isRequired,
},
render: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
var text = TextForEvent.textForEvent(this.props.mxEvent);

View File

@@ -16,7 +16,8 @@ limitations under the License.
'use strict';
var React = require('react');
import React from 'react';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'UnknownBody',
@@ -24,7 +25,7 @@ module.exports = React.createClass({
render: function() {
const text = this.props.mxEvent.getContent().body;
return (
<span className="mx_UnknownBody" title="Redacted or unknown message type">
<span className="mx_UnknownBody" title={_t("Removed or unknown message type")}>
{text}
</span>
);

View File

@@ -19,6 +19,7 @@ var React = require('react');
var ObjectUtils = require("../../../ObjectUtils");
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require("../../../index");
import { _t } from '../../../languageHandler';
var Modal = require("../../../Modal");
module.exports = React.createClass({
@@ -154,8 +155,8 @@ module.exports = React.createClass({
else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Invalid alias format",
description: "'" + alias + "' is not a valid format for an alias",
title: _t('Invalid alias format'),
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
});
}
},
@@ -170,8 +171,8 @@ module.exports = React.createClass({
else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Invalid address format",
description: "'" + alias + "' is not a valid format for an address",
title: _t('Invalid address format'),
description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }),
});
}
},
@@ -203,7 +204,7 @@ module.exports = React.createClass({
if (this.props.canSetCanonicalAlias) {
canonical_alias_section = (
<select onChange={this.onCanonicalAliasChange} defaultValue={ this.state.canonicalAlias }>
<option value="" key="unset">not specified</option>
<option value="" key="unset">{ _t('not specified') }</option>
{
Object.keys(self.state.domainToAliases).map(function(domain, i) {
return self.state.domainToAliases[domain].map(function(alias, j) {
@@ -220,7 +221,7 @@ module.exports = React.createClass({
}
else {
canonical_alias_section = (
<b>{ this.state.canonicalAlias || "not set" }</b>
<b>{ this.state.canonicalAlias || _t('not set') }</b>
);
}
@@ -229,7 +230,7 @@ module.exports = React.createClass({
remote_aliases_section = (
<div>
<div className="mx_RoomSettings_aliasLabel">
Remote addresses for this room:
{_t("Remote addresses for this room:")}
</div>
<div className="mx_RoomSettings_aliasesTable">
{ this.state.remoteDomains.map((domain, i) => {
@@ -254,13 +255,13 @@ module.exports = React.createClass({
<div>
<h3>Addresses</h3>
<div className="mx_RoomSettings_aliasLabel">
The main address for this room is: { canonical_alias_section }
{ _t('The main address for this room is') }: { canonical_alias_section }
</div>
<div className="mx_RoomSettings_aliasLabel">
{ (this.state.domainToAliases[localDomain] &&
this.state.domainToAliases[localDomain].length > 0)
? "Local addresses for this room:"
: "This room has no local addresses" }
? _t('Local addresses for this room:')
: _t('This room has no local addresses') }
</div>
<div className="mx_RoomSettings_aliasesTable">
{ (this.state.domainToAliases[localDomain] || []).map((alias, i) => {
@@ -268,7 +269,7 @@ module.exports = React.createClass({
if (this.props.canSetAliases) {
deleteButton = (
<img src="img/cancel-small.svg" width="14" height="14"
alt="Delete" onClick={ self.onAliasDeleted.bind(self, localDomain, i) } />
alt={ _t('Delete') } onClick={ self.onAliasDeleted.bind(self, localDomain, i) } />
);
}
return (
@@ -276,7 +277,7 @@ module.exports = React.createClass({
<EditableText
className="mx_RoomSettings_alias mx_RoomSettings_editable"
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
placeholder={ "New address (e.g. #foo:" + localDomain + ")" }
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
blurToCancel={ false }
onValueChanged={ self.onAliasChanged.bind(self, localDomain, i) }
editable={ self.props.canSetAliases }
@@ -294,7 +295,7 @@ module.exports = React.createClass({
ref="add_alias"
className="mx_RoomSettings_alias mx_RoomSettings_editable"
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
placeholder={ "New address (e.g. #foo:" + localDomain + ")" }
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
blurToCancel={ false }
onValueChanged={ self.onAliasAdded } />
<div className="mx_RoomSettings_addAlias mx_filterFlipColor">

View File

@@ -21,6 +21,8 @@ var Tinter = require('../../../Tinter');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
import dis from '../../../dispatcher';
var ROOM_COLORS = [
// magic room default values courtesy of Ribot
["#76cfa6", "#eaf5f0"],
@@ -86,11 +88,7 @@ module.exports = React.createClass({
}
).catch(function(err) {
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Saving room color settings is only available to registered users"
});
dis.dispatch({action: 'view_set_mxid'});
}
});
}

View File

@@ -20,6 +20,7 @@ var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require("../../../index");
var Modal = require("../../../Modal");
var UserSettingsStore = require('../../../UserSettingsStore');
import { _t, _tJsx } from '../../../languageHandler';
module.exports = React.createClass({
@@ -120,35 +121,47 @@ module.exports = React.createClass({
<input type="checkbox" ref="globalDisableUrlPreview"
onChange={ this.onGlobalDisableUrlPreviewChange }
checked={ this.state.globalDisableUrlPreview } />
Disable URL previews by default for participants in this room
{_t("Disable URL previews by default for participants in this room")}
</label>;
}
else {
disableRoomPreviewUrls =
<label>
URL previews are { this.state.globalDisableUrlPreview ? "disabled" : "enabled" } by default for participants in this room.
{_t("URL previews are %(globalDisableUrlPreview)s by default for participants in this room.", {globalDisableUrlPreview: this.state.globalDisableUrlPreview ? _t("disabled") : _t("enabled")})}
</label>;
}
let urlPreviewText = null;
if (UserSettingsStore.getUrlPreviewsDisabled()) {
urlPreviewText = (
_tJsx("You have <a>disabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{sub}</a>)
);
}
else {
urlPreviewText = (
_tJsx("You have <a>enabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{sub}</a>)
);
}
return (
<div className="mx_RoomSettings_toggles">
<h3>URL Previews</h3>
<h3>{_t("URL Previews")}</h3>
<label>
You have <a href="#/settings">{ UserSettingsStore.getUrlPreviewsDisabled() ? 'disabled' : 'enabled' }</a> URL previews by default.
{urlPreviewText}
</label>
{ disableRoomPreviewUrls }
<label>
<input type="checkbox" ref="userEnableUrlPreview"
onChange={ this.onUserEnableUrlPreviewChange }
checked={ this.state.userEnableUrlPreview } />
Enable URL previews for this room (affects only you)
{_t("Enable URL previews for this room (affects only you)")}
</label>
<label>
<input type="checkbox" ref="userDisableUrlPreview"
onChange={ this.onUserDisableUrlPreviewChange }
checked={ this.state.userDisableUrlPreview } />
Disable URL previews for this room (affects only you)
{_t("Disable URL previews for this room (affects only you)")}
</label>
</div>
);

View File

@@ -4,7 +4,7 @@ import classNames from 'classnames';
import flatMap from 'lodash/flatMap';
import isEqual from 'lodash/isEqual';
import sdk from '../../../index';
import type {Completion, SelectionRange} from '../../../autocomplete/Autocompleter';
import type {Completion} from '../../../autocomplete/Autocompleter';
import Q from 'q';
import {getCompletions} from '../../../autocomplete/Autocompleter';

View File

@@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
const React = require('react');
const MatrixClientPeg = require("../../../MatrixClientPeg");
const sdk = require('../../../index');
const dis = require("../../../dispatcher");
const ObjectUtils = require('../../../ObjectUtils');
const AppsDrawer = require('./AppsDrawer');
import React from 'react';
import MatrixClientPeg from "../../../MatrixClientPeg";
import sdk from '../../../index';
import dis from "../../../dispatcher";
import ObjectUtils from '../../../ObjectUtils';
import AppsDrawer from './AppsDrawer';
import { _t, _tJsx} from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'AuxPanel',
@@ -77,10 +79,10 @@ module.exports = React.createClass({
fileDropTarget = (
<div className="mx_RoomView_fileDropTarget">
<div className="mx_RoomView_fileDropTargetLabel"
title="Drop File Here">
title={_t("Drop File Here")}>
<TintableSvg src="img/upload-big.svg" width="45" height="59"/>
<br/>
Drop file here to upload
{_t("Drop file here to upload")}
</div>
</div>
);
@@ -91,17 +93,22 @@ module.exports = React.createClass({
let supportedText;
let joinText;
if (!MatrixClientPeg.get().supportsVoip()) {
supportedText = " (unsupported)";
supportedText = _t(" (unsupported)");
} else {
joinText = (<span>
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}}
href="#">voice</a> or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video'); }}
href="#">video</a>.
{_tJsx(
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
[/<voiceText>(.*?)<\/voiceText>/, /<videoText>(.*?)<\/videoText>/],
[
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}} href="#">{sub}</a>,
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video');}} href="#">{sub}</a>,
]
)}
</span>);
}
conferenceCallNotification = (
<div className="mx_RoomView_ongoingConfCallNotification">
Ongoing conference call{ supportedText }. { joinText }
{_t("Ongoing conference call%(supportedText)s. %(joinText)s", {supportedText: supportedText, joinText: joinText})}
</div>
);
}

View File

@@ -21,6 +21,7 @@ var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
var PRESENCE_CLASS = {
@@ -115,7 +116,7 @@ module.exports = React.createClass({
nameEl = (
<div className="mx_EntityTile_details">
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
<EmojiText element="div" className="mx_EntityTile_name_hover">{name}</EmojiText>
<EmojiText element="div" className="mx_EntityTile_name_hover" dir="auto">{name}</EmojiText>
<PresenceLabel activeAgo={ activeAgo }
currentlyActive={this.props.presenceCurrentlyActive}
presenceState={this.props.presenceState} />
@@ -124,7 +125,7 @@ module.exports = React.createClass({
}
else {
nameEl = (
<EmojiText element="div" className="mx_EntityTile_name">{name}</EmojiText>
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{name}</EmojiText>
);
}
@@ -140,10 +141,10 @@ module.exports = React.createClass({
var power;
var powerLevel = this.props.powerLevel;
if (powerLevel >= 50 && powerLevel < 99) {
power = <img src="img/mod.svg" className="mx_EntityTile_power" width="16" height="17" alt="Mod"/>;
power = <img src="img/mod.svg" className="mx_EntityTile_power" width="16" height="17" alt={_t("Moderator")}/>;
}
if (powerLevel >= 99) {
power = <img src="img/admin.svg" className="mx_EntityTile_power" width="16" height="17" alt="Admin"/>;
power = <img src="img/admin.svg" className="mx_EntityTile_power" width="16" height="17" alt={_t("Admin")}/>;
}

View File

@@ -16,8 +16,10 @@ limitations under the License.
'use strict';
var React = require('react');
var classNames = require("classnames");
import { _t } from '../../../languageHandler';
var Modal = require('../../../Modal');
var sdk = require('../../../index');
@@ -36,10 +38,12 @@ var eventTileTypes = {
'm.call.answer' : 'messages.TextualEvent',
'm.call.hangup' : 'messages.TextualEvent',
'm.room.name' : 'messages.TextualEvent',
'm.room.avatar' : 'messages.RoomAvatarEvent',
'm.room.topic' : 'messages.TextualEvent',
'm.room.third_party_invite' : 'messages.TextualEvent',
'm.room.history_visibility' : 'messages.TextualEvent',
'm.room.encryption' : 'messages.TextualEvent',
'm.room.power_levels' : 'messages.TextualEvent',
};
var MAX_READ_AVATARS = 5;
@@ -128,6 +132,9 @@ module.exports = WithMatrixClient(React.createClass({
* for now.
*/
tileShape: React.PropTypes.string,
// show twelve hour timestamps
isTwelveHour: React.PropTypes.bool,
},
getInitialState: function() {
@@ -283,21 +290,17 @@ module.exports = WithMatrixClient(React.createClass({
},
getReadAvatars: function() {
// return early if there are no read receipts
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
return (<span className="mx_EventTile_readAvatars"></span>);
}
const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
const avatars = [];
const receiptOffset = 15;
let left = 0;
// It's possible that the receipt was sent several days AFTER the event.
// If it is, we want to display the complete date along with the HH:MM:SS,
// rather than just HH:MM:SS.
let dayAfterEvent = new Date(this.props.mxEvent.getTs());
dayAfterEvent.setDate(dayAfterEvent.getDate() + 1);
dayAfterEvent.setHours(0);
dayAfterEvent.setMinutes(0);
dayAfterEvent.setSeconds(0);
let dayAfterEventTime = dayAfterEvent.getTime();
var receipts = this.props.readReceipts || [];
for (var i = 0; i < receipts.length; ++i) {
var receipt = receipts[i];
@@ -333,7 +336,6 @@ module.exports = WithMatrixClient(React.createClass({
suppressAnimation={this._suppressReadReceiptAnimation}
onClick={this.toggleAllReadAvatars}
timestamp={receipt.ts}
showFullTimestamp={receipt.ts >= dayAfterEventTime}
/>
);
}
@@ -394,8 +396,7 @@ module.exports = WithMatrixClient(React.createClass({
var msgtype = content.msgtype;
var eventType = this.props.mxEvent.getType();
// Info messages are basically information about commands processed on a
// room, or emote messages
// Info messages are basically information about commands processed on a room
var isInfoMessage = (eventType !== 'm.room.message');
var EventTileType = sdk.getComponent(eventTileTypes[eventType]);
@@ -409,9 +410,10 @@ module.exports = WithMatrixClient(React.createClass({
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted;
var classes = classNames({
const classes = classNames({
mx_EventTile: true,
mx_EventTile_info: isInfoMessage,
mx_EventTile_12hr: this.props.isTwelveHour,
mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting',
mx_EventTile_sending: isSending,
mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
@@ -423,7 +425,8 @@ module.exports = WithMatrixClient(React.createClass({
menu: this.state.menu,
mx_EventTile_verified: this.state.verified == true,
mx_EventTile_unverified: this.state.verified == false,
mx_EventTile_bad: this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted',
mx_EventTile_bad: msgtype === 'm.bad.encrypted',
mx_EventTile_emote: msgtype === 'm.emote',
mx_EventTile_redacted: isRedacted,
});
@@ -468,9 +471,9 @@ module.exports = WithMatrixClient(React.createClass({
if (needsSenderProfile) {
let aux = null;
if (!this.props.tileShape) {
if (msgtype === 'm.image') aux = "sent an image";
else if (msgtype === 'm.video') aux = "sent a video";
else if (msgtype === 'm.file') aux = "uploaded a file";
if (msgtype === 'm.image') aux = _t('sent an image');
else if (msgtype === 'm.video') aux = _t('sent a video');
else if (msgtype === 'm.file') aux = _t('uploaded a file');
sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />;
}
else {
@@ -478,36 +481,34 @@ module.exports = WithMatrixClient(React.createClass({
}
}
var editButton = (
<span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} />
const editButton = (
<span className="mx_EventTile_editButton" title={ _t("Options") } onClick={this.onEditClicked} />
);
var e2e;
let e2e;
// cosmetic padlocks:
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" />;
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12" />;
}
// real padlocks
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Undecryptable")} src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
}
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-verified.svg" width="10" height="12"/>;
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12"/>;
}
else {
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by an unverified device")} src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
}
}
else if (e2eEnabled) {
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Unencrypted message")} src="img/e2e-unencrypted.svg" width="12" height="12"/>;
}
const timestamp = this.props.mxEvent.getTs() ?
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
if (this.props.tileShape === "notif") {
var room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
return (
<div className={classes}>
<div className="mx_EventTile_roomName">

View File

@@ -0,0 +1,96 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2017 Michael Telatynski
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
import KeyCode from '../../../KeyCode';
module.exports = React.createClass({
displayName: 'ForwardMessage',
propTypes: {
currentRoomId: React.PropTypes.string.isRequired,
/* the MatrixEvent to be forwarded */
mxEvent: React.PropTypes.object.isRequired,
onCancelClick: React.PropTypes.func.isRequired,
},
componentWillMount: function() {
dis.dispatch({
action: 'ui_opacity',
leftOpacity: 1.0,
rightOpacity: 0.3,
middleOpacity: 0.5,
});
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
document.addEventListener('keydown', this._onKeyDown);
},
componentWillUnmount: function() {
dis.dispatch({
action: 'ui_opacity',
sideOpacity: 1.0,
middleOpacity: 1.0,
});
dis.unregister(this.dispatcherRef);
document.removeEventListener('keydown', this._onKeyDown);
},
onAction: function(payload) {
if (payload.action === 'view_room') {
const event = this.props.mxEvent;
const Client = MatrixClientPeg.get();
Client.sendEvent(payload.room_id, event.getType(), event.getContent()).done(() => {
dis.dispatch({action: 'message_sent'});
}, (err) => {
if (err.name === "UnknownDeviceError") {
dis.dispatch({
action: 'unknown_device_error',
err: err,
room: Client.getRoom(payload.room_id),
});
}
dis.dispatch({action: 'message_send_failed'});
});
if (this.props.currentRoomId === payload.room_id) this.props.onCancelClick();
}
},
_onKeyDown: function(ev) {
switch (ev.keyCode) {
case KeyCode.ESCAPE:
this.props.onCancelClick();
break;
}
},
render: function() {
return (
<div className="mx_ForwardMessage">
<h1>{_t('Please select the destination room for this message')}</h1>
</div>
);
},
});

View File

@@ -100,7 +100,9 @@ module.exports = React.createClass({
render: function() {
var p = this.state.preview;
if (!p) return <div/>;
if (!p || Object.keys(p).length === 0) {
return <div/>;
}
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
var image = p["og:image"];

View File

@@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default class MemberDeviceInfo extends React.Component {
render() {
@@ -25,19 +26,19 @@ export default class MemberDeviceInfo extends React.Component {
if (this.props.device.isBlocked()) {
indicator = (
<div className="mx_MemberDeviceInfo_blacklisted">
<img src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} alt="Blacklisted"/>
<img src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} alt={_t("Blacklisted")}/>
</div>
);
} else if (this.props.device.isVerified()) {
indicator = (
<div className="mx_MemberDeviceInfo_verified">
<img src="img/e2e-verified.svg" width="10" height="12" alt="Verified"/>
<img src="img/e2e-verified.svg" width="10" height="12" alt={_t("Verified")}/>
</div>
);
} else {
indicator = (
<div className="mx_MemberDeviceInfo_unverified">
<img src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }} alt="Unverified"/>
<img src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }} alt={_t("Unverified")}/>
</div>
);
}
@@ -49,7 +50,7 @@ export default class MemberDeviceInfo extends React.Component {
// add the deviceId as a titletext to help with debugging
return (
<div className="mx_MemberDeviceInfo"
title={"device id: " + this.props.device.deviceId} >
title={_t("device id: ") + this.props.device.deviceId} >
<div className="mx_MemberDeviceInfo_deviceInfo">
<div className="mx_MemberDeviceInfo_deviceId">
{deviceName}

View File

@@ -31,6 +31,7 @@ import classNames from 'classnames';
import dis from '../../../dispatcher';
import Modal from '../../../Modal';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import createRoom from '../../../createRoom';
import DMRoomMap from '../../../utils/DMRoomMap';
import Unread from '../../../Unread';
@@ -219,7 +220,7 @@ module.exports = WithMatrixClient(React.createClass({
onKick: function() {
const membership = this.props.member.membership;
const kickLabel = membership === "invite" ? "Disinvite" : "Kick";
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createDialog(ConfirmUserActionDialog, {
member: this.props.member,
@@ -241,8 +242,8 @@ module.exports = WithMatrixClient(React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Kick error: " + err);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to kick user",
title: _t("Failed to kick"),
description: ((err && err.message) ? err.message : "Operation failed"),
});
}
).finally(()=>{
@@ -256,7 +257,7 @@ module.exports = WithMatrixClient(React.createClass({
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createDialog(ConfirmUserActionDialog, {
member: this.props.member,
action: this.props.member.membership == 'ban' ? 'Unban' : 'Ban',
action: this.props.member.membership == 'ban' ? _t("Unban") : _t("Ban"),
askReason: this.props.member.membership != 'ban',
danger: this.props.member.membership != 'ban',
onFinished: (proceed, reason) => {
@@ -283,8 +284,8 @@ module.exports = WithMatrixClient(React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Ban error: " + err);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to ban user",
title: _t("Error"),
description: _t("Failed to ban user"),
});
}
).finally(()=>{
@@ -333,8 +334,8 @@ module.exports = WithMatrixClient(React.createClass({
}, function(err) {
console.error("Mute error: " + err);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to mute user",
title: _t("Error"),
description: _t("Failed to mute user"),
});
}
).finally(()=>{
@@ -374,16 +375,12 @@ module.exports = WithMatrixClient(React.createClass({
console.log("Mod toggle success");
}, function(err) {
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "This action cannot be performed by a guest user. Please register to be able to do this."
});
dis.dispatch({action: 'view_set_mxid'});
} else {
console.error("Toggle moderator error:" + err);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to toggle moderator status",
title: _t("Error"),
description: _t("Failed to toggle moderator status"),
});
}
}
@@ -403,8 +400,8 @@ module.exports = WithMatrixClient(React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to change power level " + err);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to change power level",
title: _t("Error"),
description: _t("Failed to change power level"),
});
}
).finally(()=>{
@@ -432,13 +429,13 @@ module.exports = WithMatrixClient(React.createClass({
if (parseInt(myPower) === parseInt(powerLevel)) {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning",
title: _t("Warning!"),
description:
<div>
You will not be able to undo this change as you are promoting the user to have the same power level as yourself.<br/>
Are you sure?
{ _t("You will not be able to undo this change as you are promoting the user to have the same power level as yourself.") }<br/>
{ _t("Are you sure?") }
</div>,
button: "Continue",
button: _t("Continue"),
onFinished: function(confirmed) {
if (confirmed) {
self._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
@@ -581,9 +578,9 @@ module.exports = WithMatrixClient(React.createClass({
// still loading
devComponents = <Spinner />;
} else if (devices === null) {
devComponents = "Unable to load device list";
devComponents = _t("Unable to load device list");
} else if (devices.length === 0) {
devComponents = "No devices with registered encryption keys";
devComponents = _t("No devices with registered encryption keys");
} else {
devComponents = [];
for (var i = 0; i < devices.length; i++) {
@@ -595,7 +592,7 @@ module.exports = WithMatrixClient(React.createClass({
return (
<div>
<h3>Devices</h3>
<h3>{ _t("Devices") }</h3>
<div className="mx_MemberInfo_devices">
{devComponents}
</div>
@@ -644,11 +641,11 @@ module.exports = WithMatrixClient(React.createClass({
<div className="mx_RoomTile_avatar">
<img src="img/create-big.svg" width="26" height="26" />
</div>
<div className={labelClasses}><i>Start new chat</i></div>
<div className={labelClasses}><i>{ _t("Start a chat") }</i></div>
</AccessibleButton>;
startChat = <div>
<h3>Direct chats</h3>
<h3>{ _t("Direct chats") }</h3>
{tiles}
{startNewChat}
</div>;
@@ -661,7 +658,7 @@ module.exports = WithMatrixClient(React.createClass({
if (this.state.can.kick) {
const membership = this.props.member.membership;
const kickLabel = membership === "invite" ? "Disinvite" : "Kick";
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
kickButton = (
<AccessibleButton className="mx_MemberInfo_field"
onClick={this.onKick}>
@@ -670,9 +667,9 @@ module.exports = WithMatrixClient(React.createClass({
);
}
if (this.state.can.ban) {
let label = 'Ban';
let label = _t("Ban");
if (this.props.member.membership == 'ban') {
label = 'Unban';
label = _t("Unban");
}
banButton = (
<AccessibleButton className="mx_MemberInfo_field"
@@ -682,7 +679,7 @@ module.exports = WithMatrixClient(React.createClass({
);
}
if (this.state.can.mute) {
const muteLabel = this.state.muted ? "Unmute" : "Mute";
const muteLabel = this.state.muted ? _t("Unmute") : _t("Mute");
muteButton = (
<AccessibleButton className="mx_MemberInfo_field"
onClick={this.onMuteToggle}>
@@ -691,7 +688,7 @@ module.exports = WithMatrixClient(React.createClass({
);
}
if (this.state.can.toggleMod) {
var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator";
var giveOpLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator");
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
{giveOpLabel}
</AccessibleButton>;
@@ -704,7 +701,7 @@ module.exports = WithMatrixClient(React.createClass({
if (kickButton || banButton || muteButton || giveModButton) {
adminTools =
<div>
<h3>Admin tools</h3>
<h3>{_t("Admin tools")}</h3>
<div className="mx_MemberInfo_buttons">
{muteButton}
@@ -717,8 +714,16 @@ module.exports = WithMatrixClient(React.createClass({
const memberName = this.props.member.name;
if (this.props.member.user) {
var presenceState = this.props.member.user.presence;
var presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
var presenceLastTs = this.props.member.user.lastPresenceTs;
var presenceCurrentlyActive = this.props.member.user.currentlyActive;
}
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
var PowerSelector = sdk.getComponent('elements.PowerSelector');
var PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
const EmojiText = sdk.getComponent('elements.EmojiText');
return (
<div className="mx_MemberInfo">
@@ -734,7 +739,12 @@ module.exports = WithMatrixClient(React.createClass({
{ this.props.member.userId }
</div>
<div className="mx_MemberInfo_profileField">
Level: <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
{ _t("Level:") } <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
</div>
<div className="mx_MemberInfo_profileField">
<PresenceLabel activeAgo={ presenceLastActiveAgo }
currentlyActive={ presenceCurrentlyActive }
presenceState={ presenceState } />
</div>
</div>

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
import { _t } from '../../../languageHandler';
var classNames = require('classnames');
var Matrix = require("matrix-js-sdk");
var q = require('q');
@@ -27,12 +28,6 @@ var CallHandler = require("../../../CallHandler");
var Invite = require("../../../Invite");
var INITIAL_LOAD_NUM_MEMBERS = 30;
var SHARE_HISTORY_WARNING =
<span>
Newly invited users will see the history of this room. <br/>
If you'd prefer invited users not to see messages that were sent before they joined, <br/>
turn off, 'Share message history with new users' in the settings for this room.
</span>;
module.exports = React.createClass({
displayName: 'MemberList',
@@ -207,7 +202,9 @@ module.exports = React.createClass({
// For now we'll pretend this is any entity. It should probably be a separate tile.
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var text = "and " + overflowCount + " other" + (overflowCount > 1 ? "s" : "") + "...";
var text = (overflowCount > 1)
? _t("and %(overflowCount)s others...", { overflowCount: overflowCount })
: _t("and one other...");
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
@@ -352,7 +349,7 @@ module.exports = React.createClass({
if (invitedMemberTiles.length > 0) {
invitedSection = (
<div className="mx_MemberList_invited">
<h2>Invited</h2>
<h2>{ _t("Invited") }</h2>
<div className="mx_MemberList_wrapper">
{invitedMemberTiles}
</div>
@@ -363,8 +360,8 @@ module.exports = React.createClass({
var inputBox = (
<form autoComplete="off">
<input className="mx_MemberList_query" id="mx_MemberList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder="Filter room members" />
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={ _t('Filter room members') } />
</form>
);

View File

@@ -22,6 +22,7 @@ var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var dis = require('../../../dispatcher');
var Modal = require("../../../Modal");
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'MemberTile',
@@ -63,7 +64,7 @@ module.exports = React.createClass({
},
getPowerLabel: function() {
return this.props.member.userId + " (power " + this.props.member.powerLevel + ")";
return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel});
},
render: function() {

View File

@@ -13,14 +13,14 @@ 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.
*/
const React = require('react');
const CallHandler = require('../../../CallHandler');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const Modal = require('../../../Modal');
const sdk = require('../../../index');
const dis = require('../../../dispatcher');
// import Autocomplete from './Autocomplete';
var React = require('react');
import { _t } from '../../../languageHandler';
var CallHandler = require('../../../CallHandler');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var Modal = require('../../../Modal');
var sdk = require('../../../index');
var dis = require('../../../dispatcher');
import Autocomplete from './Autocomplete';
import classNames from 'classnames';
import UserSettingsStore from '../../../UserSettingsStore';
@@ -35,6 +35,7 @@ export default class MessageComposer extends React.Component {
this.onShowAppsClick = this.onShowAppsClick.bind(this);
this.onHideAppsClick = this.onHideAppsClick.bind(this);
this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
this.uploadFiles = this.uploadFiles.bind(this);
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
this.onInputContentChanged = this.onInputContentChanged.bind(this);
this.onUpArrow = this.onUpArrow.bind(this);
@@ -45,6 +46,7 @@ export default class MessageComposer extends React.Component {
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this.onEvent = this.onEvent.bind(this);
this.onPageUnload = this.onPageUnload.bind(this);
this.state = {
autocompleteQuery: '',
@@ -52,7 +54,7 @@ export default class MessageComposer extends React.Component {
inputState: {
style: [],
blockType: null,
isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', true),
isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false),
wordCount: 0,
},
showFormatting: UserSettingsStore.getSyncedSetting('MessageComposer.showFormatting', false),
@@ -65,12 +67,21 @@ export default class MessageComposer extends React.Component {
// marked as encrypted.
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
MatrixClientPeg.get().on("event", this.onEvent);
window.addEventListener('beforeunload', this.onPageUnload);
}
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("event", this.onEvent);
}
window.removeEventListener('beforeunload', this.onPageUnload);
}
onPageUnload(event) {
if (this.messageComposerInput) {
this.messageComposerInput.sentHistory.saveLastTextEntry();
}
}
onEvent(event) {
@@ -81,37 +92,33 @@ export default class MessageComposer extends React.Component {
onUploadClick(ev) {
if (MatrixClientPeg.get().isGuest()) {
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't upload files. Please register to upload.",
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
this.refs.uploadInput.click();
}
onUploadFileSelected(files, isPasted) {
if (!isPasted) {
files = files.target.files;
}
onUploadFileSelected(files) {
this.uploadFiles(files.target.files);
}
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
uploadFiles(files) {
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
let TintableSvg = sdk.getComponent("elements.TintableSvg");
const fileList = [];
for (let i=0; i<files.length; i++) {
fileList.push(<li key={i}>
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || 'Attachment'}
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || _t('Attachment')}
</li>);
}
Modal.createDialog(QuestionDialog, {
title: "Upload Files",
title: _t('Upload Files'),
description: (
<div>
<p>Are you sure you want upload the following files?</p>
<p>{ _t('Are you sure you want to upload the following files?') }</p>
<ul style={{listStyle: 'none', textAlign: 'left'}}>
{fileList}
</ul>
@@ -245,11 +252,11 @@ export default class MessageComposer extends React.Component {
if (roomIsEncrypted) {
// FIXME: show a /!\ if there are untrusted devices in the room...
e2eImg = 'img/e2e-verified.svg';
e2eTitle = 'Encrypted room';
e2eTitle = _t('Encrypted room');
e2eClass = 'mx_MessageComposer_e2eIcon';
} else {
e2eImg = 'img/e2e-unencrypted.svg';
e2eTitle = 'Unencrypted room';
e2eTitle = _t('Unencrypted room');
e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
}
@@ -262,15 +269,15 @@ export default class MessageComposer extends React.Component {
if (this.props.callState && this.props.callState !== 'ended') {
hangupButton =
<div key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
<img src="img/hangup.svg" alt="Hangup" title="Hangup" width="25" height="26"/>
<img src="img/hangup.svg" alt={ _t('Hangup') } title={ _t('Hangup') } width="25" height="26"/>
</div>;
} else {
callButton =
<div key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title="Voice call">
<div key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title={ _t('Voice call') }>
<TintableSvg src="img/icon-call.svg" width="35" height="35"/>
</div>;
videoCallButton =
<div key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title="Video call">
<div key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title={ _t('Video call') }>
<TintableSvg src="img/icons-video.svg" width="35" height="35"/>
</div>;
}
@@ -297,7 +304,7 @@ export default class MessageComposer extends React.Component {
// complex because of conference calls.
const uploadButton = (
<div key="controls_upload" className="mx_MessageComposer_upload"
onClick={this.onUploadClick} title="Upload file">
onClick={this.onUploadClick} title={ _t('Upload file') }>
<TintableSvg src="img/icons-upload.svg" width="35" height="35"/>
<input ref="uploadInput" type="file"
style={uploadInputStyle}
@@ -308,7 +315,7 @@ export default class MessageComposer extends React.Component {
const formattingButton = (
<img className="mx_MessageComposer_formatting"
title="Show Text Formatting Toolbar"
title={_t("Show Text Formatting Toolbar")}
src="img/button-text-formatting.svg"
onClick={this.onToggleFormattingClicked}
style={{visibility: this.state.showFormatting ||
@@ -317,7 +324,7 @@ export default class MessageComposer extends React.Component {
);
const placeholderText = roomIsEncrypted ?
"Send an encrypted message…" : "Send a message (unencrypted)…";
_t('Send an encrypted message') + '…' : _t('Send a message (unencrypted)') + '…';
controls.push(
<MessageComposerInput
@@ -329,7 +336,7 @@ export default class MessageComposer extends React.Component {
tryComplete={this._tryComplete}
onUpArrow={this.onUpArrow}
onDownArrow={this.onDownArrow}
onUploadFileSelected={this.onUploadFileSelected}
onFilesPasted={this.uploadFiles}
tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
onContentChanged={this.onInputContentChanged}
onInputStateChanged={this.onInputStateChanged} />,
@@ -344,8 +351,8 @@ export default class MessageComposer extends React.Component {
} else {
controls.push(
<div key="controls_error" className="mx_MessageComposer_noperm_error">
You do not have permission to post to this room
</div>,
{ _t('You do not have permission to post to this room') }
</div>
);
}
@@ -373,7 +380,7 @@ export default class MessageComposer extends React.Component {
mx_filterFlipColor: true,
});
return <img className={className}
title={name}
title={ _t(name) }
onMouseDown={disabled ? null : onFormatButtonClicked}
key={name}
src={`img/button-text-${name}${suffix}.svg`}
@@ -393,11 +400,11 @@ export default class MessageComposer extends React.Component {
<div className="mx_MessageComposer_formatbar" style={this.state.showFormatting ? {} : {display: 'none'}}>
{formatButtons}
<div style={{flex: 1}}></div>
<img title={`Turn Markdown ${this.state.inputState.isRichtextEnabled ? 'on' : 'off'}`}
<img title={ this.state.inputState.isRichtextEnabled ? _t("Turn Markdown on") : _t("Turn Markdown off") }
onMouseDown={this.onToggleMarkdownClicked}
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} />
<img title="Hide Text Formatting Toolbar"
<img title={ _t("Hide Text Formatting Toolbar") }
onClick={this.onToggleFormattingClicked}
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
src="img/icon-text-cancel.svg" />

View File

@@ -28,11 +28,12 @@ import Q from 'q';
import MatrixClientPeg from '../../../MatrixClientPeg';
import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
import SlashCommands from '../../../SlashCommands';
import KeyCode from '../../../KeyCode';
import Modal from '../../../Modal';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher';
import KeyCode from '../../../KeyCode';
import UserSettingsStore from '../../../UserSettingsStore';
import * as RichText from '../../../RichText';
@@ -44,8 +45,6 @@ import {onSendMessageFailed} from './MessageComposerInputOld';
const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
const KEY_M = 77;
const ZWS_CODE = 8203;
const ZWS = String.fromCharCode(ZWS_CODE); // zero width space
function stateToMarkdown(state) {
@@ -61,7 +60,7 @@ function stateToMarkdown(state) {
export default class MessageComposerInput extends React.Component {
static getKeyBinding(e: SyntheticKeyboardEvent): string {
// C-m => Toggles between rich text and markdown modes
if (e.keyCode === KEY_M && KeyBindingUtil.isCtrlKeyCommand(e)) {
if (e.keyCode === KeyCode.KEY_M && KeyBindingUtil.isCtrlKeyCommand(e)) {
return 'toggle-mode';
}
@@ -84,7 +83,6 @@ export default class MessageComposerInput extends React.Component {
this.onAction = this.onAction.bind(this);
this.handleReturn = this.handleReturn.bind(this);
this.handleKeyCommand = this.handleKeyCommand.bind(this);
this.handlePastedFiles = this.handlePastedFiles.bind(this);
this.onEditorContentChanged = this.onEditorContentChanged.bind(this);
this.setEditorState = this.setEditorState.bind(this);
this.onUpArrow = this.onUpArrow.bind(this);
@@ -94,7 +92,7 @@ export default class MessageComposerInput extends React.Component {
this.setDisplayedCompletion = this.setDisplayedCompletion.bind(this);
this.onMarkdownToggleClicked = this.onMarkdownToggleClicked.bind(this);
const isRichtextEnabled = UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', true);
const isRichtextEnabled = UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false);
this.state = {
// whether we're in rich text or markdown mode
@@ -355,6 +353,7 @@ export default class MessageComposerInput extends React.Component {
}
sendTyping(isTyping) {
if (UserSettingsStore.getSyncedSetting('dontSendTypingNotifications', false)) return;
MatrixClientPeg.get().sendTyping(
this.props.room.roomId,
this.isTyping, TYPING_SERVER_TIMEOUT
@@ -476,10 +475,6 @@ export default class MessageComposerInput extends React.Component {
return false;
}
handlePastedFiles(files) {
this.props.onUploadFileSelected(files, true);
}
handleReturn(ev) {
if (ev.shiftKey) {
this.onEditorContentChanged(RichUtils.insertSoftNewline(this.state.editorState));
@@ -508,8 +503,8 @@ export default class MessageComposerInput extends React.Component {
console.error("Command failure: %s", err);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Server error",
description: "Server unavailable, overloaded, or something else went wrong.",
title: _t("Server error"),
description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong.")),
});
});
}
@@ -517,8 +512,8 @@ export default class MessageComposerInput extends React.Component {
console.error(cmd.error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Command error",
description: cmd.error
title: _t("Command error"),
description: cmd.error,
});
}
return true;
@@ -541,9 +536,9 @@ export default class MessageComposerInput extends React.Component {
let sendTextFn = this.client.sendTextMessage;
if (contentText.startsWith('/me')) {
contentText = contentText.replace('/me ', '');
contentText = contentText.substring(4);
// bit of a hack, but the alternative would be quite complicated
if (contentHTML) contentHTML = contentHTML.replace('/me ', '');
if (contentHTML) contentHTML = contentHTML.replace(/\/me ?/, '');
sendHtmlFn = this.client.sendHtmlEmote;
sendTextFn = this.client.sendEmoteMessage;
}
@@ -723,9 +718,10 @@ export default class MessageComposerInput extends React.Component {
<div className={className}>
<img className="mx_MessageComposer_input_markdownIndicator mx_filterFlipColor"
onMouseDown={this.onMarkdownToggleClicked}
title={`Markdown is ${this.state.isRichtextEnabled ? 'disabled' : 'enabled'}`}
title={ this.state.isRichtextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
src={`img/button-md-${!this.state.isRichtextEnabled}.png`} />
<Editor ref="editor"
dir="auto"
placeholder={this.props.placeholder}
editorState={this.state.editorState}
onChange={this.onEditorContentChanged}
@@ -733,7 +729,7 @@ export default class MessageComposerInput extends React.Component {
keyBindingFn={MessageComposerInput.getKeyBinding}
handleKeyCommand={this.handleKeyCommand}
handleReturn={this.handleReturn}
handlePastedFiles={this.handlePastedFiles}
handlePastedFiles={this.props.onFilesPasted}
stripPastedStyles={!this.state.isRichtextEnabled}
onTab={this.onTab}
onUpArrow={this.onUpArrow}
@@ -763,7 +759,7 @@ MessageComposerInput.propTypes = {
onDownArrow: React.PropTypes.func,
onUploadFileSelected: React.PropTypes.func,
onFilesPasted: React.PropTypes.func,
// attempts to confirm currently selected completion, returns whether actually confirmed
tryComplete: React.PropTypes.func,

View File

@@ -20,6 +20,8 @@ var SlashCommands = require("../../../SlashCommands");
var Modal = require("../../../Modal");
var MemberEntry = require("../../../TabCompleteEntries").MemberEntry;
var sdk = require('../../../index');
import { _t } from '../../../languageHandler';
import UserSettingsStore from "../../../UserSettingsStore";
var dis = require("../../../dispatcher");
var KeyCode = require("../../../KeyCode");
@@ -27,7 +29,6 @@ var Markdown = require("../../../Markdown");
var TYPING_USER_TIMEOUT = 10000;
var TYPING_SERVER_TIMEOUT = 30000;
var MARKDOWN_ENABLED = true;
export function onSendMessageFailed(err, room) {
// XXX: temporary logging to try to diagnose
@@ -68,11 +69,15 @@ export default React.createClass({
// The text to use a placeholder in the input box
placeholder: React.PropTypes.string.isRequired,
// callback to handle files pasted into the composer
onFilesPasted: React.PropTypes.func,
},
componentWillMount: function() {
this.oldScrollHeight = 0;
this.markdownEnabled = MARKDOWN_ENABLED;
this.markdownEnabled = !UserSettingsStore.getSyncedSetting('disableMarkdown', false);
var self = this;
this.sentHistory = {
// The list of typed messages. Index 0 is more recent
@@ -290,8 +295,8 @@ export default React.createClass({
else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Unknown command",
description: "Usage: /markdown on|off"
title: _t("Unknown command"),
description: _t("Usage") + ": /markdown on|off",
});
}
return;
@@ -310,8 +315,8 @@ export default React.createClass({
console.error("Command failure: %s", err);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Server error",
description: "Server unavailable, overloaded, or something else went wrong.",
title: _t("Server error"),
description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong.")),
});
});
}
@@ -319,8 +324,8 @@ export default React.createClass({
console.error(cmd.error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Command error",
description: cmd.error
title: _t("Command error"),
description: cmd.error,
});
}
return;
@@ -420,6 +425,7 @@ export default React.createClass({
},
sendTyping: function(isTyping) {
if (UserSettingsStore.getSyncedSetting('dontSendTypingNotifications', false)) return;
MatrixClientPeg.get().sendTyping(
this.props.room.roomId,
this.isTyping, TYPING_SERVER_TIMEOUT
@@ -437,10 +443,27 @@ export default React.createClass({
this.refs.textarea.focus();
},
_onPaste: function(ev) {
const items = ev.clipboardData.items;
const files = [];
for (const item of items) {
if (item.kind === 'file') {
files.push(item.getAsFile());
}
}
if (files.length && this.props.onFilesPasted) {
this.props.onFilesPasted(files);
return true;
}
return false;
},
render: function() {
return (
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
<textarea autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder} />
<textarea dir="auto" autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder}
onPaste={this._onPaste}
/>
</div>
);
}

View File

@@ -16,10 +16,10 @@ limitations under the License.
'use strict';
var React = require('react');
import React from 'react';
import { _t } from '../../../languageHandler';
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
module.exports = React.createClass({
displayName: 'PresenceLabel',
@@ -53,31 +53,30 @@ module.exports = React.createClass({
var d = parseInt(t / (60 * 60 * 24));
if (t < 60) {
if (t < 0) {
return "0s";
return _t("for %(amount)ss", {amount: 0});
}
return s + "s";
return _t("for %(amount)ss", {amount: s});
}
if (t < 60 * 60) {
return m + "m";
return _t("for %(amount)sm", {amount: m});
}
if (t < 24 * 60 * 60) {
return h + "h";
return _t("for %(amount)sh", {amount: h});
}
return d + "d ";
return _t("for %(amount)sd", {amount: d});
},
getPrettyPresence: function(presence) {
if (presence === "online") return "Online";
if (presence === "unavailable") return "Idle"; // XXX: is this actually right?
if (presence === "offline") return "Offline";
if (presence === "online") return _t("Online");
if (presence === "unavailable") return _t("Idle"); // XXX: is this actually right?
if (presence === "offline") return _t("Offline");
return "Unknown";
},
render: function() {
if (this.props.activeAgo >= 0) {
var ago = this.props.currentlyActive ? "now" : (this.getDuration(this.props.activeAgo) + " ago");
// var ago = this.getDuration(this.props.activeAgo) + " ago";
// if (this.props.currentlyActive) ago += " (now?)";
let duration = this.getDuration(this.props.activeAgo);
let ago = this.props.currentlyActive || !duration ? "" : duration;
return (
<div className="mx_PresenceLabel">
{ this.getPrettyPresence(this.props.presenceState) } { ago }

View File

@@ -23,6 +23,9 @@ var sdk = require('../../../index');
var Velociraptor = require('../../../Velociraptor');
require('../../../VelocityBounce');
import { _t } from '../../../languageHandler';
import DateUtils from '../../../DateUtils';
var bounce = false;
try {
@@ -63,9 +66,6 @@ module.exports = React.createClass({
// Timestamp when the receipt was read
timestamp: React.PropTypes.number,
// True to show the full date/time rather than just the time
showFullTimestamp: React.PropTypes.bool,
},
getDefaultProps: function() {
@@ -170,16 +170,10 @@ module.exports = React.createClass({
let title;
if (this.props.timestamp) {
const prefix = "Seen by " + this.props.member.userId + " at ";
let ts = new Date(this.props.timestamp);
if (this.props.showFullTimestamp) {
// "15/12/2016, 7:05:45 PM (@alice:matrix.org)"
title = prefix + ts.toLocaleString();
}
else {
// "7:05:45 PM (@alice:matrix.org)"
title = prefix + ts.toLocaleTimeString();
}
title = _t(
"Seen by %(userName)s at %(dateTime)s",
{userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp))}
);
}
return (

View File

@@ -17,7 +17,9 @@ limitations under the License.
'use strict';
var React = require('react');
var classNames = require('classnames');
var sdk = require('../../../index');
import { _t } from '../../../languageHandler';
var MatrixClientPeg = require('../../../MatrixClientPeg');
var Modal = require("../../../Modal");
var dis = require("../../../dispatcher");
@@ -39,6 +41,7 @@ module.exports = React.createClass({
oobData: React.PropTypes.object,
editing: React.PropTypes.bool,
saving: React.PropTypes.bool,
inRoom: React.PropTypes.bool,
collapsedRhs: React.PropTypes.bool,
onSettingsClick: React.PropTypes.func,
onSaveClick: React.PropTypes.func,
@@ -49,7 +52,7 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
editing: false,
onSettingsClick: function() {},
inRoom: false,
onSaveClick: function() {},
};
},
@@ -117,8 +120,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to set avatar: " + errMsg);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to set avatar.",
title: _t("Error"),
description: _t("Failed to set avatar."),
});
}).done();
},
@@ -185,7 +188,14 @@ module.exports = React.createClass({
'm.room.name', user_id
);
save_button = <AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save</AccessibleButton>;
save_button = (
<AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>
{_t("Save")}
</AccessibleButton>
);
}
if (this.props.onCancelClick) {
cancel_button = <CancelButton onClick={this.props.onCancelClick}/>;
}
@@ -203,7 +213,7 @@ module.exports = React.createClass({
// don't display the search count until the search completes and
// gives us a valid (possibly zero) searchCount.
if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) {
searchStatus = <div className="mx_RoomHeader_searchStatus">&nbsp;(~{ this.props.searchInfo.searchCount } results)</div>;
searchStatus = <div className="mx_RoomHeader_searchStatus">&nbsp;{ _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }</div>;
}
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
@@ -218,17 +228,17 @@ module.exports = React.createClass({
}
}
var roomName = 'Join Room';
var roomName = _t("Join Room");
if (this.props.oobData && this.props.oobData.name) {
roomName = this.props.oobData.name;
} else if (this.props.room) {
roomName = this.props.room.name;
}
const emojiTextClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
name =
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
<EmojiText element="div" className={ "mx_RoomHeader_nametext " + (settingsHint ? "mx_RoomHeader_settingsHint" : "") } title={ roomName }>{roomName}</EmojiText>
<EmojiText dir="auto" element="div" className={emojiTextClasses} title={roomName}>{ roomName }</EmojiText>
{ searchStatus }
</div>;
}
@@ -245,7 +255,7 @@ module.exports = React.createClass({
}
}
if (topic) {
topic_el = <div className="mx_RoomHeader_topic" ref="topic" title={ topic }>{ topic }</div>;
topic_el = <div className="mx_RoomHeader_topic" ref="topic" title={ topic } dir="auto">{ topic }</div>;
}
}
@@ -259,7 +269,7 @@ module.exports = React.createClass({
<div className="mx_RoomHeader_avatarPicker_edit">
<label htmlFor="avatarInput" ref="file_label">
<img src="img/camera.svg"
alt="Upload avatar" title="Upload avatar"
alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
width="17" height="15" />
</label>
<input id="avatarInput" type="file" onChange={ this.onAvatarSelected }/>
@@ -278,7 +288,7 @@ module.exports = React.createClass({
var settings_button;
if (this.props.onSettingsClick) {
settings_button =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title="Settings">
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title={_t("Settings")}>
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
</AccessibleButton>;
}
@@ -294,15 +304,23 @@ module.exports = React.createClass({
var forget_button;
if (this.props.onForgetClick) {
forget_button =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onForgetClick} title="Forget room">
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onForgetClick} title={ _t("Forget room") }>
<TintableSvg src="img/leave.svg" width="26" height="20"/>
</AccessibleButton>;
}
let search_button;
if (this.props.onSearchClick && this.props.inRoom) {
search_button =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title={ _t("Search") }>
<TintableSvg src="img/icons-search.svg" width="35" height="35"/>
</AccessibleButton>;
}
var rightPanel_buttons;
if (this.props.collapsedRhs) {
rightPanel_buttons =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onShowRhsClick} title="Show panel">
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onShowRhsClick} title={ _t('Show panel') }>
<TintableSvg src="img/maximise.svg" width="10" height="16"/>
</AccessibleButton>;
}
@@ -313,9 +331,7 @@ module.exports = React.createClass({
<div className="mx_RoomHeader_rightRow">
{ settings_button }
{ forget_button }
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search">
<TintableSvg src="img/icons-search.svg" width="35" height="35"/>
</AccessibleButton>
{ search_button }
{ rightPanel_buttons }
</div>;
}

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@ limitations under the License.
'use strict';
var React = require("react");
var ReactDOM = require("react-dom");
import { _t } from '../../../languageHandler';
var GeminiScrollbar = require('react-gemini-scrollbar');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var CallHandler = require('../../../CallHandler');
@@ -29,7 +31,14 @@ var Rooms = require('../../../Rooms');
import DMRoomMap from '../../../utils/DMRoomMap';
var Receipt = require('../../../utils/Receipt');
var HIDE_CONFERENCE_CHANS = true;
const HIDE_CONFERENCE_CHANS = true;
const VERBS = {
'm.favourite': 'favourite',
'im.vector.fake.direct': 'tag direct chat',
'im.vector.fake.recent': 'restore',
'm.lowpriority': 'demote',
};
module.exports = React.createClass({
displayName: 'RoomList',
@@ -44,12 +53,15 @@ module.exports = React.createClass({
getInitialState: function() {
return {
isLoadingLeftRooms: false,
totalRoomCount: null,
lists: {},
incomingCall: null,
};
},
componentWillMount: function() {
this.mounted = false;
var cli = MatrixClientPeg.get();
cli.on("Room", this.onRoom);
cli.on("deleteRoom", this.onDeleteRoom);
@@ -61,14 +73,22 @@ module.exports = React.createClass({
cli.on("RoomMember.name", this.onRoomMemberName);
cli.on("accountData", this.onAccountData);
var s = this.getRoomLists();
this.setState(s);
this.refreshRoomList();
// order of the sublists
//this.listOrder = [];
// loop count to stop a stack overflow if the user keeps waggling the
// mouse for >30s in a row, or if running under mocha
this._delayedRefreshRoomListLoopCount = 0
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
// Initialise the stickyHeaders when the component is created
this._updateStickyHeaders(true);
this.mounted = true;
},
componentDidUpdate: function() {
@@ -106,6 +126,8 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
this.mounted = false;
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room", this.onRoom);
@@ -196,30 +218,33 @@ module.exports = React.createClass({
}, 500),
refreshRoomList: function() {
// console.log("DEBUG: Refresh room list delta=%s ms",
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
// );
// TODO: ideally we'd calculate this once at start, and then maintain
// any changes to it incrementally, updating the appropriate sublists
// as needed.
// Alternatively we'd do something magical with Immutable.js or similar.
const lists = this.getRoomLists();
let totalRooms = 0;
for (const l of Object.values(lists)) {
totalRooms += l.length;
}
this.setState({
lists: this.getRoomLists(),
totalRoomCount: totalRooms,
});
// TODO: rather than bluntly regenerating and re-sorting everything
// every time we see any kind of room change from the JS SDK
// we could do incremental updates on our copy of the state
// based on the room which has actually changed. This would stop
// us re-rendering all the sublists every time anything changes anywhere
// in the state of the client.
this.setState(this.getRoomLists());
this._lastRefreshRoomListTs = Date.now();
// this._lastRefreshRoomListTs = Date.now();
},
getRoomLists: function() {
var self = this;
var s = { lists: {} };
const lists = {};
s.lists["im.vector.fake.invite"] = [];
s.lists["m.favourite"] = [];
s.lists["im.vector.fake.recent"] = [];
s.lists["im.vector.fake.direct"] = [];
s.lists["m.lowpriority"] = [];
s.lists["im.vector.fake.archived"] = [];
lists["im.vector.fake.invite"] = [];
lists["m.favourite"] = [];
lists["im.vector.fake.recent"] = [];
lists["im.vector.fake.direct"] = [];
lists["m.lowpriority"] = [];
lists["im.vector.fake.archived"] = [];
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
@@ -233,7 +258,7 @@ module.exports = React.createClass({
// ", prevMembership = " + me.events.member.getPrevContent().membership);
if (me.membership == "invite") {
s.lists["im.vector.fake.invite"].push(room);
lists["im.vector.fake.invite"].push(room);
}
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
// skip past this room & don't put it in any lists
@@ -247,69 +272,48 @@ module.exports = React.createClass({
if (tagNames.length) {
for (var i = 0; i < tagNames.length; i++) {
var tagName = tagNames[i];
s.lists[tagName] = s.lists[tagName] || [];
s.lists[tagNames[i]].push(room);
lists[tagName] = lists[tagName] || [];
lists[tagName].push(room);
}
}
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
s.lists["im.vector.fake.direct"].push(room);
lists["im.vector.fake.direct"].push(room);
}
else {
s.lists["im.vector.fake.recent"].push(room);
lists["im.vector.fake.recent"].push(room);
}
}
else if (me.membership === "leave") {
s.lists["im.vector.fake.archived"].push(room);
lists["im.vector.fake.archived"].push(room);
}
else {
console.error("unrecognised membership: " + me.membership + " - this should never happen");
}
});
if (s.lists["im.vector.fake.direct"].length == 0 &&
MatrixClientPeg.get().getAccountData('m.direct') === undefined &&
!MatrixClientPeg.get().isGuest())
{
// scan through the 'recents' list for any rooms which look like DM rooms
// and make them DM rooms
const oldRecents = s.lists["im.vector.fake.recent"];
s.lists["im.vector.fake.recent"] = [];
for (const room of oldRecents) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
s.lists["im.vector.fake.direct"].push(room);
} else {
s.lists["im.vector.fake.recent"].push(room);
}
}
// save these new guessed DM rooms into the account data
const newMDirectEvent = {};
for (const room of s.lists["im.vector.fake.direct"]) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
const otherPerson = Rooms.getOnlyOtherMember(room, me);
if (!otherPerson) continue;
const roomList = newMDirectEvent[otherPerson.userId] || [];
roomList.push(room.roomId);
newMDirectEvent[otherPerson.userId] = roomList;
}
// if this fails, fine, we'll just do the same thing next time we get the room lists
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
}
//console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
return s;
// we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
/*
this.listOrder = [
"im.vector.fake.invite",
"m.favourite",
"im.vector.fake.recent",
"im.vector.fake.direct",
Object.keys(otherTagNames).filter(tagName=>{
return (!tagName.match(/^m\.(favourite|lowpriority)$/));
}).sort(),
"m.lowpriority",
"im.vector.fake.archived"
];
*/
return lists;
},
_getScrollNode: function() {
if (!this.mounted) return null;
var panel = ReactDOM.findDOMNode(this);
if (!panel) return null;
@@ -337,10 +341,11 @@ module.exports = React.createClass({
var incomingCallBox = document.getElementById("incomingCallBox");
if (incomingCallBox && incomingCallBox.parentElement) {
var scrollArea = this._getScrollNode();
if (!scrollArea) return;
// Use the offset of the top of the scroll area from the window
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
// Use the offset of the top of the componet from the window
// Use the offset of the top of the component from the window
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaHeight = ReactDOM.findDOMNode(this).getBoundingClientRect().height;
@@ -360,6 +365,7 @@ module.exports = React.createClass({
// properly through React
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
var scrollArea = this._getScrollNode();
if (!scrollArea) return;
// Use the offset of the top of the scroll area from the window
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
@@ -457,16 +463,71 @@ module.exports = React.createClass({
this.refs.gemscroll.forceUpdate();
},
_getEmptyContent: function(section) {
const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
if (this.props.collapsed) {
return <RoomDropTarget label="" />;
}
const StartChatButton = sdk.getComponent('elements.StartChatButton');
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
const TintableSvg = sdk.getComponent('elements.TintableSvg');
switch (section) {
case 'im.vector.fake.direct':
return <div className="mx_RoomList_emptySubListTip">
Press
<StartChatButton size="16" callout={true}/>
to start a chat with someone
</div>;
case 'im.vector.fake.recent':
return <div className="mx_RoomList_emptySubListTip">
You're not in any rooms yet! Press
<CreateRoomButton size="16" callout={true}/>
to make a room or
<RoomDirectoryButton size="16" callout={true}/>
to browse the directory
</div>;
}
// We don't want to display drop targets if there are no room tiles to drag'n'drop
if (this.state.totalRoomCount === 0) {
return null;
}
const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section);
return <RoomDropTarget label={labelText} />;
},
_getHeaderItems: function(section) {
const StartChatButton = sdk.getComponent('elements.StartChatButton');
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
switch (section) {
case 'im.vector.fake.direct':
return <span className="mx_RoomList_headerButtons">
<StartChatButton size="16" />
</span>;
case 'im.vector.fake.recent':
return <span className="mx_RoomList_headerButtons">
<RoomDirectoryButton size="16" />
<CreateRoomButton size="16" />
</span>;
}
},
render: function() {
var RoomSubList = sdk.getComponent('structures.RoomSubList');
var self = this;
return (
<GeminiScrollbar className="mx_RoomList_scrollbar"
autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll">
<div className="mx_RoomList">
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
label="Invites"
label={ _t('Invites') }
editable={ false }
order="recent"
selectedRoom={ self.props.selectedRoom }
@@ -477,9 +538,9 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['m.favourite'] }
label="Favourites"
label={ _t('Favourites') }
tagName="m.favourite"
verb="favourite"
emptyContent={this._getEmptyContent('m.favourite')}
editable={ true }
order="manual"
selectedRoom={ self.props.selectedRoom }
@@ -490,9 +551,10 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
label="People"
label={ _t('People') }
tagName="im.vector.fake.direct"
verb="tag direct chat"
emptyContent={this._getEmptyContent('im.vector.fake.direct')}
headerItems={this._getHeaderItems('im.vector.fake.direct')}
editable={ true }
order="recent"
selectedRoom={ self.props.selectedRoom }
@@ -504,9 +566,10 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
label="Rooms"
label={ _t('Rooms') }
editable={ true }
verb="restore"
emptyContent={this._getEmptyContent('im.vector.fake.recent')}
headerItems={this._getHeaderItems('im.vector.fake.recent')}
order="recent"
selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall }
@@ -515,13 +578,13 @@ module.exports = React.createClass({
onHeaderClick={ self.onSubListHeaderClick }
onShowMoreRooms={ self.onShowMoreRooms } />
{ Object.keys(self.state.lists).map(function(tagName) {
{ Object.keys(self.state.lists).map((tagName) => {
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
return <RoomSubList list={ self.state.lists[tagName] }
key={ tagName }
label={ tagName }
tagName={ tagName }
verb={ "tag as " + tagName }
emptyContent={this._getEmptyContent(tagName)}
editable={ true }
order="manual"
selectedRoom={ self.props.selectedRoom }
@@ -535,9 +598,9 @@ module.exports = React.createClass({
}) }
<RoomSubList list={ self.state.lists['m.lowpriority'] }
label="Low priority"
label={ _t('Low priority') }
tagName="m.lowpriority"
verb="demote"
emptyContent={this._getEmptyContent('m.lowpriority')}
editable={ true }
order="recent"
selectedRoom={ self.props.selectedRoom }
@@ -548,7 +611,7 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
label="Historical"
label={ _t('Historical') }
editable={ false }
order="recent"
selectedRoom={ self.props.selectedRoom }

View File

@@ -19,6 +19,7 @@ limitations under the License.
var React = require('react');
var sdk = require('../../../index');
var MatrixClientPeg = require('../../../MatrixClientPeg');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'RoomNameEditor',
@@ -35,8 +36,8 @@ module.exports = React.createClass({
this._initialName = name ? name.getContent().name : '';
this._placeholderName = "Unnamed Room";
if (defaultName && defaultName !== 'Empty room') {
this._placeholderName = _t("Unnamed Room");
if (defaultName && defaultName !== 'Empty room') { // default name from JS SDK, needs no translation as we don't ever show it.
this._placeholderName += " (" + defaultName + ")";
}
},
@@ -55,9 +56,9 @@ module.exports = React.createClass({
placeholderClassName="mx_RoomHeader_placeholder"
placeholder={ this._placeholderName }
blurToCancel={ false }
initialValue={ this._initialName }/>
initialValue={ this._initialName }
dir="auto" />
</div>
);
},
});

View File

@@ -21,6 +21,8 @@ var React = require('react');
var sdk = require('../../../index');
var MatrixClientPeg = require('../../../MatrixClientPeg');
import { _t, _tJsx } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'RoomPreviewBar',
@@ -47,7 +49,7 @@ module.exports = React.createClass({
// The alias that was used to access this room, if appropriate
// If given, this will be how the room is referred to (eg.
// in error messages).
roomAlias: React.PropTypes.object,
roomAlias: React.PropTypes.string,
},
getDefaultProps: function() {
@@ -82,9 +84,9 @@ module.exports = React.createClass({
},
_roomNameElement: function(fallback) {
fallback = fallback || 'a room';
fallback = fallback || _t('a room');
const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
return name ? <b>{ name }</b> : fallback;
return name ? name : fallback;
},
render: function() {
@@ -112,8 +114,7 @@ module.exports = React.createClass({
if (this.props.invitedEmail) {
if (this.state.threePidFetchError) {
emailMatchBlock = <div className="error">
Unable to ascertain that the address this invite was
sent to matches one associated with your account.
{_t("Unable to ascertain that the address this invite was sent to matches one associated with your account.")}
</div>;
} else if (this.state.invitedEmailMxid != MatrixClientPeg.get().credentials.userId) {
emailMatchBlock =
@@ -122,8 +123,10 @@ module.exports = React.createClass({
<img src="img/warning.svg" width="24" height="23" title= "/!\\" alt="/!\\" />
</div>
<div className="mx_RoomPreviewBar_warningText">
This invitation was sent to <b><span className="email">{this.props.invitedEmail}</span></b>, which is not associated with this account.<br/>
You may wish to login with a different account, or add this email to this account.
{_t("This invitation was sent to an email address which is not associated with this account:")}
<b><span className="email">{this.props.invitedEmail}</span></b>
<br/>
{_t("You may wish to login with a different account, or add this email to this account.")}
</div>
</div>;
}
@@ -131,18 +134,24 @@ module.exports = React.createClass({
joinBlock = (
<div>
<div className="mx_RoomPreviewBar_invite_text">
You have been invited to join this room by <b>{ this.props.inviterName }</b>
{ _t('You have been invited to join this room by %(inviterName)s', {inviterName: this.props.inviterName}) }
</div>
<div className="mx_RoomPreviewBar_join_text">
Would you like to <a onClick={ this.props.onJoinClick }>accept</a> or <a onClick={ this.props.onRejectClick }>decline</a> this invitation?
{ _tJsx(
'Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?',
[/<acceptText>(.*?)<\/acceptText>/, /<declineText>(.*?)<\/declineText>/],
[
(sub) => <a onClick={ this.props.onJoinClick }>{sub}</a>,
(sub) => <a onClick={ this.props.onRejectClick }>{sub}</a>
]
)}
</div>
{emailMatchBlock}
</div>
);
} else if (kicked || banned) {
const verb = kicked ? 'kicked' : 'banned';
const roomName = this._roomNameElement('this room');
const roomName = this._roomNameElement(_t('This room'));
const kickerMember = this.props.room.currentState.getMember(
myMember.events.member.getSender()
);
@@ -150,29 +159,39 @@ module.exports = React.createClass({
kickerMember.name : myMember.events.member.getSender();
let reason;
if (myMember.events.member.getContent().reason) {
reason = <div>Reason: {myMember.events.member.getContent().reason}</div>
reason = <div>{_t("Reason: %(reasonText)s", {reasonText: myMember.events.member.getContent().reason})}</div>
}
let rejoinBlock;
if (!banned) {
rejoinBlock = <div><a onClick={ this.props.onJoinClick }><b>Rejoin</b></a></div>;
rejoinBlock = <div><a onClick={ this.props.onJoinClick }><b>{_t("Rejoin")}</b></a></div>;
}
let actionText;
if (kicked) {
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
}
else if (banned) {
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
} // no other options possible due to the kicked || banned check above.
joinBlock = (
<div>
<div className="mx_RoomPreviewBar_join_text">
You have been {verb} from {roomName} by {kickerName}.<br />
{actionText}
<br />
{reason}
{rejoinBlock}
<a onClick={ this.props.onForgetClick }><b>Forget</b></a>
<a onClick={ this.props.onForgetClick }><b>{_t("Forget room")}</b></a>
</div>
</div>
);
} else if (this.props.error) {
var name = this.props.roomAlias || "This room";
var name = this.props.roomAlias || _t("This room");
var error;
if (this.props.error.errcode == 'M_NOT_FOUND') {
error = name + " does not exist";
error = _t("%(roomName)s does not exist.", {roomName: name});
} else {
error = name + " is not accessible at this time";
error = _t("%(roomName)s is not accessible at this time.", {roomName: name});
}
joinBlock = (
<div>
@@ -186,8 +205,12 @@ module.exports = React.createClass({
joinBlock = (
<div>
<div className="mx_RoomPreviewBar_join_text">
You are trying to access { name }.<br/>
<a onClick={ this.props.onJoinClick }><b>Click here</b></a> to join the discussion!
{ _t('You are trying to access %(roomName)s.', {roomName: name}) }
<br/>
{ _tJsx("<a>Click here</a> to join the discussion!",
/<a>(.*?)<\/a>/,
(sub) => <a onClick={ this.props.onJoinClick }><b>{sub}</b></a>
)}
</div>
</div>
);
@@ -196,7 +219,7 @@ module.exports = React.createClass({
if (this.props.canPreview) {
previewBlock = (
<div className="mx_RoomPreviewBar_preview_text">
This is a preview of this room. Room interactions have been disabled.
{ _t('This is a preview of this room. Room interactions have been disabled') }.
</div>
);
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
import q from 'q';
import React from 'react';
import { _t, _tJsx } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import SdkConfig from '../../../SdkConfig';
import sdk from '../../../index';
@@ -45,7 +46,7 @@ const BannedUser = React.createClass({
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createDialog(ConfirmUserActionDialog, {
member: this.props.member,
action: 'Unban',
action: _t('Unban'),
danger: false,
onFinished: (proceed) => {
if (!proceed) return;
@@ -56,8 +57,8 @@ const BannedUser = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to unban: " + err);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "Failed to unban",
title: _t('Error'),
description: _t('Failed to unban'),
});
}).done();
},
@@ -70,7 +71,7 @@ const BannedUser = React.createClass({
<AccessibleButton className="mx_RoomSettings_unbanButton"
onClick={this._onUnbanClick}
>
Unban
{ _t('Unban') }
</AccessibleButton>
{this.props.member.userId}
</li>
@@ -129,14 +130,17 @@ module.exports = React.createClass({
console.error("Failed to get room visibility: " + err);
});
this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => {
this.forceUpdate();
}, (err) => {
this.setState({
scalar_error: err
this.scalarClient = null;
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => {
this.forceUpdate();
}, (err) => {
this.setState({
scalar_error: err
});
});
});
}
dis.dispatch({
action: 'ui_opacity',
@@ -397,13 +401,13 @@ module.exports = React.createClass({
var value = ev.target.value;
Modal.createDialog(QuestionDialog, {
title: "Privacy warning",
title: _t('Privacy warning'),
description:
<div>
Changes to who can read history will only apply to future messages in this room.<br/>
The visibility of existing history will be unchanged.
{ _t('Changes to who can read history will only apply to future messages in this room') }.<br/>
{ _t('The visibility of existing history will be unchanged') }.
</div>,
button: "Continue",
button: _t('Continue'),
onFinished: function(confirmed) {
if (confirmed) {
self.setState({
@@ -490,7 +494,7 @@ module.exports = React.createClass({
ev.preventDefault();
var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
Modal.createDialog(IntegrationsManager, {
src: this.scalarClient.hasCredentials() ?
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) :
null,
onFinished: ()=>{
@@ -520,11 +524,11 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.props.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
var errCode = err.errcode || "unknown error code";
var errCode = err.errcode || _t('unknown error code');
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: `Failed to forget room (${errCode})`
title: _t('Error'),
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
});
});
},
@@ -534,14 +538,14 @@ module.exports = React.createClass({
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning!",
title: _t('Warning!'),
description: (
<div>
<p>End-to-end encryption is in beta and may not be reliable.</p>
<p>You should <b>not</b> yet trust it to secure data.</p>
<p>Devices will <b>not</b> yet be able to decrypt history from before they joined the room.</p>
<p>Once encryption is enabled for a room it <b>cannot</b> be turned off again (for now).</p>
<p>Encrypted messages will not be visible on clients that do not yet implement encryption.</p>
<p>{ _t('End-to-end encryption is in beta and may not be reliable') }.</p>
<p>{ _t('You should not yet trust it to secure data') }.</p>
<p>{ _t('Devices will not yet be able to decrypt history from before they joined the room') }.</p>
<p>{ _t('Once encryption is enabled for a room it cannot be turned off again (for now)') }.</p>
<p>{ _t('Encrypted messages will not be visible on clients that do not yet implement encryption') }.</p>
</div>
),
onFinished: confirm=>{
@@ -569,7 +573,7 @@ module.exports = React.createClass({
<input type="checkbox" ref="blacklistUnverified"
defaultChecked={ isGlobalBlacklistUnverified || isRoomBlacklistUnverified }
disabled={ isGlobalBlacklistUnverified || (this.refs.encrypt && !this.refs.encrypt.checked) }/>
Never send encrypted messages to unverified devices in this room from this device.
{ _t('Never send encrypted messages to unverified devices in this room from this device') }.
</label>;
if (!isEncrypted &&
@@ -579,7 +583,7 @@ module.exports = React.createClass({
<label>
<input type="checkbox" ref="encrypt" onClick={ this.onEnableEncryptionClick }/>
<img className="mx_RoomSettings_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12" />
Enable encryption (warning: cannot be disabled again!)
{ _t('Enable encryption') } { _t('(warning: cannot be disabled again!)') }
</label>
{ settings }
</div>
@@ -593,7 +597,7 @@ module.exports = React.createClass({
? <img className="mx_RoomSettings_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" />
: <img className="mx_RoomSettings_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12" />
}
Encryption is { isEncrypted ? "" : "not " } enabled in this room.
{ isEncrypted ? _t("Encryption is enabled in this room") : _t("Encryption is not enabled in this room") }.
</label>
{ settings }
</div>
@@ -644,12 +648,12 @@ module.exports = React.createClass({
if (Object.keys(user_levels).length) {
userLevelsSection =
<div>
<h3>Privileged Users</h3>
<h3>{ _t('Privileged Users') }</h3>
<ul className="mx_RoomSettings_userLevels">
{Object.keys(user_levels).map(function(user, i) {
return (
<li className="mx_RoomSettings_userLevel" key={user}>
{ user } is a <PowerSelector value={ user_levels[user] } disabled={true}/>
{ _t("%(user)s is a", {user: user}) } <PowerSelector value={ user_levels[user] } disabled={true}/>
</li>
);
})}
@@ -657,7 +661,7 @@ module.exports = React.createClass({
</div>;
}
else {
userLevelsSection = <div>No users have specific privileges in this room.</div>;
userLevelsSection = <div>{ _t('No users have specific privileges in this room') }.</div>;
}
var banned = this.props.room.getMembersWithMembership("ban");
@@ -665,7 +669,7 @@ module.exports = React.createClass({
if (banned.length) {
bannedUsersSection =
<div>
<h3>Banned users</h3>
<h3>{ _t('Banned users') }</h3>
<ul className="mx_RoomSettings_banned">
{banned.map(function(member) {
return (
@@ -680,7 +684,7 @@ module.exports = React.createClass({
if (this._yankValueFromEvent("m.room.create", "m.federate") === false) {
unfederatableSection = (
<div className="mx_RoomSettings_powerLevel">
Ths room is not accessible by remote Matrix servers.
{ _t('This room is not accessible by remote Matrix servers') }.
</div>
);
}
@@ -691,14 +695,14 @@ module.exports = React.createClass({
if (myMember.membership === "join") {
leaveButton = (
<AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onLeaveClick }>
Leave room
{ _t('Leave room') }
</AccessibleButton>
);
}
else if (myMember.membership === "leave") {
leaveButton = (
<AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onForgetClick }>
Forget room
{ _t('Forget room') }
</AccessibleButton>
);
}
@@ -708,8 +712,8 @@ module.exports = React.createClass({
// TODO: support editing custom user_levels
var tags = [
{ name: "m.favourite", label: "Favourite", ref: "tag_favourite" },
{ name: "m.lowpriority", label: "Low priority", ref: "tag_lowpriority" },
{ name: "m.favourite", label: _t('Favourite'), ref: "tag_favourite" },
{ name: "m.lowpriority", label: _t('Low priority'), ref: "tag_lowpriority" },
];
Object.keys(this.state.tags).sort().forEach(function(tagName) {
@@ -722,7 +726,7 @@ module.exports = React.createClass({
if (canSetTag || self.state.tags) {
var tagsSection =
<div className="mx_RoomSettings_tags">
Tagged as: { canSetTag ?
{_t("Tagged as: ")}{ canSetTag ?
(tags.map(function(tag, i) {
return (<label key={ i }>
<input type="checkbox"
@@ -750,7 +754,11 @@ module.exports = React.createClass({
if (this.state.join_rule === "public" && aliasCount == 0) {
addressWarning =
<div className="mx_RoomSettings_warning">
To link to a room it must have <a href="#addresses">an address</a>.
{ _tJsx(
'To link to a room it must have <a>an address</a>.',
/<a>(.*?)<\/a>/,
(sub) => <a href="#addresses">{sub}</a>
)}
</div>;
}
@@ -758,43 +766,46 @@ module.exports = React.createClass({
if (this.state.join_rule !== "public" && this.state.guest_access === "forbidden") {
inviteGuestWarning =
<div className="mx_RoomSettings_warning">
Guests cannot join this room even if explicitly invited. <a href="#" onClick={ (e) => {
{ _t('Guests cannot join this room even if explicitly invited.') } <a href="#" onClick={ (e) => {
this.setState({ join_rule: "invite", guest_access: "can_join" });
e.preventDefault();
}}>Click here to fix</a>.
}}>{ _t('Click here to fix') }</a>.
</div>;
}
var integrationsButton;
var integrationsError;
if (this.state.showIntegrationsError && this.state.scalar_error) {
console.error(this.state.scalar_error);
integrationsError = (
<span className="mx_RoomSettings_integrationsButton_errorPopup">
Could not connect to the integration server
</span>
);
}
let integrationsButton;
let integrationsError;
if (this.scalarClient.hasCredentials()) {
integrationsButton = (
if (this.scalarClient !== null) {
if (this.state.showIntegrationsError && this.state.scalar_error) {
console.error(this.state.scalar_error);
integrationsError = (
<span className="mx_RoomSettings_integrationsButton_errorPopup">
{ _t('Could not connect to the integration server') }
</span>
);
}
if (this.scalarClient.hasCredentials()) {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" onClick={ this.onManageIntegrations }>
Manage Integrations
</div>
);
} else if (this.state.scalar_error) {
integrationsButton = (
{ _t('Manage Integrations') }
</div>
);
} else if (this.state.scalar_error) {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton_error" onClick={ this.onShowIntegrationsError }>
Integrations Error <img src="img/warning.svg" width="17"/>
{ integrationsError }
</div>
);
} else {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" style={{ opacity: 0.5 }}>
Manage Integrations
</div>
);
Integrations Error <img src="img/warning.svg" width="17"/>
{ integrationsError }
</div>
);
} else {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" style={{opacity: 0.5}}>
{ _t('Manage Integrations') }
</div>
);
}
}
return (
@@ -807,28 +818,28 @@ module.exports = React.createClass({
<div className="mx_RoomSettings_toggles">
<div className="mx_RoomSettings_settings">
<h3>Who can access this room?</h3>
<h3>{ _t('Who can access this room?') }</h3>
{ inviteGuestWarning }
<label>
<input type="radio" name="roomVis" value="invite_only"
disabled={ !this.mayChangeRoomAccess() }
onChange={this._onRoomAccessRadioToggle}
checked={this.state.join_rule !== "public"}/>
Only people who have been invited
{ _t('Only people who have been invited') }
</label>
<label>
<input type="radio" name="roomVis" value="public_no_guests"
disabled={ !this.mayChangeRoomAccess() }
onChange={this._onRoomAccessRadioToggle}
checked={this.state.join_rule === "public" && this.state.guest_access !== "can_join"}/>
Anyone who knows the room's link, apart from guests
{ _t('Anyone who knows the room\'s link, apart from guests') }
</label>
<label>
<input type="radio" name="roomVis" value="public_with_guests"
disabled={ !this.mayChangeRoomAccess() }
onChange={this._onRoomAccessRadioToggle}
checked={this.state.join_rule === "public" && this.state.guest_access === "can_join"}/>
Anyone who knows the room's link, including guests
{ _t('Anyone who knows the room\'s link, including guests') }
</label>
{ addressWarning }
<br/>
@@ -837,45 +848,45 @@ module.exports = React.createClass({
<input type="checkbox" disabled={ !roomState.mayClientSendStateEvent("m.room.aliases", cli) }
onChange={ this._onToggle.bind(this, "isRoomPublished", true, false)}
checked={this.state.isRoomPublished}/>
List this room in { MatrixClientPeg.get().getDomain() }'s room directory?
{_t("List this room in %(domain)s's room directory?", { domain: MatrixClientPeg.get().getDomain() })}
</label>
</div>
<div className="mx_RoomSettings_settings">
<h3>Who can read history?</h3>
<h3>{ _t('Who can read history?') }</h3>
<label>
<input type="radio" name="historyVis" value="world_readable"
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
checked={historyVisibility === "world_readable"}
onChange={this._onHistoryRadioToggle} />
Anyone
{_t("Anyone")}
</label>
<label>
<input type="radio" name="historyVis" value="shared"
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
checked={historyVisibility === "shared"}
onChange={this._onHistoryRadioToggle} />
Members only (since the point in time of selecting this option)
{ _t('Members only') } ({ _t('since the point in time of selecting this option') })
</label>
<label>
<input type="radio" name="historyVis" value="invited"
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
checked={historyVisibility === "invited"}
onChange={this._onHistoryRadioToggle} />
Members only (since they were invited)
{ _t('Members only') } ({ _t('since they were invited') })
</label>
<label >
<input type="radio" name="historyVis" value="joined"
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
checked={historyVisibility === "joined"}
onChange={this._onHistoryRadioToggle} />
Members only (since they joined)
{ _t('Members only') } ({ _t('since they joined') })
</label>
</div>
</div>
<div>
<h3>Room Colour</h3>
<h3>{ _t('Room Colour') }</h3>
<ColorSettings ref="color_settings" room={this.props.room} />
</div>
@@ -893,41 +904,41 @@ module.exports = React.createClass({
<UrlPreviewSettings ref="url_preview_settings" room={this.props.room} />
<h3>Permissions</h3>
<h3>{ _t('Permissions') }</h3>
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">The default role for new room members is </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('The default role for new room members is') } </span>
<PowerSelector ref="users_default" value={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged}/>
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To send messages, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('To send messages') }, { _t('you must be a') } </span>
<PowerSelector ref="events_default" value={send_level} controlled={false} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged}/>
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To invite users into the room, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('To invite users into the room') }, { _t('you must be a') } </span>
<PowerSelector ref="invite" value={invite_level} controlled={false} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged}/>
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To configure the room, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('To configure the room') }, { _t('you must be a') } </span>
<PowerSelector ref="state_default" value={state_level} controlled={false} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged}/>
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To kick users, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('To kick users') }, { _t('you must be a') } </span>
<PowerSelector ref="kick" value={kick_level} controlled={false} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged}/>
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To ban users, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('To ban users') }, { _t('you must be a') } </span>
<PowerSelector ref="ban" value={ban_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/>
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To redact messages, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('To remove other users\' messages') }, { _t('you must be a') } </span>
<PowerSelector ref="redact" value={redact_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/>
</div>
{Object.keys(events_levels).map(function(event_type, i) {
return (
<div className="mx_RoomSettings_powerLevel" key={event_type}>
<span className="mx_RoomSettings_powerLevelKey">To send events of type <code>{ event_type }</code>, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">{ _t('To send events of type') } <code>{ event_type }</code>, { _t('you must be a') } </span>
<PowerSelector value={ events_levels[event_type] } controlled={false} disabled={true} onChange={self.onPowerLevelsChanged}/>
</div>
);
@@ -940,9 +951,9 @@ module.exports = React.createClass({
{ bannedUsersSection }
<h3>Advanced</h3>
<h3>{ _t('Advanced') }</h3>
<div className="mx_RoomSettings_settings">
This room's internal ID is <code>{ this.props.room.roomId }</code>
{ _t('This room\'s internal ID is') } <code>{ this.props.room.roomId }</code>
</div>
</div>
);

View File

@@ -98,9 +98,9 @@ module.exports = React.createClass({
}
},
onClick: function() {
onClick: function(ev) {
if (this.props.onClick) {
this.props.onClick(this.props.room.roomId);
this.props.onClick(this.props.room.roomId, ev);
}
},
@@ -224,13 +224,13 @@ module.exports = React.createClass({
if (this.props.selected) {
let nameSelected = <EmojiText>{name}</EmojiText>;
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
label = <div title={ name } className={ nameClasses } dir="auto">{ nameSelected }</div>;
} else {
label = <EmojiText element="div" title={ name } className={ nameClasses }>{name}</EmojiText>;
label = <EmojiText element="div" title={ name } className={ nameClasses } dir="auto">{name}</EmojiText>;
}
} else if (this.state.hover) {
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" room={this.props.room} />;
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" room={this.props.room} dir="auto" />;
}
//var incomingCallBox;

View File

@@ -18,6 +18,7 @@ limitations under the License.
var React = require('react');
var sdk = require('../../../index');
import { _t } from "../../../languageHandler";
module.exports = React.createClass({
displayName: 'RoomTopicEditor',
@@ -43,9 +44,10 @@ module.exports = React.createClass({
<EditableText ref="editor"
className="mx_RoomHeader_topic mx_RoomHeader_editable"
placeholderClassName="mx_RoomHeader_placeholder"
placeholder="Add a topic"
placeholder={_t("Add a topic")}
blurToCancel={ false }
initialValue={ this._initialTopic }/>
initialValue={ this._initialTopic }
dir="auto" />
);
},
});

View File

@@ -60,7 +60,7 @@ module.exports = React.createClass({
}
}
return (
<li data-scroll-token={eventId+"+"+j}>
<li data-scroll-tokens={eventId+"+"+j}>
{ret}
</li>);
},

View File

@@ -17,6 +17,7 @@ var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var sdk = require("../../../index");
import { _t } from '../../../languageHandler';
var GeminiScrollbar = require('react-gemini-scrollbar');
// A list capable of displaying entities which conform to the SearchableEntity
@@ -25,7 +26,6 @@ var SearchableEntityList = React.createClass({
displayName: 'SearchableEntityList',
propTypes: {
searchPlaceholderText: React.PropTypes.string,
emptyQueryShowsAll: React.PropTypes.bool,
showInputBox: React.PropTypes.bool,
onQueryChanged: React.PropTypes.func, // fn(inputText)
@@ -37,7 +37,6 @@ var SearchableEntityList = React.createClass({
getDefaultProps: function() {
return {
showInputBox: true,
searchPlaceholderText: "Search",
entities: [],
emptyQueryShowsAll: false,
onSubmit: function() {},
@@ -118,7 +117,9 @@ var SearchableEntityList = React.createClass({
_createOverflowEntity: function(overflowCount, totalCount) {
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var text = "and " + overflowCount + " other" + (overflowCount > 1 ? "s" : "") + "...";
var text = (overflowCount > 1)
? _t("and %(overflowCount)s others...", { overflowCount: overflowCount })
: _t("and one other...");
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
@@ -137,7 +138,7 @@ var SearchableEntityList = React.createClass({
onChange={this.onQueryChanged} value={this.state.query}
onFocus= {() => { this.setState({ focused: true }); }}
onBlur= {() => { this.setState({ focused: false }); }}
placeholder={this.props.searchPlaceholderText} />
placeholder={ _t("Search") } />
</form>
);
}

View File

@@ -19,6 +19,8 @@ limitations under the License.
import React from 'react';
import dis from '../../../dispatcher';
import AccessibleButton from '../elements/AccessibleButton';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
// cancel button which is shared between room header and simple room header
export function CancelButton(props) {
@@ -27,7 +29,7 @@ export function CancelButton(props) {
return (
<AccessibleButton className='mx_RoomHeader_cancelButton' onClick={onClick}>
<img src="img/cancel.svg" className='mx_filterFlipColor'
width="18" height="18" alt="Cancel"/>
width="18" height="18" alt={_t("Cancel")}/>
</AccessibleButton>
);
}
@@ -45,6 +47,9 @@ export default React.createClass({
// is the RightPanel collapsed?
collapsedRhs: React.PropTypes.bool,
// `src` to a TintableSvg. Optional.
icon: React.PropTypes.string,
},
onShowRhsClick: function(ev) {
@@ -53,9 +58,17 @@ export default React.createClass({
render: function() {
let cancelButton;
let icon;
if (this.props.onCancelClick) {
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
}
if (this.props.icon) {
const TintableSvg = sdk.getComponent('elements.TintableSvg');
icon = <TintableSvg
className="mx_RoomHeader_icon" src={this.props.icon}
width="25" height="25"
/>;
}
let showRhsButton;
/* // don't bother cluttering things up with this for now.
@@ -73,6 +86,7 @@ export default React.createClass({
<div className="mx_RoomHeader" >
<div className="mx_RoomHeader_wrapper">
<div className="mx_RoomHeader_simpleHeader">
{ icon }
{ this.props.title }
{ showRhsButton }
{ cancelButton }

View File

@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@ limitations under the License.
'use strict';
var React = require('react');
import { _t } from '../../../languageHandler';
var sdk = require('../../../index');
module.exports = React.createClass({
@@ -32,14 +34,16 @@ module.exports = React.createClass({
<div className="mx_TopUnreadMessagesBar">
<div className="mx_TopUnreadMessagesBar_scrollUp"
onClick={this.props.onScrollUpClick}>
Jump to first unread message. <span style={{ textDecoration: 'underline' }} onClick={this.props.onCloseClick}>Mark all read</span>
<img src="img/scrollto.svg" width="24" height="24"
alt={ _t('Scroll to unread messages') }
title={ _t('Scroll to unread messages') }/>
{ _t("Jump to first unread message.") }
</div>
<img className="mx_TopUnreadMessagesBar_close"
<img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor"
src="img/cancel.svg" width="18" height="18"
alt="Close" title="Close"
alt={_t("Close")} title={_t("Close")}
onClick={this.props.onCloseClick} />
</div>
);
},
});

View File

@@ -15,13 +15,13 @@ limitations under the License.
*/
import React from 'react';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import AddThreepid from '../../../AddThreepid';
import WithMatrixClient from '../../../wrappers/WithMatrixClient';
import Modal from '../../../Modal';
export default WithMatrixClient(React.createClass({
displayName: 'AddPhoneNumber',
@@ -50,7 +50,7 @@ export default WithMatrixClient(React.createClass({
},
_onPhoneCountryChange: function(phoneCountry) {
this.setState({ phoneCountry: phoneCountry });
this.setState({ phoneCountry: phoneCountry.iso2 });
},
_onPhoneNumberChange: function(ev) {
@@ -83,7 +83,7 @@ export default WithMatrixClient(React.createClass({
console.error("Unable to add phone number: " + err);
let msg = err.message;
Modal.createDialog(ErrorDialog, {
title: "Error",
title: _t("Error"),
description: msg,
});
}).finally(() => {
@@ -98,20 +98,19 @@ export default WithMatrixClient(React.createClass({
if (this._unmounted) return;
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
let msgElements = [
<div key="_static" >A text message has been sent to +{msisdn}.
Please enter the verification code it contains</div>
<div key="_static" >{ _t("A text message has been sent to +%(msisdn)s. Please enter the verification code it contains", { msisdn: msisdn} ) }</div>
];
if (err) {
let msg = err.error;
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
msg = "Incorrect verification code";
msg = _t("Incorrect verification code");
}
msgElements.push(<div key="_error" className="error">{msg}</div>);
}
Modal.createDialog(TextInputDialog, {
title: "Enter Code",
title: _t("Enter Code"),
description: <div>{msgElements}</div>,
button: "Submit",
button: _t("Submit"),
onFinished: (should_verify, token) => {
if (!should_verify) {
this._addThreepid = null;
@@ -147,24 +146,26 @@ export default WithMatrixClient(React.createClass({
return (
<form className="mx_UserSettings_profileTableRow" onSubmit={this._onAddMsisdnSubmit}>
<div className="mx_UserSettings_profileLabelCell">
<label>{_t('Phone')}</label>
</div>
<div className="mx_UserSettings_profileInputCell">
<div className="mx_Login_phoneSection">
<div className="mx_UserSettings_phoneSection">
<CountryDropdown onOptionChange={this._onPhoneCountryChange}
className="mx_Login_phoneCountry"
className="mx_UserSettings_phoneCountry"
value={this.state.phoneCountry}
isSmall={true}
/>
<input type="text"
ref={this._collectAddMsisdnInput}
className="mx_UserSettings_phoneNumberField"
placeholder="Add phone number"
placeholder={ _t('Add phone number') }
value={this.state.phoneNumber}
onChange={this._onPhoneNumberChange}
/>
</div>
</div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<input type="image" value="Add" src="img/plus.svg" width="14" height="14" />
<input type="image" value={_t("Add")} src="img/plus.svg" width="14" height="14" />
</div>
</form>
);

View File

@@ -17,6 +17,7 @@ limitations under the License.
var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var sdk = require('../../../index');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'ChangeAvatar',
@@ -105,7 +106,7 @@ module.exports = React.createClass({
onError: function(error) {
this.setState({
errorText: "Failed to upload profile picture!"
errorText: _t("Failed to upload profile picture!")
});
},
@@ -127,7 +128,7 @@ module.exports = React.createClass({
if (this.props.showUploadSection) {
uploadSection = (
<div className={this.props.className}>
Upload new:
{_t("Upload new:")}
<input type="file" accept="image/*" onChange={this.onFileSelected}/>
{this.state.errorText}
</div>

View File

@@ -18,6 +18,7 @@ limitations under the License.
var React = require('react');
var sdk = require('../../../index');
var MatrixClientPeg = require("../../../MatrixClientPeg");
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'ChangeDisplayName',
@@ -52,7 +53,7 @@ module.exports = React.createClass({
return (
<EditableTextContainer
getInitialValue={this._getDisplayName}
placeholder="No display name"
placeholder={_t("No display name")}
blurToSubmit={true}
onSubmit={this._changeDisplayName} />
);

View File

@@ -21,6 +21,9 @@ var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var sdk = require("../../../index");
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
import sessionStore from '../../../stores/SessionStore';
module.exports = React.createClass({
displayName: 'ChangePassword',
@@ -31,7 +34,10 @@ module.exports = React.createClass({
rowClassName: React.PropTypes.string,
rowLabelClassName: React.PropTypes.string,
rowInputClassName: React.PropTypes.string,
buttonClassName: React.PropTypes.string
buttonClassName: React.PropTypes.string,
confirm: React.PropTypes.bool,
// Whether to autoFocus the new password input
autoFocusNewPasswordInput: React.PropTypes.bool,
},
Phases: {
@@ -47,70 +53,103 @@ module.exports = React.createClass({
onCheckPassword: function(oldPass, newPass, confirmPass) {
if (newPass !== confirmPass) {
return {
error: "New passwords don't match."
error: _t("New passwords don't match")
};
} else if (!newPass || newPass.length === 0) {
return {
error: "Passwords can't be empty"
error: _t("Passwords can't be empty")
};
}
}
},
confirm: true,
};
},
getInitialState: function() {
return {
phase: this.Phases.Edit
phase: this.Phases.Edit,
cachedPassword: null,
};
},
changePassword: function(old_password, new_password) {
var cli = MatrixClientPeg.get();
componentWillMount: function() {
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
this._setStateFromSessionStore();
},
componentWillUnmount: function() {
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
},
_setStateFromSessionStore: function() {
this.setState({
cachedPassword: this._sessionStore.getCachedPassword(),
});
},
changePassword: function(oldPassword, newPassword) {
const cli = MatrixClientPeg.get();
if (!this.props.confirm) {
this._changePassword(cli, oldPassword, newPassword);
return;
}
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning",
title: _t("Warning!"),
description:
<div>
Changing password will currently reset any end-to-end encryption keys on all devices,
making encrypted chat history unreadable, unless you first export your room keys
and re-import them afterwards.
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">will be improved</a>.
{ _t(
'Changing password will currently reset any end-to-end encryption keys on all devices, ' +
'making encrypted chat history unreadable, unless you first export your room keys ' +
'and re-import them afterwards. ' +
'In future this will be improved.'
) } (<a href="https://github.com/vector-im/riot-web/issues/2671">https://github.com/vector-im/riot-web/issues/2671</a>)
</div>,
button: "Continue",
button: _t("Continue"),
extraButtons: [
<button className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
Export E2E room keys
{ _t('Export E2E room keys') }
</button>
],
onFinished: (confirmed) => {
if (confirmed) {
var authDict = {
type: 'm.login.password',
user: cli.credentials.userId,
password: old_password
};
this.setState({
phase: this.Phases.Uploading
});
var self = this;
cli.setPassword(authDict, new_password).then(function() {
self.props.onFinished();
}, function(err) {
self.props.onError(err);
}).finally(function() {
self.setState({
phase: self.Phases.Edit
});
}).done();
this._changePassword(cli, oldPassword, newPassword);
}
},
});
},
_changePassword: function(cli, oldPassword, newPassword) {
const authDict = {
type: 'm.login.password',
user: cli.credentials.userId,
password: oldPassword,
};
this.setState({
phase: this.Phases.Uploading,
});
cli.setPassword(authDict, newPassword).then(() => {
this.props.onFinished();
}, (err) => {
this.props.onError(err);
}).finally(() => {
this.setState({
phase: this.Phases.Edit,
});
}).done();
},
_onExportE2eKeysClicked: function() {
Modal.createDialogAsync(
(cb) => {
@@ -121,60 +160,67 @@ module.exports = React.createClass({
matrixClient: MatrixClientPeg.get(),
}
);
},
},
onClickChange: function() {
var old_password = this.refs.old_input.value;
var new_password = this.refs.new_input.value;
var confirm_password = this.refs.confirm_input.value;
var err = this.props.onCheckPassword(
old_password, new_password, confirm_password
const oldPassword = this.state.cachedPassword || this.refs.old_input.value;
const newPassword = this.refs.new_input.value;
const confirmPassword = this.refs.confirm_input.value;
const err = this.props.onCheckPassword(
oldPassword, newPassword, confirmPassword,
);
if (err) {
this.props.onError(err);
}
else {
this.changePassword(old_password, new_password);
} else {
this.changePassword(oldPassword, newPassword);
}
},
render: function() {
var rowClassName = this.props.rowClassName;
var rowLabelClassName = this.props.rowLabelClassName;
var rowInputClassName = this.props.rowInputClassName;
var buttonClassName = this.props.buttonClassName;
const rowClassName = this.props.rowClassName;
const rowLabelClassName = this.props.rowLabelClassName;
const rowInputClassName = this.props.rowInputClassName;
const buttonClassName = this.props.buttonClassName;
let currentPassword = null;
if (!this.state.cachedPassword) {
currentPassword = <div className={rowClassName}>
<div className={rowLabelClassName}>
<label htmlFor="passwordold">Current password</label>
</div>
<div className={rowInputClassName}>
<input id="passwordold" type="password" ref="old_input" />
</div>
</div>;
}
switch (this.state.phase) {
case this.Phases.Edit:
const passwordLabel = this.state.cachedPassword ?
_t('Password') : _t('New Password');
return (
<div className={this.props.className}>
{ currentPassword }
<div className={rowClassName}>
<div className={rowLabelClassName}>
<label htmlFor="passwordold">Current password</label>
<label htmlFor="password1">{ passwordLabel }</label>
</div>
<div className={rowInputClassName}>
<input id="passwordold" type="password" ref="old_input" />
<input id="password1" type="password" ref="new_input" autoFocus={this.props.autoFocusNewPasswordInput} />
</div>
</div>
<div className={rowClassName}>
<div className={rowLabelClassName}>
<label htmlFor="password1">New password</label>
</div>
<div className={rowInputClassName}>
<input id="password1" type="password" ref="new_input" />
</div>
</div>
<div className={rowClassName}>
<div className={rowLabelClassName}>
<label htmlFor="password2">Confirm password</label>
<label htmlFor="password2">{ _t('Confirm password') }</label>
</div>
<div className={rowInputClassName}>
<input id="password2" type="password" ref="confirm_input" />
</div>
</div>
<AccessibleButton className={buttonClassName}
onClick={this.onClickChange}>
Change Password
onClick={this.onClickChange}
element="button">
{ _t('Change Password') }
</AccessibleButton>
</div>
);

View File

@@ -19,6 +19,7 @@ import classNames from 'classnames';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
export default class DevicesPanel extends React.Component {
@@ -54,10 +55,10 @@ export default class DevicesPanel extends React.Component {
var errtxt;
if (error.httpStatus == 404) {
// 404 probably means the HS doesn't yet support the API.
errtxt = "Your home server does not support device management.";
errtxt = _t("Your home server does not support device management.");
} else {
console.error("Error loading devices:", error);
errtxt = "Unable to load device list.";
errtxt = _t("Unable to load device list");
}
this.setState({deviceLoadError: errtxt});
}
@@ -127,9 +128,9 @@ export default class DevicesPanel extends React.Component {
return (
<div className={classes}>
<div className="mx_DevicesPanel_header">
<div className="mx_DevicesPanel_deviceId">ID</div>
<div className="mx_DevicesPanel_deviceName">Name</div>
<div className="mx_DevicesPanel_deviceLastSeen">Last seen</div>
<div className="mx_DevicesPanel_deviceId">{_t("Device ID")}</div>
<div className="mx_DevicesPanel_deviceName">{_t("Device Name")}</div>
<div className="mx_DevicesPanel_deviceLastSeen">{_t("Last seen")}</div>
<div className="mx_DevicesPanel_deviceButtons"></div>
</div>
{devices.map(this._renderDevice)}

View File

@@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import DateUtils from '../../../DateUtils';
@@ -48,7 +49,7 @@ export default class DevicesPanelEntry extends React.Component {
display_name: value,
}).catch((e) => {
console.error("Error setting device display name", e);
throw new Error("Failed to set display name");
throw new Error(_t("Failed to set display name"));
});
}
@@ -71,6 +72,7 @@ export default class DevicesPanelEntry extends React.Component {
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
Modal.createDialog(InteractiveAuthDialog, {
title: _t("Authentication"),
matrixClient: MatrixClientPeg.get(),
authData: error.data,
makeRequest: this._makeDeleteRequest,
@@ -84,7 +86,7 @@ export default class DevicesPanelEntry extends React.Component {
if (this._unmounted) { return; }
this.setState({
deleting: false,
deleteError: "Failed to delete device",
deleteError: _t("Failed to delete device"),
});
}).done();
}
@@ -132,7 +134,7 @@ export default class DevicesPanelEntry extends React.Component {
deleteButton = (
<div className="mx_textButton"
onClick={this._onDeleteClick}>
Delete
{ _t("Delete") }
</div>
);
}

View File

@@ -18,6 +18,7 @@ limitations under the License.
var React = require("react");
var Notifier = require("../../../Notifier");
var dis = require("../../../dispatcher");
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'EnableNotificationsButton',
@@ -60,13 +61,13 @@ module.exports = React.createClass({
if (this.enabled()) {
return (
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>
Disable Notifications
{_t("Disable Notifications")}
</button>
);
} else {
return (
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>
Enable Notifications
{_t("Enable Notifications")}
</button>
);
}

View File

@@ -18,6 +18,7 @@ var dis = require("../../../dispatcher");
var CallHandler = require("../../../CallHandler");
var sdk = require('../../../index');
var MatrixClientPeg = require("../../../MatrixClientPeg");
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'CallView',
@@ -130,7 +131,11 @@ module.exports = React.createClass({
var voice;
if (this.state.call && this.state.call.type === "voice" && this.props.showVoice) {
var callRoom = MatrixClientPeg.get().getRoom(this.state.call.roomId);
voice = <div className="mx_CallView_voice" onClick={ this.props.onClick }>Active call ({ callRoom.name })</div>;
voice = (
<div className="mx_CallView_voice" onClick={ this.props.onClick }>
{_t("Active call (%(roomName)s)", {roomName: callRoom.name})}
</div>
);
}
return (

View File

@@ -17,6 +17,7 @@ var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var dis = require("../../../dispatcher");
var CallHandler = require("../../../CallHandler");
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'IncomingCallBox',
@@ -45,23 +46,36 @@ module.exports = React.createClass({
room = MatrixClientPeg.get().getRoom(this.props.incomingCall.roomId);
}
var caller = room ? room.name : "unknown";
var caller = room ? room.name : _t("unknown caller");
let incomingCallText = null;
if (this.props.incomingCall) {
if (this.props.incomingCall.type === "voice") {
incomingCallText = _t("Incoming voice call from %(name)s", {name: caller});
}
else if (this.props.incomingCall.type === "video") {
incomingCallText = _t("Incoming video call from %(name)s", {name: caller});
}
else {
incomingCallText = _t("Incoming call from %(name)s", {name: caller});
}
}
return (
<div className="mx_IncomingCallBox" id="incomingCallBox">
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
<div className="mx_IncomingCallBox_title">
Incoming { this.props.incomingCall ? this.props.incomingCall.type : '' } call from { caller }
{incomingCallText}
</div>
<div className="mx_IncomingCallBox_buttons">
<div className="mx_IncomingCallBox_buttons_cell">
<div className="mx_IncomingCallBox_buttons_decline" onClick={this.onRejectClick}>
Decline
{_t("Decline")}
</div>
</div>
<div className="mx_IncomingCallBox_buttons_cell">
<div className="mx_IncomingCallBox_buttons_accept" onClick={this.onAnswerClick}>
Accept
{_t("Accept")}
</div>
</div>
</div>

View File

@@ -17,6 +17,7 @@ limitations under the License.
var MatrixClientPeg = require('./MatrixClientPeg');
var Modal = require('./Modal');
var sdk = require('./index');
import { _t } from './languageHandler';
var dis = require("./dispatcher");
var Rooms = require("./Rooms");
@@ -36,17 +37,11 @@ function createRoom(opts) {
opts = opts || {};
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
const Loader = sdk.getComponent("elements.Spinner");
const client = MatrixClientPeg.get();
if (client.isGuest()) {
setTimeout(()=>{
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't create new rooms. Please register to create room and start a chat."
});
}, 0);
dis.dispatch({action: 'view_set_mxid'});
return q(null);
}
@@ -63,6 +58,11 @@ function createRoom(opts) {
createOpts.is_direct = true;
}
// By default, view the room after creating it
if (opts.andView === undefined) {
opts.andView = true;
}
// Allow guests by default since the room is private and they'd
// need an invite. This means clicking on a 3pid invite email can
// actually drop you right in to a chat.
@@ -96,16 +96,18 @@ function createRoom(opts) {
// room has been created, so we race here with the client knowing that
// the room exists, causing things like
// https://github.com/vector-im/vector-web/issues/1813
dis.dispatch({
action: 'view_room',
room_id: roomId
});
if (opts.andView) {
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
}
return roomId;
}, function(err) {
console.error("Failed to create room " + roomId + " " + err);
Modal.createDialog(ErrorDialog, {
title: "Failure to create room",
description: "Server may be unavailable, overloaded, or you hit a bug.",
title: _t("Failure to create room"),
description: _t("Server may be unavailable, overloaded, or you hit a bug."),
});
return null;
});

View File

@@ -16,13 +16,13 @@ limitations under the License.
'use strict';
var flux = require("flux");
const flux = require("flux");
class MatrixDispatcher extends flux.Dispatcher {
/**
* @param {Object} payload Required. The payload to dispatch.
* Must contain at least an 'action' key.
* @param {boolean} sync Optional. Pass true to dispatch
* @param {boolean=} sync Optional. Pass true to dispatch
* synchronously. This is useful for anything triggering
* an operation that the browser requires user interaction
* for.

View File

@@ -0,0 +1 @@
{}

214
src/i18n/strings/da.json Normal file
View File

@@ -0,0 +1,214 @@
{
"Filter room members": "Filter medlemmer",
"You have no visible notifications": "Du har ingen synlige meddelelser",
"Invites": "Invitationer",
"Favourites": "Favoritter",
"People": "Personilg chat",
"Rooms": "Rum",
"Low priority": "Lav prioritet",
"Historical": "Historisk",
"New passwords must match each other.": "Nye adgangskoder skal matche hinanden.",
"A new password must be entered.": "Der skal indtastes en ny adgangskode.",
"The email address linked to your account must be entered.": "Den emailadresse, der tilhører til din adgang, skal indtastes.",
"Failed to send email: ": "Kunne ikke sende e-mail: ",
"unknown device": "ukendt enhed",
"NOT verified": "IKKE verificeret",
"Blacklisted": "blokeret",
"verified": "verificeret",
"Name": "Navn",
"Device ID": "Enheds-ID",
"Verification": "Verifikation",
"Ed25519 fingerprint": "Ed25519 fingerprint",
"User ID": "Bruger ID",
"Curve25519 identity key": "Curve25519 identitetsnøgle",
"Claimed Ed25519 fingerprint key": "Påstået Ed25519 fingeraftryk nøgle",
"none": "ingen",
"Algorithm": "Algoritme",
"unencrypted": "ukrypteret",
"Decryption error": "Dekrypteringsfejl",
"Session ID": "Sessions ID",
"End-to-end encryption information": "End-to-end krypterings oplysninger",
"Event information": "Hændelses information",
"Sender device information": "Afsende enheds-oplysning",
"Displays action": "Viser handling",
"Bans user with given id": "Forbyder bruger med givet id",
"Deops user with given id": "Fjerner OP af bruger med givet id",
"Invites user with given id to current room": "Inviterer bruger med givet id til nuværende rum",
"Joins room with given alias": "Kommer ind i rum med givet alias",
"Kicks user with given id": "Smid bruger med givet id ud",
"Changes your display nickname": "Ændrer dit visningsnavn",
"Searches DuckDuckGo for results": "Søger DuckDuckGo for resultater",
"Commands": "kommandoer",
"Emoji": "Emoji",
"Sorry, this homeserver is using a login which is not recognised ": "Beklager, denne homeerver bruger et login, der ikke kan genkendes ",
"Login as guest": "Log ind som gæst",
"Return to app": "Tilbage til app",
"Sign in": "Log ind",
"Create a new account": "Oprette en ny bruger",
"Send an encrypted message": "Send en krypteret meddelelse",
"Send a message (unencrypted)": "Send en meddelelse (ukrypteret)",
"Warning!": "Advarsel!",
"accept": "acceptere",
"accepted an invitation": "Godkendt en invitation",
"accepted the invitation for": "Accepteret invitationen til",
"Account": "Konto",
"Add email address": "Tilføj e-mail-adresse",
"Add phone number": "Tilføj telefonnummer",
"Admin": "Administrator",
"Advanced": "Avanceret",
"all room members": "Alle rum medlemmer",
"all room members, from the point they are invited": "Alle rum medlemmer, siden invitations-tidspunkt",
"all room members, from the point they joined": "Alle rum medlemmer, siden de deltog",
"an address": "en adresse",
"and": "og",
"An email has been sent to": "En e-mail blev sendt til",
"answered the call.": "svarede på kaldet",
"anyone": "alle",
"Anyone who knows the room's link, apart from guests": "Alle der kender link til rummet, bortset fra gæster",
"Anyone who knows the room's link, including guests": "Alle der kender link til rummet, inklusiv gæster",
"Are you sure you want to leave the room?": "Er du sikker på du vil forlade rummet?",
"Are you sure you want to reject the invitation?": "Er du sikker på du vil afvise invitationen?",
"Are you sure you want to upload the following files?": "Er du sikker på du vil sende de følgende filer?",
"banned": "bortvist",
"Banned users": "Bortviste brugere",
"Bug Report": "Fejlrapport",
"Bulk Options": "Masseindstillinger",
"Can't connect to homeserver - please check your connectivity and ensure your": "Kan ikke oprette forbindelse til homeserver - Kontroller din forbindelse og sørg for din ",
"Can't load user settings": "Kan ikke indlæse brugerindstillinger",
"changed avatar": "Ændret avatar",
"changed name": "Ændret navn",
"changed their display name from": "Ændret deres visningsnavn fra",
"changed their profile picture": "Ændret deres profilbillede",
"changed the power level of": "Ændret effektniveauet på",
"changed the room name to": "Ændrede rumnavnet til",
"changed the topic to": "Ændret emnet til",
"Changes to who can read history will only apply to future messages in this room": "Ændringer til hvem der kan læse historie gælder kun for fremtidige meddelelser i dette rum",
"Clear Cache and Reload": "Ryd cache og genindlæs",
"Clear Cache": "Ryd cache",
"Click here": "Klik her",
"Click here to fix": "Klik her for at rette",
"*️⃣ Commands": "kommandoer",
"Confirm your new password": "Bekræft din nye adgangskode",
"Continue": "fortsætte",
"Could not connect to the integration server": "Kunne ikke oprette forbindelse til integrationsserveren",
"Create an account": "Opret en brugerkonto",
"Create Room": "Opret rum",
"Cryptography": "Kryptografi",
"Deactivate Account": "Deaktiver brugerkonto",
"Deactivate my account": "Deaktiver min brugerkonto",
"decline": "nedgang",
"Default": "Standard",
"demote": "degradere",
"Devices will not yet be able to decrypt history from before they joined the room": "Enhederne vil ikke være i stand til at dekryptere historikken fra, før de kom til rummet",
"Direct Chat": "Personligt Chat",
"Disable inline URL previews by default": "Deaktiver forrige weblinks forhåndsvisninger som standard",
"Display name": "Visningsnavn",
"Email Address": "Email adresse",
"Email, name or matrix ID": "E-mail, navn eller matrix-id",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Krypterede meddelelser vil ikke være synlige på klienter, der endnu ikke implementerer kryptering",
"Encrypted room": "Krypteret rummet",
"Encryption is enabled in this room": "Kryptering er aktiveret i dette rum",
"Encryption is not enabled in this room": "Kryptering er ikke aktiveret i dette rum",
"ended the call.": "Afsluttede opkaldet.",
"End-to-end encryption is in beta and may not be reliable": "End-to-end kryptering er i beta og kan ikke være pålidelig",
"Error": "Fejl",
"Export E2E room keys": "Eksporter E2E rum nøgler",
"Failed to change password. Is your password correct?": "Kunne ikke ændre adgangskode. Er din adgangskode korrekt?",
"Failed to forget room": "Kunne ikke glemme rummet",
"Failed to leave room": "Kunne ikke forlade rum",
"Failed to reject invitation": "Kunne ikke afvise invitationen",
"Failed to send email": "Kunne ikke sende e-mail",
"Failed to set avatar.": "Kunne ikke angive avatar.",
"Failed to unban": "Var ikke i stand til at ophæve forbuddet",
"Favourite": "Favorit",
"Notifications": "Meddelser",
"Please Register": "Vær venlig at registrere",
"Remove": "Fjerne",
"Settings": "Indstillinger",
"unknown error code": "Ukendt fejlkode",
"en": "Engelsk",
"pt-br": "Brasiliansk Portugisisk",
"de": "Tysk",
"da": "Dansk",
"ru": "Russisk",
"%(targetName)s accepted an invitation.": "%(targetName)s accepterede en invitation.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepteret invitationen til %(displayName)s.",
"%(names)s and %(lastPerson)s are typing": "%(names)s og %(lastPerson)s er ved at skrive",
"%(names)s and one other are typing": "%(names)s og den anden skriver",
"%(names)s and %(count)s others are typing": "%(names)s og %(count)s andre skriver",
"%(senderName)s answered the call.": "%(senderName)s besvarede opkaldet.",
"af": "Afrikaans",
"ar-eg": "Arabisk (Egypten)",
"ar-ma": "Arabisk (Marokko)",
"ar-sa": "Arabisk (Saudiarabien",
"ar-sy": "Arabisk (Syrien)",
"be": "Hviderussisk",
"bg": "Bulgarisk",
"ca": "Katalansk",
"cs": "Tjekkisk",
"de-at": "Tysk (Østrig)",
"de-ch": "Tysk (Schweitz)",
"el": "Græsk",
"en-au": "Engelsk (Australien)",
"en-ca": "Engelsk (Canada)",
"en-ie": "Engelsk (Irland)",
"en-nz": "Engelsk (New Zealand)",
"en-us": "Engelsk (USA)",
"en-za": "Engelsk (Sydafrika)",
"es-ar": "Spansk (Argentina)",
"es-bo": "Spansk (Bolivia)",
"es-cl": "Spansk (Chile)",
"es-ec": "Spansk (Ecuador)",
"es-hn": "Spansk (Honduras)",
"es-mx": "Spansk (Mexico)",
"es-ni": "Spansk (Nicaragua)",
"es-py": "Spansk (Paraguay)",
"es": "Spansk (Spanien)",
"es-uy": "Spansk (Uruguay)",
"es-ve": "Spansk (Venezuela)",
"et": "Estonsk",
"fa": "Farsi",
"fi": "Finsk",
"fr-be": "Fransk (Belgien)",
"fr-ca": "Fransk (Canada)",
"fr-ch": "Fransk (Schweitz)",
"fr": "French",
"ga": "Irsk",
"he": "Hebræisk",
"hi": "Hindi",
"hr": "Kroatisk",
"hu": "Ungarsk",
"id": "Indonesisk",
"is": "Islandsk",
"it": "Italian",
"ja": "Japansk",
"ji": "Yiddish",
"lt": "Littauisk",
"lv": "Lettisk",
"ms": "Malaysisk",
"mt": "Maltesisk",
"nl": "Dutch",
"no": "Norsk",
"pl": "Polsk",
"pt": "Portuguese",
"ro": "Rumænsk",
"sb": "Sorbisk",
"sk": "Slovakisk",
"sl": "Slovensk",
"sq": "Albansk",
"sr": "Serbisk (Latin)",
"sv": "Svensk",
"th": "Thai",
"tn": "Tswana",
"tr": "Tyrkisk",
"ts": "Tonga",
"uk": "Ukrainsk",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamesisk",
"xh": "Xhosa",
"zh-cn": "Kinesisk (Folkerepublikken Kina)",
"zh-sg": "Kinesisk (Singapore)",
"zh-tw": "Kinesisk (Taiwan)",
"zu": "Zulu"
}

915
src/i18n/strings/de_DE.json Normal file
View File

@@ -0,0 +1,915 @@
{
"Filter room members": "Raum-Benutzer filtern",
"You have no visible notifications": "Du hast keine sichtbaren Benachrichtigungen",
"Invites": "Einladungen",
"Favourites": "Favoriten",
"People": "Direkt-Chats",
"Rooms": "Räume",
"Low priority": "Niedrige Priorität",
"Historical": "Archiv",
"New passwords must match each other.": "Die neuen Passwörter müssen identisch sein.",
"A new password must be entered.": "Es muss ein neues Passwort eingegeben werden.",
"The email address linked to your account must be entered.": "Es muss die Email-Adresse eingeben werden, welche zum Account gehört.",
"Failed to send email: ": "Email konnte nicht versendet werden: ",
"unknown device": "Unbekanntes Gerät",
"NOT verified": "NICHT verifiziert",
"Blacklisted": "Blockiert",
"verified": "verifiziert",
"Name": "Name",
"Device ID": "Geräte-ID",
"Verification": "Verifizierung",
"Ed25519 fingerprint": "Ed25519-Fingerprint",
"User ID": "Benutzer-ID",
"Curve25519 identity key": "Curve25519-Identitäts-Schlüssel",
"Claimed Ed25519 fingerprint key": "Geforderter Ed25519 Fingerprint Schlüssel",
"none": "keiner",
"Algorithm": "Algorithmus",
"unencrypted": "unverschlüsselt",
"Decryption error": "Entschlüsselungs Fehler",
"Session ID": "Sitzungs-ID",
"End-to-end encryption information": "Ende-zu-Ende-Verschlüsselungs-Informationen",
"Event information": "Ereignis-Informationen",
"Sender device information": "Absender Geräte Informationen",
"Displays action": "Zeigt Aktionen an",
"Bans user with given id": "Sperrt Benutzer mit der angegebenen ID",
"Deops user with given id": "Entfernt OP beim Benutzer mit der angegebenen ID",
"Invites user with given id to current room": "Lädt den Benutzer mit der angegebenen ID in den aktuellen Raum ein",
"Joins room with given alias": "Betrete Raum mit angegebenen Alias",
"Kicks user with given id": "Kickt Benutzer mit angegebener ID",
"Changes your display nickname": "Ändert deinen angezeigten Nicknamen",
"Change Password": "Passwort ändern",
"Searches DuckDuckGo for results": "Verwendet DuckDuckGo für Suchergebnisse",
"Commands": "Kommandos",
"Emoji": "Emoji",
"Sorry, this homeserver is using a login which is not recognised ": "Entschuldigung, dieser Homeserver nutzt eine Anmeldetechnik, die nicht bekannt ist ",
"Login as guest": "Als Gast anmelden",
"Return to app": "Zurück zur Anwendung",
"Sign in": "Anmelden",
"Create a new account": "Erstelle einen neuen Benutzer",
"Send an encrypted message": "Verschlüsselte Nachricht senden",
"Send a message (unencrypted)": "Nachricht senden (unverschlüsselt)",
"Warning!": "Warnung!",
"Direct Chat": "Privater Chat",
"Error": "Fehler",
"accept": "akzeptiere",
"accepted an invitation": "Einladung akzeptieren",
"accepted the invitation for": "Akzeptierte die Einladung für",
"Add email address": "E-Mail-Adresse hinzufügen",
"Advanced": "Erweitert",
"all room members, from the point they joined": "Alle Raum-Mitglieder, ab dem Zeitpunkt, an dem sie beigetreten sind",
"and": "und",
"An email has been sent to": "Eine E-Mail wurde gesendet an",
"anyone": "Jeder",
"Anyone who knows the room's link, apart from guests": "Alle, denen der Raum-Link bekannt ist, ausgenommen Gäste",
"Anyone who knows the room's link, including guests": "Jeder der den Raum-Link kennt - auch Gäste",
"Are you sure you want to leave the room?": "Bist du sicher, dass du den Raum verlassen willst?",
"Are you sure you want to reject the invitation?": "Bist du sicher, dass die die Einladung ablehnen willst?",
"Are you sure you want to upload the following files?": "Bist du sicher, dass du die folgenden Dateien hochladen möchtest?",
"banned": "gebannt",
"Banned users": "Gebannte Nutzer",
"Bug Report": "Fehlerbericht",
"changed avatar": "änderte Avatar",
"changed their display name from": "änderte seinen Anzeigenamen von",
"changed their profile picture": "änderte sein Profilbild",
"changed the room name to": "änderte den Raumnamen zu",
"changed the topic to": "änderte das Thema zu",
"Changes to who can read history will only apply to future messages in this room": "Änderungen, die bestimmen, wer den Chatverlauf lesen kann, gelten nur für zukünftige Nachrichten in diesem Raum",
"Clear Cache and Reload": "Cache leeren und neu laden",
"Click here": "Hier klicken,",
"Confirm your new password": "Neues Passwort bestätigen",
"Continue": "Fortfahren",
"Create an account": "Erstelle einen Account",
"Create Room": "Raum erstellen",
"Cryptography": "Verschlüsselung",
"Deactivate Account": "Account deaktivieren",
"Deactivate my account": "Meinen Account deaktivieren",
"decline": "Ablehnen",
"Devices will not yet be able to decrypt history from before they joined the room": "Geräte werden nicht in der Lage sein, den Chatverlauf vor dem Betreten des Raumes zu entschlüsseln",
"Display name": "Anzeigename",
"Email Address": "E-Mail-Adresse",
"Email, name or matrix ID": "E-Mail, Name oder Matrix-ID",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Verschlüsselte Nachrichten werden an Clients nicht sichtbar sein, die Verschlüsselung noch nicht implementiert haben",
"Encrypted room": "Verschlüsselter Raum",
"Encryption is enabled in this room": "Verschlüsselung ist in diesem Raum aktiviert",
"Encryption is not enabled in this room": "Verschlüsselung ist in diesem Raum nicht aktiviert",
"ended the call.": "beendete den Anruf.",
"End-to-end encryption is in beta and may not be reliable": "Die Ende-zu-Ende-Verschlüsselung befindet sich aktuell im Beta-Stadium und ist eventuell noch nicht hundertprozentig zuverlässig",
"Failed to send email": "Fehler beim Senden der E-Mail",
"Account": "Konto",
"Add phone number": "Telefonnummer hinzufügen",
"an address": "an Adresse",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Dein Passwort wurde erfolgreich geändert. Du wirst erst Benachrichtigungen auf anderen Geräten empfangen können, wenn du dich dort erneut anmeldest",
"all room members": "Alle Raum-Mitglieder",
"all room members, from the point they are invited": "Alle Raum-Mitglieder, ab dem Zeitpunkt, an dem sie eingeladen werden",
"answered the call.": "beantwortete den Anruf.",
"Can't load user settings": "Benutzereinstellungen können nicht geladen werden",
"changed name": "änderte Namen",
"changed the power level of": "änderte Berechtigungslevel von",
"Clear Cache": "Leere Cache",
"Click here to fix": "Zum reparieren hier klicken",
"*️⃣ Commands": "*️⃣ Befehle",
"Default": "Standard",
"demote": "Berechtigungslevel herabstufen",
"Export E2E room keys": "E2E-Raum-Schlüssel exportieren",
"Failed to change password. Is your password correct?": "Passwortänderung fehlgeschlagen. Ist dein Passwort richtig?",
"Failed to forget room": "Vergessen des Raums schlug fehl",
"Failed to leave room": "Verlassen des Raums fehlgeschlagen",
"Failed to reject invitation": "Einladung konnte nicht abgelehnt werden",
"Failed to set avatar.": "Fehler beim Setzen des Profilbilds.",
"Failed to unban": "Entbannen fehlgeschlagen",
"Failed to upload file": "Datei-Upload fehlgeschlagen",
"Favourite": "Favorit",
"favourite": "Als Favorit setzen",
"Forget room": "Raum vergessen",
"Forgot your password?": "Passwort vergessen?",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Aus Sicherheitsgründen werden beim Ausloggen alle Ende-zu-Ende-Verschlüsselungs-Schlüssel in diesem Browser gelöscht. Wenn du in späteren Riot-Sitzungen den bisherigen Chatverlauf entschlüsseln möchtest, exportiere bitte deine Schlüssel zur sicheren Aufbewahrung.",
"For security, this session has been signed out. Please sign in again.": "Aus Sicherheitsgründen wurde diese Sitzung beendet. Bitte melde dich erneut an.",
"Found a bug?": "Fehler gefunden?",
"Guests cannot join this room even if explicitly invited.": "Gäste können diesem Raum nicht beitreten, auch wenn sie explizit eingeladen wurden.",
"Guests can't set avatars. Please register.": "Gäste können kein Profilbild setzen. Bitte registrieren.",
"Guest users can't upload files. Please register to upload.": "Gäste können keine Dateien hochladen. Bitte zunächst registrieren.",
"had": "hatte",
"Hangup": "Auflegen",
"Homeserver is": "Der Homeserver ist",
"Identity Server is": "Der Identitätsserver ist",
"I have verified my email address": "Ich habe meine E-Mail-Adresse verifiziert",
"Import E2E room keys": "E2E-Raum-Schlüssel importieren",
"Invalid Email Address": "Ungültige E-Mail-Adresse",
"invited": "eingeladen",
"Invite new room members": "Neue Raum-Mitglieder einladen",
"is a": "ist ein",
"is trusted": "wird vertraut",
"Sign in with": "Ich möchte mich anmelden mit",
"joined and left": "hat den Raum betreten und wieder verlassen",
"joined": "hat den Raum betreten",
"joined the room": "trat dem Raum bei",
"Leave room": "Verlasse Raum",
"left and rejoined": "ging(en) und trat(en) erneut bei",
"left": "hat den Raum verlassen",
"left the room": "verließ den Raum",
"Logged in as": "Angemeldet als",
"Logout": "Abmelden",
"made future room history visible to": "mache kommende Raum-Historie sichtbar für",
"Manage Integrations": "Integrationen verwalten",
"Members only": "Nur Mitglieder",
"Mobile phone number": "Mobiltelefonnummer",
"Moderator": "Moderator",
"my Matrix ID": "Meine Matrix-ID",
"Never send encrypted messages to unverified devices from this device": "Niemals verschlüsselte Nachrichten an unverifizierte Geräte von diesem Gerät aus versenden",
"Never send encrypted messages to unverified devices in this room from this device": "Niemals verschlüsselte Nachrichten an unverifizierte Geräte in diesem Raum von diesem Gerät aus senden",
"New password": "Neues Passwort",
"Notifications": "Benachrichtigungen",
" (not supported by this browser)": " (von diesem Browser nicht unterstützt)",
"<not supported>": "<nicht unterstützt>",
"No users have specific privileges in this room": "Kein Benutzer hat in diesem Raum besondere Berechtigungen",
"olm version": "OLM-Version",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Sobald Verschlüsselung für einen Raum aktiviert wird, kann diese (aktuell noch) nicht wieder deaktiviert werden",
"Only people who have been invited": "Nur Personen, die eingeladen wurden",
"or": "oder",
"other": "weiteres",
"others": "andere",
"Password": "Passwort",
"Permissions": "Berechtigungen",
"Phone": "Telefon",
"placed a": "plazierte einen",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Bitte prüfen sie ihre E-Mails und klicken sie auf den enthaltenden Link. Anschließend klicke auf \"Fortsetzen\".",
"Please Register": "Bitte registrieren",
"Privacy warning": "Datenschutzwarnung",
"Privileged Users": "Privilegierte Nutzer",
"Profile": "Profil",
"Refer a friend to Riot:": "Freunde zu Riot einladen:",
"rejected": "abgelehnt",
"Once you&#39;ve followed the link it contains, click below": "Nachdem du dem darin enthaltenen Link gefolgt bist, klicke unten",
"rejected the invitation.": "lehnte die Einladung ab.",
"Reject invitation": "Einladung ablehnen",
"Remove Contact Information?": "Kontakt-Informationen entfernen?",
"removed their display name": "löschte den eigenen Anzeigenamen",
"Remove": "Entfernen",
"requested a VoIP conference": "hat eine VoIP-Konferenz angefordert",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Eine Passwortänderung sorgt aktuell dafür, dass alle Ende-zu-Ende-Schlüssel von allen Geräten zurückgesetzt werden. Dadurch wird die verschlüsselte Chat-Historie unlesbar, es sei denn Sie exportieren vorher Ihre Raum-Schlüssel und importieren sie nachher wieder. In Zukunft wird dies verbessert.",
"restore": "Zum zurücksetzen",
"Return to login screen": "Zur Anmeldemaske zurückkehren",
"Room Colour": "Raumfarbe",
"Room name (optional)": "Raumname (optional)",
"Scroll to unread messages": "Zu den ungelesenen Nachrichten scrollen",
"Send Invites": "Einladungen senden",
"Send Reset Email": "E-Mail für das Zurücksetzen senden",
"sent an image": "hat ein Bild gesendet",
"sent an invitation to": "sandte eine Einladung an",
"sent a video": "hat ein Video gesendet",
"Server may be unavailable or overloaded": "Server ist eventuell nicht verfügbar oder überlastet",
"set a profile picture": "setzte ein Profilbild",
"set their display name to": "setzte den Anzeigenamen auf",
"Settings": "Einstellungen",
"Signed Out": "Abgemeldet",
"Sign out": "Abmelden",
"since the point in time of selecting this option": "ab dem Zeitpunkt, an dem diese Option gewählt wird",
"since they joined": "ab dem Zeitpunkt, an dem sie beigetreten sind",
"since they were invited": "seitdem sie eingeladen wurden",
"Someone": "Jemand",
"Start a chat": "Starte einen Chat",
"Start Chat": "Chat beginnen",
"Success": "Erfolg",
"tag direct chat": "Zum kennzeichnen als direkten Chat",
"The default role for new room members is": "Die Standard-Rolle für neue Raum-Mitglieder ist",
"their invitations": "ihre Einladungen",
"their invitation": "ihre Einladung",
"These are experimental features that may break in unexpected ways. Use with caution": "Dies sind experimentelle Funktionen die in unerwarteter Weise Fehler verursachen können. Mit Vorsicht benutzen",
"The visibility of existing history will be unchanged": "Die Sichtbarkeit der bereits vorhandenen Chat-Historie bleibt unverändert",
"This doesn't appear to be a valid email address": "Dies scheint keine gültige E-Mail-Adresse zu sein",
"this invitation?": "diese Einladung?",
"This is a preview of this room. Room interactions have been disabled": "Dies ist eine Vorschau dieses Raumes. Raum-Interaktionen wurden deaktiviert",
"This room is not accessible by remote Matrix servers": "Andere Matrix-Server können auf diesen Raum nicht zugreifen",
"This room's internal ID is": "Die interne ID dieses Raumes ist",
"To ban users": "Zum Nutzer bannen",
"To configure the room": "Um den Raum zu konfigurieren",
"To invite users into the room": "Um Nutzer in den Raum einzuladen",
"to join the discussion": "um an der Diskussion teilzunehmen",
"To kick users": "Um Nutzer zu entfernen",
"Admin": "Administrator",
"Server may be unavailable, overloaded, or you hit a bug.": "Server ist nicht verfügbar, überlastet oder du bist auf einen Fehler gestoßen.",
"Could not connect to the integration server": "Konnte keine Verbindung zum Integrations-Server herstellen",
"Disable inline URL previews by default": "URL-Vorschau im Chat standardmäßig deaktivieren",
"Guests can't use labs features. Please register.": "Gäste können keine Labor-Funktionen nutzen. Bitte registrieren.",
"Labs": "Labor",
"Show panel": "Zeige Panel",
"To redact messages": "Zum Nachrichten verbergen",
"Can't connect to homeserver - please check your connectivity and ensure your": "Die Verbindung mit dem Homeserver ist fehlgeschlagen. Bitte überprüfe deine Verbindung und stelle sicher, dass dein(e) ",
"tag as": "kennzeichne als",
"To reset your password, enter the email address linked to your account": "Um dein Passwort zurückzusetzen, gib bitte die mit deinem Account verknüpfte E-Mail-Adresse ein",
"To send messages": "Zum Nachrichten senden",
"turned on end-to-end encryption (algorithm": "aktivierte Ende-zu-Ende-Verschlüsselung (Algorithmus",
"Unable to add email address": "E-Mail-Adresse konnte nicht hinzugefügt werden",
"Unable to remove contact information": "Unfähig die Kontakt-Informationen zu löschen",
"Unable to verify email address.": "Unfähig die E-Mail-Adresse zu verifizieren.",
"Unban": "Entbannen",
"Unencrypted room": "Unverschlüsselter Raum",
"unknown error code": "Unbekannter Fehlercode",
"unknown": "unbekannt",
"Upload avatar": "Profilbild hochladen",
"uploaded a file": "lud eine Datei hoch",
"Upload Files": "Dateien hochladen",
"Upload file": "Datei hochladen",
"User Interface": "Benutzeroberfläche",
"User name": "Nutzername",
"Users": "Nutzer",
"User": "Nutzer",
"Verification Pending": "Verifizierung ausstehend",
"Video call": "Video-Anruf",
"Voice call": "Sprachanruf",
"VoIP conference finished.": "VoIP-Konferenz wurde beendet.",
"VoIP conference started.": "VoIP-Konferenz gestartet.",
"(warning: cannot be disabled again!)": "(Warnung: Kann nicht wieder deaktiviert werden!)",
"was banned": "wurde aus dem Raum verbannt",
"was invited": "wurde eingeladen",
"was kicked": "wurde gekickt",
"was unbanned": "wurde entbannt",
"was": "wurde",
"Who can access this room?": "Wer hat Zugang zu diesem Raum?",
"Who can read history?": "Wer kann die Chat-Historie lesen?",
"Who would you like to add to this room?": "Wen möchtest du zu diesem Raum hinzufügen?",
"Who would you like to communicate with?": "Mit wem möchtest du kommunizieren?",
"Would you like to": "Möchtest du",
"You do not have permission to post to this room": "Du hast keine Berechtigung an diesen Raum etwas zu senden",
"You have been invited to join this room by %(inviterName)s": "%(inviterName)s hat dich in diesen Raum eingeladen",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "Du wurdest auf allen Geräten abgemeldet und wirst keine Push-Benachrichtigungen mehr erhalten. Um die Benachrichtigungen zu reaktivieren, musst du dich auf jedem Gerät neu anmelden",
"you must be a": "nötige Rolle",
"Your password has been reset": "Dein Passwort wurde zurückgesetzt",
"You should not yet trust it to secure data": "Du solltest nicht darauf vertrauen um deine Daten abzusichern",
"removed their profile picture": "löschte das eigene Profilbild",
"times": "mal",
"Bulk Options": "Bulk-Optionen",
"Call Timeout": "Anruf-Timeout",
"Conference call failed.": "Konferenzgespräch fehlgeschlagen.",
"Conference calling is in development and may not be reliable.": "Konferenzgespräche befinden sich noch in der Entwicklungsphase und sind möglicherweise nicht zuverlässig nutzbar.",
"Conference calls are not supported in encrypted rooms": "Konferenzgespräche werden in verschlüsselten Räumen nicht unterstützt",
"Conference calls are not supported in this client": "Konferenzgespräche werden von diesem Client nicht unterstützt",
"Existing Call": "Bereits bestehender Anruf",
"Failed to set up conference call": "Konferenzgespräch konnte nicht gestartet werden",
"Failed to verify email address: make sure you clicked the link in the email": "Verifizierung der E-Mail-Adresse fehlgeschlagen: Bitte stelle sicher, dass du den Link in der E-Mail angeklickt hast",
"Failure to create room": "Raumerstellung fehlgeschlagen",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Gastnutzer können keine neuen Räume erstellen. Bitte registriere dich um Räume zu erstellen und Chats zu starten.",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot hat keine Berechtigung Benachrichtigungen zu senden - bitte prüfe deine Browser-Einstellungen",
"Riot was not given permission to send notifications - please try again": "Riot hat das Recht nicht bekommen Benachrichtigungen zu senden. Bitte erneut probieren",
"This email address is already in use": "Diese E-Mail-Adresse wird bereits verwendet",
"This email address was not found": "Diese E-Mail-Adresse konnte nicht gefunden werden",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "Die Datei '%(fileName)s' überschreitet das Größen-Limit für Uploads auf diesem Homeserver",
"The file '%(fileName)s' failed to upload": "Das Hochladen der Datei '%(fileName)s' schlug fehl",
"The remote side failed to pick up": "Die Gegenstelle konnte nicht abheben",
"This phone number is already in use": "Diese Telefonnummer wird bereits verwendet",
"Unable to restore previous session": "Die vorherige Sitzung konnte nicht wiederhergestellt werden",
"Unable to capture screen": "Unfähig den Bildschirm aufzunehmen",
"Unable to enable Notifications": "Benachrichtigungen konnten nicht aktiviert werden",
"Upload Failed": "Upload fehlgeschlagen",
"VoIP is unsupported": "VoIP wird nicht unterstützt",
"You are already in a call.": "Du bist bereits in einem Gespräch.",
"You cannot place a call with yourself.": "Du kannst keinen Anruf mit dir selbst starten.",
"You cannot place VoIP calls in this browser.": "Du kannst keine VoIP-Gespräche in diesem Browser starten.",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "Du musst dich erneut anmelden, um Ende-zu-Ende-Verschlüsselungs-Schlüssel für dieses Gerät zu generieren und um den öffentlichen Schlüssel auf deinem Homeserver zu hinterlegen. Dies muss nur einmal durchgeführt werden, bitte entschuldige die Unannehmlichkeiten.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Deine E-Mail-Adresse scheint nicht mit einer Matrix-ID auf diesem Heimserver verbunden zu sein.",
"Sun": "So",
"Mon": "Mo",
"Tue": "Di",
"Wed": "Mi",
"Thu": "Do",
"Fri": "Fr",
"Sat": "Sa",
"Jan": "Jan",
"Feb": "Feb",
"Mar": "März",
"Apr": "April",
"May": "Mai",
"Jun": "Juni",
"Jul": "Juli",
"Aug": "Aug",
"Sep": "Sep",
"Oct": "Okt",
"Nov": "Nov",
"Dec": "Dez",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s. %(monthName)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Anzeigename eingeben:",
"Upload an avatar:": "Profilbild hochladen:",
"This server does not support authentication with a phone number.": "Dieser Server unterstützt keine Authentifizierung mittels Telefonnummer.",
"Missing password.": "Fehlendes Passwort.",
"Passwords don't match.": "Passwörter stimmen nicht überein.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Passwort zu kurz (min. %(MIN_PASSWORD_LENGTH)s).",
"This doesn't look like a valid email address.": "Dies scheint keine gültige E-Mail-Adresse zu sein.",
"This doesn't look like a valid phone number.": "Dies scheint keine gültige Telefonnummer zu sein.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "Benutzernamen dürfen nur Buchstaben, Nummern, Punkte, Binde- und Unterstriche enthalten.",
"An unknown error occurred.": "Ein unbekannter Fehler ist aufgetreten.",
"I already have an account": "Ich habe bereits einen Account",
"An error occurred: %(error_string)s": "Ein Fehler trat auf: %(error_string)s",
"Topic": "Thema",
"Make this room private": "Mache diesen Raum privat",
"Share message history with new users": "Bisherigen Chatverlauf mit neuen Nutzern teilen",
"Encrypt room": "Raum verschlüsseln",
"To send events of type": "Zum Senden von Ereignissen mit Typ",
"%(names)s and %(lastPerson)s are typing": "%(names)s und %(lastPerson)s schreiben",
"%(targetName)s accepted an invitation.": "%(targetName)s hat eine Einladung angenommen.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s akzeptierte die Einladung für %(displayName)s.",
"%(names)s and one other are typing": "%(names)s und eine weitere Person tippen",
"%(names)s and %(count)s others are typing": "%(names)s und %(count)s weitere Personen schreiben",
"%(senderName)s answered the call.": "%(senderName)s hat den Anruf angenommen.",
"%(senderName)s banned %(targetName)s.": "%(senderName)s hat %(targetName)s aus dem Raum verbannt.",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s hat den Anzeigenamen von \"%(oldDisplayName)s\" auf \"%(displayName)s\" geändert.",
"%(senderName)s changed their profile picture.": "%(senderName)s hat das Profilbild geändert.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s hat das Berechtigungslevel von %(powerLevelDiffText)s geändert.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s änderte den Raumnamen zu %(roomName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s hat das Thema geändert in \"%(topic)s\".",
"/ddg is not a command": "/ddg ist kein Kommando",
"%(senderName)s ended the call.": "%(senderName)s hat den Anruf beendet.",
"Failed to lookup current room": "Aktuellen Raum nachzuschlagen schlug fehl",
"Failed to send request.": "Anfrage konnte nicht gesendet werden.",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s von %(fromPowerLevel)s zu %(toPowerLevel)s",
"%(senderName)s invited %(targetName)s.": "%(senderName)s hat %(targetName)s eingeladen.",
"%(displayName)s is typing": "%(displayName)s schreibt",
"%(targetName)s joined the room.": "%(targetName)s hat den Raum betreten.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s kickte %(targetName)s.",
"%(targetName)s left the room.": "%(targetName)s hat den Raum verlassen.",
"%(senderName)s made future room history visible to": "%(senderName)s machte die zukünftige Raumhistorie sichtbar für",
"Missing room_id in request": "Fehlende room_id in Anfrage",
"Missing user_id in request": "Fehlende user_id in Anfrage",
"Must be viewing a room": "Muss einen Raum ansehen",
"New Composer & Autocomplete": "Neuer Eingabeverarbeiter & Autovervollständigung",
"(not supported by this browser)": "(wird von diesem Browser nicht unterstützt)",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s startete einen %(callType)s-Anruf.",
"Power level must be positive integer.": "Berechtigungslevel muss eine positive ganze Zahl sein.",
"Reason": "Grund",
"%(targetName)s rejected the invitation.": "%(targetName)s hat die Einladung abgelehnt.",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s hat den Anzeigenamen entfernt (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s hat das Profilbild gelöscht.",
"%(senderName)s requested a VoIP conference.": "%(senderName)s möchte eine VoIP-Konferenz beginnen.",
"Room %(roomId)s not visible": "Raum %(roomId)s ist nicht sichtbar",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s hat ein Bild gesendet.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s hat %(targetDisplayName)s in diesen Raum eingeladen.",
"%(senderName)s set a profile picture.": "%(senderName)s hat ein Profilbild gesetzt.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s hat den Anzeigenamen geändert in %(displayName)s.",
"This room is not recognised.": "Dieser Raum wurde nicht erkannt.",
"These are experimental features that may break in unexpected ways": "Dies sind experimentelle Funktionen, die in unerwarteter Weise Fehler verursachen können",
"To use it, just wait for autocomplete results to load and tab through them.": "Um diese Funktion zu nutzen, warte einfach auf die Autovervollständigungsergebnisse und benutze dann die TAB-Taste zum durchblättern.",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s hat die Ende-zu-Ende-Verschlüsselung aktiviert (Algorithmus: %(algorithm)s).",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s zog Bann für %(targetName)s zurück.",
"Usage": "Verwendung",
"Use with caution": "Mit Vorsicht zu verwenden",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s zog die Einladung für %(targetName)s zurück.",
"You need to be able to invite users to do that.": "Du musst die Berechtigung haben, Nutzer einzuladen, um diese Aktion ausführen zu können.",
"You need to be logged in.": "Du musst angemeldet sein.",
"There are no visible files in this room": "Es gibt keine sichtbaren Dateien in diesem Raum",
"Error changing language": "Fehler beim Ändern der Sprache",
"Riot was unable to find the correct Data for the selected Language.": "Riot war nicht in der Lage die korrekten Daten für die ausgewählte Sprache zu finden.",
"Connectivity to the server has been lost.": "Verbindung zum Server wurde unterbrochen.",
"Sent messages will be stored until your connection has returned.": "Gesendete Nachrichten werden gespeichert, bis die Internetverbindung wiederhergestellt wurde.",
"Auto-complete": "Autovervollständigung",
"Resend all": "Alle erneut senden",
"cancel all": "alles abbrechen",
"now. You can also select individual messages to resend or cancel.": "jetzt. Du kannst auch einzelne Nachrichten zum erneuten Senden oder Abbrechen auswählen.",
"Active call": "Aktiver Anruf",
"withdrawn": "zurückgezogen",
"To link to a room it must have": "Um einen Raum zu verlinken, benötigt er",
"were": "wurden",
"en": "Englisch",
"pt-br": "Portugisisch (Brasilien)",
"de": "Deutsch",
"da": "Dänisch",
"ru": "Russisch",
"Drop here %(toAction)s": "Hierher ziehen: %(toAction)s",
"Drop here to tag %(section)s": "Hierher ziehen: %(section)s taggen",
"Press": "Drücke",
"tag as %(tagName)s": "als %(tagName)s taggen",
"to browse the directory": "um das Raum-Verzeichnis zu durchsuchen",
"to demote": "um das Berechtigungslevel herabzusetzen",
"to favourite": "zum Favorisieren",
"to make a room or": "um einen Raum zu erstellen, oder",
"to restore": "zum wiederherstellen",
"to start a chat with someone": "um einen Chat mit jemandem zu starten",
"to tag direct chat": "als direkten Chat markieren",
"You're not in any rooms yet! Press": "Du bist noch keinem Raum beigetreten! Drücke",
"click to reveal": "Klicke zum anzeigen",
"To remove other users' messages": "Um Nachrichten anderer Nutzer zu verbergen",
"You are trying to access %(roomName)s.": "Du versuchst, auf den Raum \"%(roomName)s\" zuzugreifen.",
"af": "Afrikaans",
"ar-ae": "Arabisch (VAE)",
"ar-bh": "Arabisch (Bahrain)",
"ar-dz": "Arabisch (Algerien)",
"ar-eg": "Arabisch (Ägypten)",
"ar-iq": "Arabisch (Irak)",
"ar-jo": "Arabisch (Jordanien)",
"ar-kw": "Arabisch (Kuwait)",
"ar-lb": "Arabisch (Libanon)",
"ar-ly": "Arabisch (Lybien)",
"ar-ma": "Arabisch (Marokko)",
"ar-om": "Arabisch (Oman)",
"ar-qa": "Arabisch (Katar)",
"ar-sa": "Arabisch (Saudi Arabien)",
"ar-sy": "Arabisch (Syrien)",
"ar-tn": "Arabisch (Tunesien)",
"ar-ye": "Arabisch (Jemen)",
"be": "Weißrussisch",
"bg": "Bulgarisch",
"cs": "Tschechisch",
"de-at": "Deutsch (Österreich)",
"de-ch": "Deutsch (Schweiz)",
"de-li": "Deutsch (Liechtenstein)",
"de-lu": "Deutsch (Luxemburg)",
"el": "Neugriechisch",
"en-au": "Englisch (Australien)",
"en-bz": "Englisch (Belize)",
"en-ca": "Englisch (Kanada)",
"en-gb": "Englisch (Vereinigtes Königreich)",
"en-ie": "Englisch (Irland)",
"en-jm": "Englisch (Jamaika)",
"en-nz": "Englisch (Neuseeland)",
"en-tt": "Englisch (Trinidad)",
"en-us": "Englisch (Vereinigte Staaten)",
"en-za": "Englisch (Südafrika)",
"es-ar": "Spanisch (Argentinien)",
"es-bo": "Spanisch (Bolivien)",
"es-cl": "Spanisch (Chile)",
"es-co": "Spanisch (Kolumbien)",
"es-cr": "Spanisch (Costa Rica)",
"es-do": "Spanisch (Dominikanische Republik)",
"es-ec": "Spanisch (Ecuador)",
"es-gt": "Spanisch (Guatemala)",
"es-hn": "Spanisch (Honduras)",
"es-mx": "Spanisch (Mexiko)",
"es-ni": "Spanisch (Nicaragua)",
"es-pa": "Spanisch (Panama)",
"es-pe": "Spanisch (Peru)",
"es-pr": "Spanisch (Puerto Rico)",
"es-py": "Spanisch (Paraguay)",
"es": "Spanisch (Spanien)",
"es-sv": "Spanisch (El Salvador)",
"es-uy": "Spanisch (Uruguay)",
"es-ve": "Spanisch (Venezuela)",
"et": "Estländisch",
"eu": "Baskisch (Baskenland)",
"fa": "Persisch (Farsi)",
"fr-be": "Französisch (Belgien)",
"fr-ca": "Französisch (Kanada)",
"fr-ch": "Französisch (Schweiz)",
"fr": "Französisch",
"fr-lu": "Französisch (Luxemburg)",
"gd": "Gälisch (Schottland)",
"he": "Hebräisch",
"hr": "Kroatisch",
"hu": "Ungarisch",
"id": "Indonesisch",
"is": "Isländisch",
"it-ch": "Italienisch (Schweiz)",
"it": "Italienisch",
"ja": "Japanisch",
"ji": "Jiddisch",
"ko": "Koreanisch",
"lt": "Litauisch",
"lv": "Lettisch",
"mk": "Mazedonisch (FYROM)",
"ms": "Malaysisch",
"mt": "Maltesisch",
"nl-be": "Niederländisch (Belgien)",
"nl": "Niederländisch",
"no": "Norwegisch",
"pl": "Polnisch",
"pt": "Portugiesisch",
"rm": "Rätoromanisch",
"ro-mo": "Rumänisch (Republik Moldau/Moldawien)",
"ro": "Rumänisch",
"ru-mo": "Russisch (Republik Moldau/Moldawien)",
"sb": "Sorbisch",
"sk": "Slowakisch",
"sl": "Slowenisch",
"sq": "Albanisch",
"sr": "Serbisch",
"sv-fi": "Schwedisch (Finnland)",
"sv": "Schwedisch",
"sx": "Sutu",
"sz": "Samisch (Lappisch)",
"th": "Thailändisch",
"tn": "Setswana",
"tr": "Türkisch",
"ts": "Tsonga",
"uk": "Ukrainisch",
"ur": "Urdu",
"ve": "Tshivenda",
"vi": "Vietnamesisch",
"zh-cn": "Chinesisch (Volksrepublik China)",
"zh-hk": "Chinesisch (Hong Kong SAR)",
"zh-sg": "Chinesisch (Singapur)",
"zh-tw": "Chinesisch (Taiwan)",
"zu": "Zulu",
"ca": "Katalanisch",
"fi": "Finnisch",
"fo": "Färöisch",
"ga": "Irisch",
"hi": "Hindi",
"xh": "Xhosa",
"Monday": "Montag",
"Tuesday": "Dienstag",
"Wednesday": "Mittwoch",
"Thursday": "Donnerstag",
"Friday": "Freitag",
"Saturday": "Samstag",
"Sunday": "Sonntag",
"Failed to forget room %(errCode)s": "Das Entfernen des Raums %(errCode)s aus deiner Liste ist fehlgeschlagen",
"Failed to join the room": "Fehler beim Betreten des Raumes",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Eine Textnachricht wurde an +%(msisdn)s gesendet. Bitte gebe den Verifikationscode ein, den er beinhaltet",
"and %(overflowCount)s others...": "und %(overflowCount)s weitere...",
"and one other...": "und ein(e) weitere(r)...",
"Are you sure?": "Bist du sicher?",
"Attachment": "Anhang",
"Ban": "Verbannen",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Verbindungsaufbau zum Heimserver nicht möglich - bitte Internetverbindung überprüfen und sicherstellen, ob das <a>SSL-Zertifikat des Heimservers</a> vertrauenswürdig ist.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Kann nicht zum Heimserver via HTTP verbinden, wenn eine HTTPS-Url in deiner Adresszeile steht. Nutzer HTTPS oder <a>aktiviere unsichere Skripte</a>.",
"changing room on a RoomView is not supported": "Das Ändern eines Raumes in einer RaumAnsicht wird nicht unterstützt",
"Click to mute audio": "Klicke um den Ton stumm zu stellen",
"Click to mute video": "Klicken, um das Video stummzuschalten",
"Command error": "Befehlsfehler",
"Decrypt %(text)s": "Entschlüssele %(text)s",
"Delete": "Löschen",
"Devices": "Geräte",
"Direct chats": "Direkte Chats",
"Disinvite": "Einladung zurückziehen",
"Download %(text)s": "%(text)s herunterladen",
"Enter Code": "Code eingeben",
"Failed to ban user": "Bannen des Nutzers fehlgeschlagen",
"Failed to change power level": "Ändern des Berechtigungslevels fehlgeschlagen",
"Failed to delete device": "Löschen des Geräts fehlgeschlagen",
"Failed to join room": "Betreten des Raumes ist fehlgeschlagen",
"Failed to kick": "Kicken fehlgeschlagen",
"Failed to mute user": "Stummschalten des Nutzers fehlgeschlagen",
"Failed to reject invite": "Ablehnen der Einladung ist fehlgeschlagen",
"Failed to save settings": "Einstellungen konnten nicht gespeichert werden",
"Failed to set display name": "Anzeigename konnte nicht gesetzt werden",
"Fill screen": "Fülle Bildschirm",
"Hide Text Formatting Toolbar": "Verberge Text-Formatierungs-Toolbar",
"Incorrect verification code": "Falscher Verifizierungscode",
"Invalid alias format": "Ungültiges Alias-Format",
"Invalid address format": "Ungültiges Adressformat",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' ist kein gültiges Adressformat",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' hat kein valides Aliasformat",
"Join Room": "Dem Raum beitreten",
"Kick": "Kicke",
"Level": "Berechtigungslevel",
"Local addresses for this room:": "Lokale Adressen dieses Raumes:",
"Markdown is disabled": "Markdown ist deaktiviert",
"Markdown is enabled": "Markdown ist aktiviert",
"Message not sent due to unknown devices being present": "Nachrichten wurden nicht gesendet, da unbekannte Geräte anwesend sind",
"New address (e.g. #foo:%(localDomain)s)": "Neue Adresse (z.B. #foo:%(localDomain)s)",
"not set": "nicht gesetzt",
"not specified": "nicht spezifiziert",
"No devices with registered encryption keys": "Keine Geräte mit registrierten Verschlüsselungs-Schlüsseln",
"No more results": "Keine weiteren Ergebnisse",
"No results": "Keine Ergebnisse",
"OK": "OK",
"Revoke Moderator": "Moderator zurückziehen",
"Search": "Suchen",
"Search failed": "Suche ist fehlgeschlagen",
"Server error": "Server-Fehler",
"Server may be unavailable, overloaded, or search timed out :(": "Der Server ist entweder nicht verfügbar, überlastet oder die Suche wurde wegen Zeitüberschreitung abgebrochen :(",
"Server may be unavailable, overloaded, or the file too big": "Server ist entweder nicht verfügbar, überlastet oder die Datei ist zu groß",
"Server unavailable, overloaded, or something else went wrong.": "Server nicht verfügbar, überlastet oder etwas anderes lief falsch.",
"Some of your messages have not been sent.": "Einige deiner Nachrichten wurden nicht gesendet.",
"Submit": "Absenden",
"The main address for this room is: %(canonical_alias_section)s": "Die Hauptadresse für diesen Raum ist: %(canonical_alias_section)s",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Diese Aktion kann nicht von einem Gast ausgeführt werden. Bitte registriere dich um dies zu tun.",
"%(actionVerb)s this person?": "Diese Person %(actionVerb)s?",
"This room has no local addresses": "Dieser Raum hat keine lokale Adresse",
"This room is private or inaccessible to guests. You may be able to join if you register.": "Dieser Raum ist privat oder für Gäste nicht zugänglich. Du kannst jedoch eventuell beitreten, wenn du dich registrierst.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Versuchte einen spezifischen Punkt in der Raum-Chronik zu laden, aber du hast keine Berechtigung die angeforderte Nachricht anzuzeigen.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Der Versuch, einen spezifischen Punkt im Chatverlauf zu laden, ist fehlgeschlagen. Der Punkt konnte nicht gefunden werden.",
"Turn Markdown off": "Markdown deaktiveren",
"Turn Markdown on": "Markdown einschalten",
"Unable to load device list": "Geräteliste konnte nicht geladen werden",
"Unknown command": "Unbekannter Befehl",
"Unknown room %(roomId)s": "Unbekannter Raum %(roomId)s",
"Usage: /markdown on|off": "Verwendung: /markdown on|off",
"You seem to be in a call, are you sure you want to quit?": "Du scheinst in einem Anruf zu sein. Bist du sicher schließen zu wollen?",
"You seem to be uploading files, are you sure you want to quit?": "Du scheinst Dateien hochzuladen. Bist du sicher schließen zu wollen?",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Du wirst diese Änderung nicht rückgängig machen können, da der Nutzer dasselbe Berechtigungslevel wie du selbst erhalten wird.",
"Make Moderator": "Zum Moderator machen",
"Room": "Raum",
"(~%(searchCount)s results)": "(~%(searchCount)s Ergebnisse)",
"Cancel": "Abbrechen",
"bold": "fett",
"italic": "kursiv",
"strike": "durchstreichen",
"underline": "unterstreichen",
"code": "Code",
"quote": "Zitat",
"bullet": "Aufzählung",
"Click to unmute video": "Klicken, um die Video-Stummschaltung zu deaktivieren",
"Click to unmute audio": "Klicken, um den Ton wieder einzuschalten",
"Failed to load timeline position": "Laden der Position im Zeitstrahl fehlgeschlagen",
"Failed to toggle moderator status": "Umschalten des Moderator-Status fehlgeschlagen",
"Enable encryption": "Verschlüsselung aktivieren",
"The main address for this room is": "Die Hauptadresse für diesen Raum ist",
"Autoplay GIFs and videos": "GIF-Dateien und Videos automatisch abspielen",
"Don't send typing notifications": "Schreibbenachrichtigungen unterdrücken",
"Hide read receipts": "Lesebestätigungen verbergen",
"Never send encrypted messages to unverified devices in this room": "In diesem Raum keine verschlüsselten Nachrichten an unverifizierte Geräte senden",
"numbullet": "Nummerierung",
"%(items)s and %(remaining)s others": "%(items)s und %(remaining)s weitere",
"%(items)s and one other": "%(items)s und ein(e) weitere(r)",
"%(items)s and %(lastItem)s": "%(items)s und %(lastItem)s",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)ssind dem Raum %(repeats)s mal beigetreten",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)shat den Raum %(repeats)s mal betreten",
"%(severalUsers)sjoined": "%(severalUsers)shaben den Raum betreten",
"%(oneUser)sjoined": "%(oneUser)shat den Raum betreten",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sverließen %(repeats)s mal den Raum",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sging %(repeats)s mal",
"%(severalUsers)sleft": "%(severalUsers)shaben den Raum verlassen",
"%(oneUser)sleft": "%(oneUser)sging",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)shaben den Raum %(repeats)s mal betreten und wieder verlassen",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)shat den Raum %(repeats)s mal betreten und wieder verlassen",
"%(severalUsers)sjoined and left": "%(severalUsers)shaben den Raum betreten und wieder verlassen",
"%(oneUser)sjoined and left": "%(oneUser)shat den Raum betreten und wieder verlassen",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)shaben den Raum verlassen und %(repeats)s mal neu betreten",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)shat den Raum %(repeats)s mal verlassen und wieder neu betreten",
"%(severalUsers)sleft and rejoined": "%(severalUsers)shaben den Raum verlassen und wieder neu betreten",
"%(oneUser)sleft left and rejoined": "%(oneUser)sging und trat erneut bei",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)shaben ihre Einladung %(repeats)s mal abgelehnt",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)shat die Einladung %(repeats)s mal abgelehnt",
"%(severalUsers)srejected their invitations": "%(severalUsers)shaben ihre Einladung abgelehnt",
"%(oneUser)srejected their invitation": "%(oneUser)shat die Einladung abgelehnt",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)szogen ihre Einladungen %(repeats)s mal zurück",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)szog seine/ihre Einladung %(repeats)s mal zurück",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)szogen ihre Einladungen zurück",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)szog seine/ihre Einladung zurück",
"were invited %(repeats)s times": "wurden %(repeats)s mal eingeladen",
"was invited %(repeats)s times": "wurde %(repeats)s mal eingeladen",
"were invited": "wurden eingeladen",
"were banned %(repeats)s times": "wurden %(repeats)s mal aus dem Raum ausgeschlossen",
"was banned %(repeats)s times": "wurde %(repeats)s mal aus dem Raum ausgeschlossen",
"were banned": "wurden aus dem Raum ausgeschlossen",
"were unbanned %(repeats)s times": "wurden %(repeats)s mal entbannt",
"was unbanned %(repeats)s times": "wurde %(repeats)s mal entbannt",
"were unbanned": "wurden entbannt",
"were kicked %(repeats)s times": "wurden %(repeats)s mal gekickt",
"was kicked %(repeats)s times": "wurde %(repeats)s mal gekickt",
"were kicked": "wurden aus dem Raum entfernt",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)shaben ihren Namen %(repeats)s mal geändert",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)shat den Namen %(repeats)s mal geändert",
"%(severalUsers)schanged their name": "%(severalUsers)shaben ihre Namen geändert",
"%(oneUser)schanged their name": "%(oneUser)shat den Namen geändert",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)shaben %(repeats)s mal ihr Profilbild geändert",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)shat %(repeats)s mal das Profilbild geändert",
"%(severalUsers)schanged their avatar": "%(severalUsers)shaben ihr Profilbild geändert",
"%(oneUser)schanged their avatar": "%(oneUser)shat das Profilbild geändert",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s. %(monthName)s %(fullYear)s %(time)s",
"%(oneUser)sleft and rejoined": "%(oneUser)shat den Raum verlassen und wieder neu betreten",
"A registered account is required for this action": "Für diese Aktion ist ein registrierter Account notwendig",
"Access Token:": "Zugangs-Token:",
"Always show message timestamps": "Nachrichten-Zeitstempel immer anzeigen",
"Authentication": "Authentifizierung",
"An error has occurred.": "Ein Fehler ist aufgetreten.",
"Confirm password": "Passwort bestätigen",
"Current password": "Aktuelles Passwort",
"Email": "E-Mail",
"Interface Language": "Oberflächen-Sprache",
"Logged in as:": "Angemeldet als:",
"matrix-react-sdk version:": "Version von matrix-react-sdk:",
"New passwords don't match": "Die neuen Passwörter stimmen nicht überein",
"olm version:": "Version von olm:",
"Passwords can't be empty": "Passwortfelder dürfen nicht leer sein",
"Registration required": "Registrierung erforderlich",
"Report it": "Melde ihn",
"riot-web version:": "Version von riot-web:",
"Scroll to bottom of page": "Zum Ende der Seite springen",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Zeitstempel im 12-Stunden-Format anzeigen (z. B. 2:30pm)",
"to tag as %(tagName)s": "um als \"%(tagName)s\" zu markieren",
"Email address": "E-Mail-Adresse",
"Error decrypting attachment": "Fehler beim Entschlüsseln des Anhangs",
"Mute": "Stummschalten",
"Operation failed": "Aktion fehlgeschlagen",
"You need to enter a user name.": "Du musst einen Benutzernamen eingeben.",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Eine Änderung des Passworts setzt derzeit alle Schlüssel für die E2E-Verschlüsselung auf allen verwendeten Geräten zurück. Bereits verschlüsselte Chat-Inhalte sind somit nur noch lesbar, wenn du zunächst alle Schlüssel exportierst und später wieder importierst. Wir arbeiten an einer Verbesserung dieser momentan noch notwendigen Vorgehensweise.",
"Unmute": "Stummschalten aufheben",
"Invalid file%(extra)s": "Ungültige Datei%(extra)s",
"Remove %(threePid)s?": "%(threePid)s entfernen?",
"Please select the destination room for this message": "Bitte den Raum auswählen, an den diese Nachricht gesendet werden soll",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s hat den Raum-Namen gelöscht.",
"Passphrases must match": "Passphrase muss übereinstimmen",
"Passphrase must not be empty": "Passphrase darf nicht leer sein",
"Export room keys": "Raum-Schlüssel exportieren",
"Enter passphrase": "Passphrase eingeben",
"Confirm passphrase": "Bestätige Passphrase",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Die Export-Datei wird mit einer Passphrase geschützt sein. Du solltest die Passphrase hier eingeben um die Datei zu entschlüsseln.",
"You must join the room to see its files": "Du musst dem Raum beitreten, um die Raum-Dateien sehen zu können",
"Reject all %(invitedRooms)s invites": "Alle %(invitedRooms)s Einladungen ablehnen",
"Start new Chat": "Starte neuen Chat",
"Guest users can't invite users. Please register.": "Gäste können keine Nutzer einladen. Bitte registrieren.",
"Failed to invite": "Einladen fehlgeschlagen",
"Failed to invite user": "Einladen des Nutzers fehlgeschlagen",
"Confirm Removal": "Entfernen bestätigen",
"Unknown error": "Unbekannter Fehler",
"Incorrect password": "Inkorrektes Passwort",
"This action is irreversible.": "Diese Aktion kann nicht rückgängig gemacht werden.",
"To continue, please enter your password.": "Zum fortfahren bitte Passwort eingeben.",
"Device name": "Geräte-Name",
"Device key": "Geräte-Schlüssel",
"In future this verification process will be more sophisticated.": "Zukünftig wird dieser Verifizierungsprozess technisch ausgereifter und eleganter gestaltet werden.",
"Verify device": "Gerät verifizieren",
"I verify that the keys match": "Ich bestätige, dass die Schlüssel passen",
"Unable to restore session": "Sitzungswiederherstellung fehlgeschlagen",
"Continue anyway": "Fahre trotzdem fort",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Dein Anzeigename ist der Name, der anderen Nutzern angezeigt wird, wenn du in Räumen sprichst. Welchen Anzeigenamen möchtest du wählen?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Do blockst aktuell unverifizierte Geräte. Um Nachrichten an diese Geräte zu senden musst du sie verifizieren.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" enthält Geräte, die du bislang noch nicht gesehen hast.",
"Unknown devices": "Unbekannte Geräte",
"Unknown Address": "Unbekannte Adresse",
"Verify...": "Verifizieren...",
"ex. @bob:example.com": "z. B. @bob:example.com",
"Add User": "Nutzer hinzufügen",
"Sign in with CAS": "Mit CAS anmelden",
"Custom Server Options": "Erweiterte Server-Optionen",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Du kannst die erweiterten Server-Optionen nutzen, um dich an anderen Matrix-Servern anzumelden, indem du eine andere Heimserver-URL angibst.",
"This allows you to use this app with an existing Matrix account on a different home server.": "Dies erlaubt dir diese App mit einem existierenden Matrix-Konto auf einem anderen Heimserver zu verwenden.",
"Dismiss": "Ablehnen",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Du kannst auch einen angepassten Idantitätsserver angeben aber dies wird typischerweise Interaktionen mit anderen Nutzern auf Basis der E-Mail-Adresse verhindern.",
"Please check your email to continue registration.": "Bitte prüfe deine E-Mail um mit der Registrierung fortzufahren.",
"Token incorrect": "Token inkorrekt",
"A text message has been sent to": "Eine Textnachricht wurde gesendet an",
"Please enter the code it contains:": "Bitte gebe den Code ein, den sie enthält:",
"powered by Matrix": "betrieben mit Matrix",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Wenn du keine E-Mail-Adresse angibst, wirst du nicht in der Lage sein, dein Passwort zurückzusetzen. Bist du sicher?",
"You are registering with %(SelectedTeamName)s": "Du registrierst dich mit %(SelectedTeamName)s",
"Default server": "Standard-Server",
"Custom server": "Angepasster Server",
"Home server URL": "Heimserver-URL",
"Identity server URL": "Identitätsserver-URL",
"What does this mean?": "Was bedeutet das?",
"Error decrypting audio": "Audio-Entschlüsselung fehlgeschlagen",
"Error decrypting image": "Bild-Entschlüsselung fehlgeschlagen",
"Image '%(Body)s' cannot be displayed.": "Das Bild '%(Body)s' kann nicht angezeigt werden.",
"This image cannot be displayed.": "Dieses Bild kann nicht angezeigt werden.",
"Error decrypting video": "Video-Entschlüsselung fehlgeschlagen",
"Import room keys": "Importiere Raum-Schlüssel",
"File to import": "Zu importierende Datei",
"Failed to invite the following users to the %(roomName)s room:": "Das Einladen der folgenden Nutzer in den Raum \"%(roomName)s\" ist fehlgeschlagen:",
"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.": "Bist du sicher, dass du dieses Ereignis entfernen (löschen) möchtest? Wenn du die Änderung eines Raum-Namens oder eines Raum-Themas löscht, kann dies dazu führen, dass die ursprüngliche Änderung rückgängig gemacht wird.",
"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.": "Dieser Prozess erlaubt es dir, die Schlüssel für in verschlüsselten Räumen empfangene Nachrichten in eine lokale Datei zu exportieren. In Zukunft wird es möglich sein, diese Datei in einen anderen Matrix-Client zu importieren, sodass dieser Client ebenfalls diese Nachrichten entschlüsseln kann.",
"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.": "Die exportierte Datei erlaubt jedem, der diese lesen kann, jede verschlüsselte Nachricht zu entschlüsseln die du sehen kannst. Du solltest sie also sicher verwahren. Um dabei zu helfen, solltest du unten eine Passphrase eingeben, die dazu verwendet wird, die exportierten Daten zu verschlüsseln. Anschließend ist es nur möglich die Daten zu lesen, wenn dieselbe Passphrase verwendet wird.",
"Analytics": "Anonymisierte Analysedaten",
"Opt out of analytics": "Zustimmung zur Übermittlung von anonymisierten Analysedaten verweigern",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot sammelt anonymisierte Analysedaten, um die Anwendung kontinuierlich verbessern zu können.",
"Add an Integration": "Eine Integration hinzufügen",
"Removed or unknown message type": "Gelöschte oder unbekannter Nachrichten-Typ",
"Disable URL previews by default for participants in this room": "URL-Vorschau für Teilnehmer dieses Raumes standardmäßig deaktivieren",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "URL-Vorschau ist standardmäßig %(globalDisableUrlPreview)s für Teilnehmer dieses Raumes.",
"URL Previews": "URL-Vorschau",
"Enable URL previews for this room (affects only you)": "Aktiviere die URL-Vorschau in diesem Raum (betrifft nur dich)",
"Offline": "Offline",
"Online": "Online",
" (unsupported)": " (nicht unterstützt)",
"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.": "Diese Vorgehensweise erlaubt es dir, die zuvor von einem anderen Matrix-Client exportierten Verschlüsselungs-Schlüssel zu importieren. Danach kannst du alle Nachrichten entschlüsseln, die auch bereits auf dem anderen Client entschlüsselt werden konnten.",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Dies wird dein Konto permanent unbenutzbar machen. Du wirst dich nicht mit derselben Nutzer-ID erneut registrieren können.",
"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:": "Um sicherzustellen, dass diesem Gerät vertraut werden kann, kontaktiere bitte den Eigentümer des Geräts über ein anderes Kommunikationsmittel (z.B. im persönlichen Gespräch oder durch einen Telefon-Anruf) und vergewissere dich, dass der Schlüssel, den der Eigentümer in den Nutzer-Einstellungen für dieses Gerät sieht, mit dem folgenden Schlüssel identisch ist:",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Wenn er passt, betätige den Bestätigen-Button unten. Wenn nicht, fängt jemand anderes dieses Gerät ab und du möchtest wahrscheinlich lieber den Blacklist-Button betätigen.",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Bei der Wiederherstellung deiner vorherigen Sitzung ist ein Fehler aufgetreten. Um fortzufahren, musst du dich erneut anmelden. Eine zuvor verschlüsselte Chat-Historie wird in der Folge nicht mehr lesbar sein.",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Wenn du zuvor eine aktuellere Version von Riot verwendet hast, ist deine Sitzung eventuell inkompatibel mit dieser Version. Bitte schließe dieses Fenster und kehre zur aktuelleren Version zurück.",
"Blacklist": "Blockieren",
"Unblacklist": "Entblockieren",
"Unverify": "Entverifizieren",
"This Home Server would like to make sure you are not a robot": "Dieser Heimserver möchte sicherstellen, dass du kein Roboter bist",
"Drop file here to upload": "Datei hier loslassen zum hochladen",
"Idle": "Untätig",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Wir empfehlen dir für jedes Gerät durch den Verifizierungsprozess zu gehen um zu bestätigen, dass sie ihrem legitimierten Besitzer gehören, aber du kannst die Nachrichten ohne Verifizierung erneut senden, wenn du es vorziehst.",
"Ongoing conference call%(supportedText)s. %(joinText)s": "Laufendes Konferenzgespräch%(supportedText)s. %(joinText)s",
"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?": "Du wirst jetzt auf die Website eines Drittanbieters weitergeleitet, damit du dein Konto für die Verwendung von %(integrationsUrl)s authentifizieren kannst. Möchtest du fortfahren?",
"Disable URL previews for this room (affects only you)": "URL-Vorschau für diesen Raum deaktivieren (betrifft nur dich)",
"Start automatically after system login": "Starte automatisch nach System-Login",
"Desktop specific": "Desktopspezifisch",
"Jump to first unread message.": "Zur ersten ungelesenen Nachricht springen.",
"Options": "Optionen",
"disabled": "deaktiviert",
"enabled": "aktiviert",
"Invited": "Eingeladen",
"Set a Display Name": "Setze einen Anzeigenamen",
"for %(amount)ss": "für %(amount)ss",
"for %(amount)sm": "seit %(amount)smin",
"for %(amount)sh": "für %(amount)sh",
"for %(amount)sd": "für %(amount)sd",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s hat das Raum-Bild entfernt.",
"VoIP": "VoIP",
"No Webcams detected": "Keine Webcam erkannt",
"Missing Media Permissions, click here to request.": "Fehlende Medienberechtigungen. Hier klicken, um Berechtigungen zu beantragen.",
"No Microphones detected": "Keine Mikrofone erkannt",
"No media permissions": "Keine Medienberechtigungen",
"You may need to manually permit Riot to access your microphone/webcam": "Gegebenenfalls kann es notwendig sein, dass du Riot manuell den Zugriff auf dein Mikrofon bzw. deine Webcam gewähren musst",
"Default Device": "Standard-Gerät",
"Microphone": "Mikrofon",
"Camera": "Kamera",
"Device already verified!": "Gerät bereits verifiziert!",
"Export": "Export",
"Failed to register as guest:": "Registrieren als Gast schlug fehl:",
"Guest access is disabled on this Home Server.": "Gastzugang ist auf diesem Heimserver deaktivert.",
"Import": "Import",
"Incorrect username and/or password.": "Inkorrekter Nutzername und/oder Passwort.",
"Results from DuckDuckGo": "Ergebnisse von DuckDuckGo",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Den Signaturschlüssel den du bereitstellst stimmt mit dem Schlüssel den du von %(userId)s's Gerät %(deviceId)s empfangen hast überein. Gerät als verifiziert markiert.",
"Add a topic": "Thema hinzufügen",
"Anyone": "Jeder",
"Are you sure you want to leave the room '%(roomName)s'?": "Bist du sicher, dass du den Raum '%(roomName)s' verlassen willst?",
"Custom level": "Benutzerdefiniertes Berechtigungslevel",
"(default: %(userName)s)": "(Standard: %(userName)s)",
"Device ID:": "Geräte-ID:",
"device id: ": "Geräte-ID: ",
"Device key:": "Geräte-Schlüssel:",
"Email address (optional)": "E-Mail-Adresse (optional)",
"List this room in %(domain)s's room directory?": "Diesen Raum zum Raum-Verzeichnis von %(domain)s hinzufügen?",
"Mobile phone number (optional)": "Mobilfunknummer (optional)",
"Password:": "Passwort:",
"Register": "Registrieren",
"Save": "Speichern",
"Setting a user name will create a fresh account": "Die Eingabe eines Benutzernamens wird ein neues Konto erzeugen",
"Tagged as: ": "Getaggt als: ",
"This Home Server does not support login using email address.": "Dieser Heimserver unterstützt den Login mittels E-Mail-Adresse nicht.",
"There was a problem logging in.": "Es gab ein Problem beim anmelden.",
"Unknown (user, device) pair:": "Unbekanntes (Nutzer-/Gerät-)Paar:",
"Remote addresses for this room:": "Entfernte Raum-Adressen für diesen Raum:",
"Unrecognised command:": "Unbekannter Befehl:",
"Unrecognised room alias:": "Unbekannter Raum-Alias:",
"Use compact timeline layout": "Nutze kompaktes Zeitstrahl-Layout",
"Verified key": "Verifizierter Schlüssel",
"WARNING: Device already verified, but keys do NOT MATCH!": "WARNUNG: Gerät bereits verifiziert, aber Schlüssel sind NICHT GLEICH!",
"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!": "WARNUNG: SCHLÜSSEL-VERIFIZIERUNG FEHLGESCHLAGEN! Der Signatur-Schlüssel für %(userId)s und Gerät %(deviceId)s ist \"%(fprint)s\" welche nicht dem bereitgestellten Schlüssel \"%(fingerprint)s\" übereinstimmen. Dies kann bedeuten, dass deine Kommunikation abgefangen wird!",
"You have <a>disabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>deaktiviert</a>.",
"You have <a>enabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>aktiviert</a>.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Du hast einen ungültigen Kontakt eingegeben. Versuche es mit der Matrix-ID oder der E-Mail-Adresse.",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName hat das Raum-Bild geändert zu <img/>",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s hat das Raum-Bild für %(roomName)s geändert",
"Hide removed messages": "Gelöschte Nachrichten verbergen",
"Start new chat": "Neuen Chat starten",
"Disable markdown formatting": "Deaktiviere Markdown-Formatierung",
"Add": "Hinzufügen",
"%(count)s new messages.one": "%(count)s neue Nachricht",
"%(count)s new messages.other": "%(count)s neue Nachrichten",
"Error: Problem communicating with the given homeserver.": "Fehler: Problem beim kommunizieren mit dem angegebenen Heimserver.",
"Failed to fetch avatar URL": "Fehler beim holen der Avatar-URL",
"The phone number entered looks invalid": "Die Telefonnummer, die eingegeben wurde, sieht ungültig aus",
"This room is private or inaccessible to guests. You may be able to join if you register.": "Dieser Raum ist privat oder für Gäste nicht betretbar. Du kannst evtl. beitreten wenn du dich registrierst.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Es wurde versucht einen spezifischen Punkt in der Chat-Historie zu laden, aber du hast keine Berechtigung diese Nachricht zu sehen.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Es wurde versucht einen spezifischen Punkt in der Chat-Historie zu laden, aber er konnte nicht gefunden werden.",
"Uploading %(filename)s and %(count)s others.zero": "%(filename)s wird hochgeladen",
"Uploading %(filename)s and %(count)s others.one": "%(filename)s und %(count)s weitere Dateien werden hochgeladen",
"Uploading %(filename)s and %(count)s others.other": "%(filename)s und %(count)s weitere Dateien werden hochgeladen",
"You must <a>register</a> to use this functionality": "Du musst dich <a>registrieren</a> um diese Funktionalität zu nutzen",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Sende erneut</a> oder <a>breche alles ab</a>. Du kannst auch auch individuelle Nachrichten erneut senden or abbrechen.",
"Create new room": "Neuen Raum erstellen",
"Welcome page": "Willkommensseite",
"Room directory": "Raum-Verzeichnis",
"Start chat": "Starte Chat",
"New Password": "Neues Passwort",
"Start chatting": "Starte plaudern",
"Start Chatting": "Starte Gespräche",
"Click on the button below to start chatting!": "Klicke den Button unten um das Plaudern zu beginnen!",
"Create a new chat or reuse an existing one": "Erstelle einen neuen Chat oder nutze einen existierenden",
"You already have existing direct chats with this user:": "Du hast bereits direkte Chats mit diesem Nutzer:",
"Username available": "Nutzername verfügbar",
"Username not available": "Nutzername nicht verfügbar",
"Something went wrong!": "Etwas ging schief!",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "Dies wird dein Konto-Name auf dem <span></span> Heimserver, oder du kannst einen <a>anderen Server</a> auswählen.",
"If you already have a Matrix account you can <a>log in</a> instead.": "Wenn du bereits ein Matrix-Benutzerkonto hast, kannst du dich stattdessen auch direkt <a>anmelden</a>.",
"Home": "Start",
"Username invalid: %(errMessage)s": "Nutzername falsch: %(errMessage)s"
}

386
src/i18n/strings/el.json Normal file
View File

@@ -0,0 +1,386 @@
{
"af": "Αφρικάνικα",
"Error": "Σφάλμα",
"Failed to forget room %(errCode)s": "Δεν ήταν δυνατή η διαγραφή του δωματίου",
"Failed to join the room": "Δεν ήταν δυνατή η σύνδεση στο δωμάτιο",
"Mute": "Σίγαση",
"Notifications": "Ειδοποιήσεις",
"Operation failed": "Η λειτουργία απέτυχε",
"Please Register": "Παρακαλώ εγγραφείτε",
"Remove": "Αφαίρεσε",
"Search": "Αναζήτηση",
"Settings": "Ρυθμίσεις",
"unknown error code": "άγνωστος κωδικός σφάλματος",
"Sunday": "Κυριακή",
"ar-ae": "Αραβικά (Η.Α.Ε)",
"ar-bh": "Αραβικά (Μπαχρέιν)",
"ar-dz": "Αραβικά (Αλγερία)",
"ar-eg": "Αραβικά (Αίγυπτος)",
"ar-iq": "Αραβικά (Ιράκ)",
"ar-jo": "Αραβικά (Ιορδανία)",
"ar-kw": "Αραβικά (Κουβέιτ)",
"ar-lb": "Αραβικά (Λίβανος)",
"ar-ly": "Αραβικά (Λιβύη)",
"ar-ma": "Αραβικά (Μαρόκο)",
"ar-om": "Αραβικά (Ομάν)",
"ar-qa": "Αραβικά (Κατάρ)",
"ar-sa": "Αραβικά (Σαουδική Αραβία)",
"ar-sy": "Αραβικά (Συρία)",
"ar-tn": "Αραβικά (Τυνισία)",
"ar-ye": "Αραβικά (Υεμένη)",
"accept": "αποδοχή",
"%(targetName)s accepted an invitation.": "%(targetName)s δέχτηκε την πρόσκληση.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s δέχτηκες την πρόσκληση για %(displayName)s.",
"Account": "Λογαριασμός",
"Add a topic": "Πρόσθεσε μια περιγραφή",
"Add email address": "Πρόσθεσε ένα email",
"Add phone number": "Πρόσθεσε έναν αριθμό τηλεφώνου",
"Admin": "Διαχειριστής",
"VoIP": "VoIP",
"No Microphones detected": "Δεν εντοπίστηκε μικρόφωνο",
"No Webcams detected": "Δεν εντοπίστηκε κάμερα",
"Default Device": "Προεπιλεγμένη Συσκευή",
"Microphone": "Μικρόφωνο",
"Camera": "Κάμερα",
"Advanced": "Προχωρημένα",
"Algorithm": "Αλγόριθμος",
"Hide removed messages": "Κρύψε διαγραμμένα μηνύματα",
"Authentication": "Πιστοποίηση",
"and": "και",
"An email has been sent to": "Ένα email στάλθηκε σε",
"A new password must be entered.": "Ο νέος κωδικός πρέπει να εισαχθεί.",
"%(senderName)s answered the call.": "Ο χρήστης %(senderName)s απάντησε.",
"An error has occurred.": "Ένα σφάλμα προέκυψε",
"Anyone": "Oποιοσδήποτε",
"Are you sure?": "Είσαι σίγουρος;",
"Are you sure you want to leave the room '%(roomName)s'?": "Είσαι σίγουρος οτι θές να φύγεις από το δωμάτιο '%(roomName)s';",
"Are you sure you want to reject the invitation?": "Είσαι σίγουρος οτι θες να απορρίψεις την πρόσκληση;",
"Are you sure you want to upload the following files?": "Είσαι σίγουρος οτι θές να ανεβάσεις τα ακόλουθα αρχεία;",
"Attachment": "Επισύναψη",
"%(senderName)s banned %(targetName)s.": "Ο χρήστης %(senderName)s έδιωξε τον χρήστη %(targetName)s.",
"Autoplay GIFs and videos": "Αυτόματη αναπαραγωγή GIFs και βίντεο",
"Bug Report": "Αναφορά Σφάλματος",
"anyone": "οποιοσδήποτε",
"Anyone who knows the room's link, apart from guests": "Oποιοσδήποτε",
"all room members, from the point they joined": "όλα τα μέλη του δωματίου, από τη στιγμή που συνδέθηκαν",
"%(items)s and %(lastItem)s": "%(items)s %(lastItem)s",
"be": "Λευκορώσικα",
"bg": "Βουλγάρικα",
"ca": "Καταλανικά",
"cs": "Τσέχικα",
"da": "Δανέζικα",
"de-at": "Γερμανικά (Αυστρία)",
"de-ch": "Γερμανικά (Ελβετία)",
"de": "Γερμανικά",
"de-li": "Γερμανικά (Λιχτενστάιν)",
"de-lu": "Γερμανικά (Λουξεμβούργο)",
"el": "Ελληνικά",
"en-au": "Αγγλικά (Αυστραλία)",
"en-bz": "Αγγλικά (Μπελίζε)",
"en-ca": "Αγγλικά (Καναδάς)",
"en": "Αγγλικά",
"en-gb": "Αγγλικά (Ηνωμένο Βασίλειο)",
"en-ie": "Αγγλικά (Ιρλανδία)",
"en-jm": "Αγγλικά (Τζαμάικα)",
"en-nz": "Αγγλικά (Νέα Ζηλανδία)",
"en-tt": "Αγγλικά (Τρινιντάντ)",
"en-us": "Αγγλικά (Ηνωμένες Πολιτείες)",
"en-za": "Αγγλικά (Νότια Αφρική)",
"es-ar": "Ισπανικά (Αργεντινή)",
"es-bo": "Ισπανικά (Βολιβία)",
"es-cl": "Ισπανικά (Χιλή)",
"es-co": "Ισπανικά (Κολομβία)",
"es-cr": "Ισπανικά (Κόστα Ρίκα)",
"es-do": "Ισπανικά (Δομινικανή Δημοκρατία)",
"es-ec": "Ισπανικά (Ισημερινός)",
"es-gt": "Ισπανικά (Γουατεμάλα)",
"es-hn": "Ισπανικά (Ονδούρα)",
"es-mx": "Ισπανικά (Μεξικό)",
"es-ni": "Ισπανικά (Νικαράγουα)",
"es-pa": "Ισπανικά (Παναμάς)",
"es-pe": "Ισπανικά (Περού)",
"es-pr": "Ισπανικά (Πουέρτο Ρίκο)",
"es-py": "Ισπανικά (Παραγουάη)",
"es": "Ισπανικά (Ισπανία)",
"es-sv": "Ισπανικά (Ελ Σαλβαδόρ)",
"es-uy": "Ισπανικά (Ουρουγουάη)",
"es-ve": "Ισπανικά (Βενεζουέλα)",
"et": "Εσθονικά",
"eu": "Βασκική (βασκική)",
"fa": "Φάρσι",
"fi": "Φινλανδικά",
"fo": "Φαρόε",
"fr-be": "Γαλλικά (Βέλγιο)",
"fr-ca": "Γαλλικά (Καναδάς)",
"fr-ch": "Γαλλικά (Ελβετία)",
"fr": "Γαλλικά",
"fr-lu": "Γαλλικά (Λουξεμβούργο)",
"ga": "Ιρλανδικά",
"gd": "Γαελική (Σκωτία)",
"he": "Εβραϊκά",
"hi": "Χίντι",
"hr": "Κροατικά",
"hu": "Ουγγρικά",
"is": "Ισλανδικά",
"it-ch": "Ιταλικά (Ελβετία)",
"it": "Ιταλικά",
"ja": "Ιαπωνικά",
"ji": "Γίντις",
"ko": "Κορεάτικα",
"lt": "Λιθουανικά",
"mk": "Μακεδονική (ΠΓΔΜ)",
"ms": "Μαλαισίας",
"mt": "Μαλτέζικα",
"nl-be": "Ολλανδικά (Βέλγιο)",
"nl": "Ολλανδικά",
"no": "Νορβηγικά",
"pl": "Πολωνέζικα",
"pt-br": "Πορτογαλικά Βραζιλίας",
"pt": "Πορτογαλικά",
"rm": "Ραιτορωμαϊκά",
"ro-mo": "Ρουμάνικα (Δημοκρατία της Μολδαβίας)",
"ro": "Ρουμάνικα",
"ru-mo": "Ρώσικα (Δημοκρατία της Μολδαβίας)",
"ru": "Ρώσικα",
"sb": "Σορβικά",
"sk": "Σλοβάκικα",
"sl": "Σλοβενικά",
"sq": "Αλβανικά",
"sr": "Σερβικά",
"sv-fi": "Σουηδικά (Φινλανδία)",
"sv": "Σουηδικά",
"sx": "Σούτου",
"sz": "Σάμη (Λαπωνικά)",
"th": "Ταϊλανδέζικα",
"tn": "Τσουάνα",
"tr": "Τουρκικά",
"ts": "Τσονγκά",
"uk": "Ουκρανικά",
"ur": "Ουρντού",
"ve": "Venda",
"vi": "Βιετναμέζικα",
"xh": "Xhosa",
"zh-cn": "Κινέζικα (ΛΔΚ)",
"zh-hk": "Κινέζικα (ΕΔΠ Χονγκ Κονγκ)",
"zh-sg": "Κινέζικα (Σιγκαπούρη)",
"zh-tw": "Κινέζικα (Ταϊβάν)",
"zu": "Ζουλού",
"id": "Ινδονησιακά",
"lv": "Λετονικά",
"A registered account is required for this action": "Ένας εγγεγραμμένος λογαριασμός απαιτείται για αυτή την ενέργεια",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Ένα μήνυμα στάλθηκε στο +%(msisdn)s. Παρακαλώ γράψε τον κωδικό επαλήθευσης που περιέχει",
"Access Token:": "Κωδικός Πρόσβασης:",
"Always show message timestamps": "Δείχνε πάντα ένδειξη ώρας στα μηνύματα",
"all room members": "όλα τα μέλη του δωματίου",
"all room members, from the point they are invited": "όλα τα μέλη του δωματίου, από τη στιγμή που προσκλήθηκαν",
"an address": "μία διεύθηνση",
"%(items)s and %(remaining)s others": "%(items)s και %(remaining)s ακόμα",
"%(items)s and one other": "%(items)s και ένας ακόμα",
"and %(overflowCount)s others...": "και %(overflowCount)s άλλοι...",
"and one other...": "και ένας ακόμα...",
"%(names)s and %(lastPerson)s are typing": "%(names)s και %(lastPerson)s γράφουν",
"%(names)s and one other are typing": "%(names)s και ένας ακόμα γράφουν",
"%(names)s and %(count)s others are typing": "%(names)s και %(count)s άλλοι γράφουν",
"Anyone who knows the room's link, including guests": "Οποιοσδήποτε γνωρίζει τον σύνδεδμο του δωματίου, συμπεριλαμβάνωντας τους επισκέπτες",
"Blacklisted": "Στη μαύρη λίστα",
"Can't load user settings": "Δεν είναι δυνατή η φόρτωση των ρυθμίσεων χρήστη",
"Change Password": "Αλλαγή Κωδικού",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "ο χρήστης %(senderName)s άλλαξε το όνομά του από %(oldDisplayName)s σε %(displayName)s.",
"%(senderName)s changed their profile picture.": "ο χρήστης %(senderName)s άλλαξε τη φωτογραφία του προφίλ του.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "Ο χρήστης %(senderDisplayName)s άλλαξε το όνομα του δωματίου σε %(roomName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "Ο χρήστης %(senderDisplayName)s άλλαξε το θέμα σε \"%(topic)s\".",
"Clear Cache and Reload": "Καθάρισε την μνήμη και Ανανέωσε",
"Clear Cache": "Καθάρισε την μνήμη",
"Bans user with given id": "Διώχνει τον χρήστη με το συγκεκριμένο id",
"%(senderDisplayName)s removed the room name.": "Ο χρήστης %(senderDisplayName)s διέγραψε το όνομα του δωματίου.",
"Changes your display nickname": "Αλλάζει το όνομα χρήστη",
"Click here": "Κάνε κλικ εδώ",
"Drop here %(toAction)s": "Σύρε εδώ %(toAction)s",
"Conference call failed.": "Η κλήση συνδιάσκεψης απέτυχε.",
"powered by Matrix": "βασισμένο στο πρωτόκολλο Matrix",
"Confirm password": "Επιβεβαίωση κωδικού",
"Confirm your new password": "Επιβεβαίωση του νέου κωδικού",
"Continue": "Συνέχεια",
"Create an account": "Δημιουργία λογαριασμού",
"Create Room": "Δημιουργία Δωματίου",
"Cryptography": "Κρυπτογραφία",
"Current password": "Τωρινός κωδικός",
"Curve25519 identity key": "Κλειδί ταυτότητας Curve25519",
"Custom level": "Προσαρμοσμένο επίπεδο",
"/ddg is not a command": "/ddg δεν αναγνωρίζεται ως εντολή",
"Deactivate Account": "Απενεργοποίηση Λογαριασμού",
"Deactivate my account": "Απενεργοποίηση του λογαριασμού μου",
"decline": "απόρριψη",
"Decrypt %(text)s": "Αποκρυπτογράφησε %(text)s",
"Decryption error": "Σφάλμα αποκρυπτογράφησης",
"(default: %(userName)s)": "(προεπιλογή: %(userName)s)",
"Delete": "Διαγραφή",
"Default": "Προεπιλογή",
"Device already verified!": "Η συσκευή έχει ήδη επαληθευτεί!",
"Device ID": "ID Συσκευής",
"Device ID:": "ID Συσκευής:",
"device id: ": "id συσκευής: ",
"Device key:": "Κλειδί Συσκευής:",
"Devices": "Συσκευές",
"Direct Chat": "Απευθείας Συνομιλία",
"Direct chats": "Απευθείας συνομιλίες",
"disabled": "ανενεργό",
"Disinvite": "Ανακάλεσε πρόσκληση",
"Display name": "Όνομα χρήστη",
"Download %(text)s": "Κατέβασε %(text)s",
"Ed25519 fingerprint": "Αποτύπωμα Ed25519",
"Email": "Ηλ. Αλληλογραφία",
"Email address": "Διεύθυνση email",
"Email address (optional)": "Διεύθυνση email (προαιρετικό)",
"Email, name or matrix ID": "Email, όνομα ή matrix ID",
"Emoji": "Εικονίδια",
"enabled": "ενεργό",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Τα κρυπτογραφημένα μηνύματα δεν θα είναι ορατά σε εφαρμογές που δεν παρέχουν τη δυνατότητα κρυπτογράφησης",
"Encrypted room": "Κρυπτογραφημένο δωμάτιο",
"%(senderName)s ended the call.": "%(senderName)s τερμάτισε την κλήση.",
"End-to-end encryption information": "Πληροφορίες σχετικά με τη κρυπτογράφηση από άκρο σε άκρο (End-to-end encryption)",
"Error decrypting attachment": "Σφάλμα κατά την αποκρυπτογράφηση της επισύναψης",
"Event information": "Πληροφορίες μηνύματος",
"Existing Call": "Υπάρχουσα Κλήση",
"Export": "Εξαγωγή",
"Export E2E room keys": "Εξαγωγή κλειδιών κρυπτογραφίας για το δωμάτιο",
"Failed to change password. Is your password correct?": "Δεν ήταν δυνατή η αλλαγή του κωδικού. Είναι ο κωδικός σωστός;",
"Failed to delete device": "Δεν ήταν δυνατή η διαγραφή της συσκευής",
"Failed to join room": "Δεν ήταν δυνατή η σύνδεση στο δωμάτιο",
"Failed to leave room": "Δεν ήταν δυνατή η αποχώρηση από το δωμάτιο",
"Failed to mute user": "Δεν ήταν δυνατή η σίγαση του χρήστη",
"Failed to register as guest:": "Δεν ήταν δυνατή η εγγραφή ως επισκέπτης:",
"Failed to reject invite": "Δεν ήταν δυνατή η απόρριψη της πρόσκλησης",
"Failed to reject invitation": "Δεν ήταν δυνατή η απόρριψη της πρόσκλησης",
"Failed to save settings": "Δεν ήταν δυνατή η αποθήκευση των ρυθμίσεων",
"Failed to send email": "Δεν ήταν δυνατή η απστολή email",
"Failed to verify email address: make sure you clicked the link in the email": "Δεν ήταν δυνατή η επαλήθευση του email: βεβαιωθείτε οτι κάνατε κλικ στον σύνδεσμο που σας στάλθηκε",
"Favourite": "Αγαπημένο",
"favourite": "αγαπημένο",
"Favourites": "Αγαπημένα",
"Fill screen": "Γέμισε την οθόνη",
"Filter room members": "Φίλτραρε τα μέλη",
"Forget room": "Διέγραψε το δωμάτιο",
"Forgot your password?": "Ξέχασες τον κωδικό σου;",
"For security, this session has been signed out. Please sign in again.": "Για λόγους ασφαλείας, αυτή η συνεδρία έχει τερματιστεί. Παρακαλώ συνδεθείτε ξανά.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Για λόγους ασφαλείας, τα κλειδιά κρυπτογράφησης θα διαγράφονται από τον φυλλομετρητή κατά την αποσύνδεση σας. Εάν επιθυμείτε να αποκρυπτογραφήσετε τις συνομιλίες σας στο μέλλον, εξάγετε τα κλειδιά σας και κρατήστε τα ασφαλή.",
"Found a bug?": "Βρήκατε κάποιο πρόβλημα;",
"Guest users can't upload files. Please register to upload.": "Οι επισκέπτες δεν μπορούν να ανεβάσουν αρχεία. Παρακαλώ εγγραφείτε πρώτα.",
"had": "είχε",
"Hangup": "Κλείσε",
"Historical": "Ιστορικό",
"Homeserver is": "Ο διακομιστής είναι",
"Identity Server is": "Διακομιστής Ταυτοποίησης",
"I have verified my email address": "Έχω επαληθεύσει το email μου",
"Import": "Εισαγωγή",
"Import E2E room keys": "Εισαγωγή κλειδιών κρυπτογράφησης",
"Incorrect username and/or password.": "Λανθασμένο όνομα χρήστη και/ή κωδικός.",
"Incorrect verification code": "Λανθασμένος κωδικός επαλήθευσης",
"Interface Language": "Γλώσσα Διεπαφής",
"Invalid Email Address": "Μη έγκυρο email",
"Invite new room members": "Προσκάλεσε νέα μέλη",
"Invited": "Προσκλήθηκε",
"Invites": "Προσκλήσεις",
"is a": "είναι ένα",
"%(displayName)s is typing": "ο χρήστης %(displayName)s γράφει",
"Sign in with": "Συνδέσου με",
"joined and left": "μπήκε και βγήκε",
"joined": "μπήκε",
"%(targetName)s joined the room.": "ο χρήστης %(targetName)s συνδέθηκε στο δωμάτιο.",
"Jump to first unread message.": "Πήγαινε στο πρώτο μη αναγνωσμένο μήνυμα.",
"%(senderName)s kicked %(targetName)s.": "Ο χρήστης %(senderName)s έδιωξε τον χρήστη %(targetName)s.",
"Kick": "Διώξε",
"Kicks user with given id": "Διώχνει χρήστες με το συγκεκριμένο id",
"Labs": "Πειραματικά",
"Leave room": "Φύγε από το δωμάτιο",
"left and rejoined": "έφυγε και ξανασυνδέθηκε",
"left": "έφυγε",
"%(targetName)s left the room.": "Ο χρήστης %(targetName)s έφυγε από το δωμάτιο.",
"Level": "Επίπεδο",
"List this room in %(domain)s's room directory?": "Να εμφανίζεται το δωμάτιο στο γενικό ευρετήριο του διακομιστή %(domain)s;",
"Local addresses for this room:": "Τοπική διεύθυνση για το δωμάτιο:",
"Logged in as:": "Συνδέθηκες ως:",
"Login as guest": "Συνδέσου ως επισκέπτης",
"Logout": "Αποσύνδεση",
"Low priority": "Χαμηλής προτεραιότητας",
"matrix-react-sdk version:": "έκδοση matrix-react-sdk:",
"Members only": "Μέλη μόνο",
"Message not sent due to unknown devices being present": "Το μήνυμα δεν στάλθηκε γιατί υπάρχουν άγνωστες συσκευές",
"Mobile phone number": "Αριθμός κινητού τηλεφώνου",
"Click here to fix": "Κάνε κλικ εδώ για διόρθωση",
"Command error": "Σφάλμα εντολής",
"Commands": "Εντολές",
"Conference calls are not supported in encrypted rooms": "Οι κλήσεις συνδιάσκεψης δεν είναι υποστηρίζονται σε κρυπτογραφημένα δωμάτια",
"Conference calls are not supported in this client": "Οι κλήσεις συνδιάσκεψης δεν είναι υποστηρίζονται από την εφαρμογή",
"Enable encryption": "Ενεργοποίηση κρυπτογραφίας",
"Enter Code": "Κωδικός",
"Failed to send request.": "Δεν ήταν δυνατή η αποστολή αιτήματος.",
"Failed to upload file": "Δεν ήταν δυνατό το ανέβασμα αρχείου",
"Failure to create room": "Δεν ήταν δυνατή η δημιουργία δωματίου",
"Join Room": "Συνδέσου",
"Moderator": "Συντονιστής",
"my Matrix ID": "το Matrix ID μου",
"Name": "Όνομα",
"New address (e.g. #foo:%(localDomain)s)": "Νέα διεύθυνση (e.g. #όνομα:%(localDomain)s)",
"New password": "Νέος κωδικός",
"New passwords don't match": "Οι νέοι κωδικοί είναι διαφορετικοί",
"New passwords must match each other.": "Οι νέοι κωδικόι πρέπει να ταιριάζουν.",
"none": "κανένα",
"(not supported by this browser)": "(δεν υποστηρίζεται από τον browser)",
"<not supported>": "<δεν υποστηρίζεται>",
"No more results": "Δεν υπάρχουν άλλα αποτελέσματα",
"No results": "Κανένα αποτέλεσμα",
"OK": "ΟΚ",
"olm version:": "έκδοση olm:",
"Password": "Κωδικός",
"Password:": "Κωδικός:",
"Passwords can't be empty": "",
"People": "Άτομα",
"Phone": "Τηλέφωνο",
"Register": "Εγγραφή",
"riot-web version:": "έκδοση riot-web:",
"Room Colour": "Χρώμα Δωματίου",
"Room name (optional)": "Όνομα Δωματίου (προαιρετικό)",
"Rooms": "Δωμάτια",
"Save": "Αποθήκευση",
"Search failed": "Η αναζήτηση απέτυχε",
"Send an encrypted message": "Στείλε ένα κρυπτογραφημένο μήνυμα",
"Send a message (unencrypted)": "Στείλε ένα μήνυμα (απλό)",
"sent an image": "έστειλε μια εικόνα",
"sent a video": "έστειλε ένα βίντεο",
"Server error": "Σφάλμα διακομιστή",
"Signed Out": "Αποσυνδέθηκες",
"Sign in": "Συνδέση",
"Sign out": "Αποσύνδεση",
"since they joined": "από τη στιγμή που συνδέθηκαν",
"since they were invited": "από τη στιγμή που προσκλήθηκαν",
"Someone": "Κάποιος",
"Start a chat": "Ξεκίνα μια συνομιλία",
"This email address is already in use": "Το email χρησιμοποιείται",
"This email address was not found": "Η διεύθηνση email δεν βρέθηκε",
"Success": "Επιτυχία",
"Start Chat": "Συνομιλία",
"Cancel": "Ακύρωση",
"cancel all": "ακύρωση όλων",
"or": "ή",
"Custom Server Options": "Προσαρμοσμένες ρυθμίσεις διακομιστή",
"Dismiss": "Αγνόησε",
"Monday": "Δευτέρα",
"Tuesday": "Τρίτη",
"Wednesday": "Τετάρτη",
"Thursday": "Πέμπτη",
"Friday": "Παρασκευή",
"Saturday": "Σάββατο",
"bold": "έντονα",
"italic": "πλάγια",
"underline": "υπογράμμιση",
"code": "κώδικας",
"quote": "αναφορά",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)s έφυγε %(repeats)s φορές",
"%(severalUsers)sleft": "%(severalUsers)s έφυγαν",
"%(oneUser)sleft": "%(oneUser)s έφυγε",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)s συνδέθηκαν και έφυγαν %(repeats)s φορές",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)s συνδέθηκε και έφυγε %(repeats)s φορές",
"%(severalUsers)sjoined and left": "%(severalUsers)s συνδέθηκαν και έφυγαν",
"%(oneUser)sjoined and left": "%(oneUser)s συνδέθηκε και έφυγε"
}

909
src/i18n/strings/en_EN.json Normal file
View File

@@ -0,0 +1,909 @@
{
"af":"Afrikaans",
"ar-ae":"Arabic (U.A.E.)",
"ar-bh":"Arabic (Bahrain)",
"ar-dz":"Arabic (Algeria)",
"ar-eg":"Arabic (Egypt)",
"ar-iq":"Arabic (Iraq)",
"ar-jo":"Arabic (Jordan)",
"ar-kw":"Arabic (Kuwait)",
"ar-lb":"Arabic (Lebanon)",
"ar-ly":"Arabic (Libya)",
"ar-ma":"Arabic (Morocco)",
"ar-om":"Arabic (Oman)",
"ar-qa":"Arabic (Qatar)",
"ar-sa":"Arabic (Saudi Arabia)",
"ar-sy":"Arabic (Syria)",
"ar-tn":"Arabic (Tunisia)",
"ar-ye":"Arabic (Yemen)",
"be":"Belarusian",
"bg":"Bulgarian",
"ca":"Catalan",
"cs":"Czech",
"da":"Danish",
"de-at":"German (Austria)",
"de-ch":"German (Switzerland)",
"de":"German",
"de-li":"German (Liechtenstein)",
"de-lu":"German (Luxembourg)",
"el":"Greek",
"en-au":"English (Australia)",
"en-bz":"English (Belize)",
"en-ca":"English (Canada)",
"en":"English",
"en-gb":"English (United Kingdom)",
"en-ie":"English (Ireland)",
"en-jm":"English (Jamaica)",
"en-nz":"English (New Zealand)",
"en-tt":"English (Trinidad)",
"en-us":"English (United States)",
"en-za":"English (South Africa)",
"es-ar":"Spanish (Argentina)",
"es-bo":"Spanish (Bolivia)",
"es-cl":"Spanish (Chile)",
"es-co":"Spanish (Colombia)",
"es-cr":"Spanish (Costa Rica)",
"es-do":"Spanish (Dominican Republic)",
"es-ec":"Spanish (Ecuador)",
"es-gt":"Spanish (Guatemala)",
"es-hn":"Spanish (Honduras)",
"es-mx":"Spanish (Mexico)",
"es-ni":"Spanish (Nicaragua)",
"es-pa":"Spanish (Panama)",
"es-pe":"Spanish (Peru)",
"es-pr":"Spanish (Puerto Rico)",
"es-py":"Spanish (Paraguay)",
"es":"Spanish (Spain)",
"es-sv":"Spanish (El Salvador)",
"es-uy":"Spanish (Uruguay)",
"es-ve":"Spanish (Venezuela)",
"et":"Estonian",
"eu":"Basque (Basque)",
"fa":"Farsi",
"fi":"Finnish",
"fo":"Faeroese",
"fr-be":"French (Belgium)",
"fr-ca":"French (Canada)",
"fr-ch":"French (Switzerland)",
"fr":"French",
"fr-lu":"French (Luxembourg)",
"ga":"Irish",
"gd":"Gaelic (Scotland)",
"he":"Hebrew",
"hi":"Hindi",
"hr":"Croatian",
"hu":"Hungarian",
"id":"Indonesian",
"is":"Icelandic",
"it-ch":"Italian (Switzerland)",
"it":"Italian",
"ja":"Japanese",
"ji":"Yiddish",
"ko":"Korean",
"lt":"Lithuanian",
"lv":"Latvian",
"mk":"Macedonian (FYROM)",
"ms":"Malaysian",
"mt":"Maltese",
"nl-be":"Dutch (Belgium)",
"nl":"Dutch",
"no":"Norwegian",
"pl":"Polish",
"pt-br":"Brazilian Portuguese",
"pt":"Portuguese",
"rm":"Rhaeto-Romanic",
"ro-mo":"Romanian (Republic of Moldova)",
"ro":"Romanian",
"ru-mo":"Russian (Republic of Moldova)",
"ru":"Russian",
"sb":"Sorbian",
"sk":"Slovak",
"sl":"Slovenian",
"sq":"Albanian",
"sr":"Serbian",
"sv-fi":"Swedish (Finland)",
"sv":"Swedish",
"sx":"Sutu",
"sz":"Sami (Lappish)",
"th":"Thai",
"tn":"Tswana",
"tr":"Turkish",
"ts":"Tsonga",
"uk":"Ukrainian",
"ur":"Urdu",
"ve":"Venda",
"vi":"Vietnamese",
"xh":"Xhosa",
"zh-cn":"Chinese (PRC)",
"zh-hk":"Chinese (Hong Kong SAR)",
"zh-sg":"Chinese (Singapore)",
"zh-tw":"Chinese (Taiwan)",
"zu":"Zulu",
"A registered account is required for this action": "A registered account is required for this action",
"a room": "a room",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains",
"Accept": "Accept",
"%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.",
"Account": "Account",
"Access Token:": "Access Token:",
"Active call (%(roomName)s)": "Active call (%(roomName)s)",
"Add": "Add",
"Add a topic": "Add a topic",
"Add email address": "Add email address",
"Add phone number": "Add phone number",
"Admin": "Admin",
"Admin tools": "Admin tools",
"And %(count)s more...": "And %(count)s more...",
"VoIP": "VoIP",
"Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.",
"No Microphones detected": "No Microphones detected",
"No Webcams detected": "No Webcams detected",
"No media permissions": "No media permissions",
"You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam",
"Default Device": "Default Device",
"Microphone": "Microphone",
"Camera": "Camera",
"Advanced": "Advanced",
"Algorithm": "Algorithm",
"Hide removed messages": "Hide removed messages",
"Always show message timestamps": "Always show message timestamps",
"Authentication": "Authentication",
"Alias (optional)": "Alias (optional)",
"all room members": "all room members",
"all room members, from the point they are invited": "all room members, from the point they are invited",
"all room members, from the point they joined": "all room members, from the point they joined",
"and": "and",
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
"%(items)s and one other": "%(items)s and one other",
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
"and %(overflowCount)s others...": "and %(overflowCount)s others...",
"and one other...": "and one other...",
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
"%(names)s and one other are typing": "%(names)s and one other are typing",
"%(names)s and %(count)s others are typing": "%(names)s and %(count)s others are typing",
"An email has been sent to": "An email has been sent to",
"A new password must be entered.": "A new password must be entered.",
"%(senderName)s answered the call.": "%(senderName)s answered the call.",
"anyone": "anyone",
"An error has occurred.": "An error has occurred.",
"Anyone": "Anyone",
"Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests",
"Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests",
"Are you sure?": "Are you sure?",
"Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?",
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Are you sure you want to upload the following files?": "Are you sure you want to upload the following files?",
"Attachment": "Attachment",
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
"%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.",
"Ban": "Ban",
"Banned users": "Banned users",
"Bans user with given id": "Bans user with given id",
"Blacklisted": "Blacklisted",
"Bug Report": "Bug Report",
"Bulk Options": "Bulk Options",
"Call Timeout": "Call Timeout",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
"Can't load user settings": "Can't load user settings",
"Change Password": "Change Password",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Changes to who can read history will only apply to future messages in this room",
"Changes your display nickname": "Changes your display nickname",
"changing room on a RoomView is not supported": "changing room on a RoomView is not supported",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key",
"Clear Cache and Reload": "Clear Cache and Reload",
"Clear Cache": "Clear Cache",
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
"Click here to fix": "Click here to fix",
"Click to mute audio": "Click to mute audio",
"Click to mute video": "Click to mute video",
"click to reveal": "click to reveal",
"Click to unmute video": "Click to unmute video",
"Click to unmute audio": "Click to unmute audio",
"Close": "Close",
"Command error": "Command error",
"Commands": "Commands",
"Conference call failed.": "Conference call failed.",
"Conference calling is in development and may not be reliable.": "Conference calling is in development and may not be reliable.",
"Conference calls are not supported in encrypted rooms": "Conference calls are not supported in encrypted rooms",
"Conference calls are not supported in this client": "Conference calls are not supported in this client",
"Confirm password": "Confirm password",
"Confirm your new password": "Confirm your new password",
"Continue": "Continue",
"Could not connect to the integration server": "Could not connect to the integration server",
"%(count)s new messages": {
"one": "%(count)s new message",
"other": "%(count)s new messages"
},
"Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one",
"Create an account": "Create an account",
"Create Room": "Create Room",
"Cryptography": "Cryptography",
"Current password": "Current password",
"Curve25519 identity key": "Curve25519 identity key",
"Custom": "Custom",
"Custom level": "Custom level",
"/ddg is not a command": "/ddg is not a command",
"Deactivate Account": "Deactivate Account",
"Deactivate my account": "Deactivate my account",
"Decline": "Decline",
"Decrypt %(text)s": "Decrypt %(text)s",
"Decryption error": "Decryption error",
"(default: %(userName)s)": "(default: %(userName)s)",
"Delete": "Delete",
"demote": "demote",
"Deops user with given id": "Deops user with given id",
"Default": "Default",
"Device already verified!": "Device already verified!",
"Device ID": "Device ID",
"Device ID:": "Device ID:",
"device id: ": "device id: ",
"Device key:": "Device key:",
"Devices": "Devices",
"Devices will not yet be able to decrypt history from before they joined the room": "Devices will not yet be able to decrypt history from before they joined the room",
"Direct Chat": "Direct Chat",
"Direct chats": "Direct chats",
"Disable Notifications": "Disable Notifications",
"disabled": "disabled",
"Disable inline URL previews by default": "Disable inline URL previews by default",
"Disable markdown formatting": "Disable markdown formatting",
"Disinvite": "Disinvite",
"Display name": "Display name",
"Displays action": "Displays action",
"Don't send typing notifications": "Don't send typing notifications",
"Download %(text)s": "Download %(text)s",
"Drop File Here": "Drop File Here",
"Drop here %(toAction)s": "Drop here %(toAction)s",
"Drop here to tag %(section)s": "Drop here to tag %(section)s",
"Ed25519 fingerprint": "Ed25519 fingerprint",
"Email": "Email",
"Email address": "Email address",
"Email address (optional)": "Email address (optional)",
"Email, name or matrix ID": "Email, name or matrix ID",
"Emoji": "Emoji",
"Enable encryption": "Enable encryption",
"Enable Notifications": "Enable Notifications",
"enabled": "enabled",
"Encrypted by a verified device": "Encrypted by a verified device",
"Encrypted by an unverified device": "Encrypted by an unverified device",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Encrypted messages will not be visible on clients that do not yet implement encryption",
"Encrypted room": "Encrypted room",
"Encryption is enabled in this room": "Encryption is enabled in this room",
"Encryption is not enabled in this room": "Encryption is not enabled in this room",
"%(senderName)s ended the call.": "%(senderName)s ended the call.",
"End-to-end encryption information": "End-to-end encryption information",
"End-to-end encryption is in beta and may not be reliable": "End-to-end encryption is in beta and may not be reliable",
"Enter Code": "Enter Code",
"Enter passphrase": "Enter passphrase",
"Error": "Error",
"Error decrypting attachment": "Error decrypting attachment",
"Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.",
"Event information": "Event information",
"Existing Call": "Existing Call",
"Export": "Export",
"Export E2E room keys": "Export E2E room keys",
"Failed to ban user": "Failed to ban user",
"Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?",
"Failed to change power level": "Failed to change power level",
"Failed to delete device": "Failed to delete device",
"Failed to fetch avatar URL": "Failed to fetch avatar URL",
"Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s",
"Failed to join room": "Failed to join room",
"Failed to join the room": "Failed to join the room",
"Failed to kick": "Failed to kick",
"Failed to leave room": "Failed to leave room",
"Failed to load timeline position": "Failed to load timeline position",
"Failed to lookup current room": "Failed to lookup current room",
"Failed to mute user": "Failed to mute user",
"Failed to register as guest:": "Failed to register as guest:",
"Failed to reject invite": "Failed to reject invite",
"Failed to reject invitation": "Failed to reject invitation",
"Failed to save settings": "Failed to save settings",
"Failed to send email": "Failed to send email",
"Failed to send request.": "Failed to send request.",
"Failed to set avatar.": "Failed to set avatar.",
"Failed to set display name": "Failed to set display name",
"Failed to set up conference call": "Failed to set up conference call",
"Failed to toggle moderator status": "Failed to toggle moderator status",
"Failed to unban": "Failed to unban",
"Failed to upload file": "Failed to upload file",
"Failed to upload profile picture!": "Failed to upload profile picture!",
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
"Failure to create room": "Failure to create room",
"Favourite": "Favourite",
"favourite": "favourite",
"Favourites": "Favourites",
"Fill screen": "Fill screen",
"Filter room members": "Filter room members",
"Forget room": "Forget room",
"Forgot your password?": "Forgot your password?",
"For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.",
"Found a bug?": "Found a bug?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s",
"Guest access is disabled on this Home Server.": "Guest access is disabled on this Home Server.",
"Guests can't set avatars. Please register.": "Guests can't set avatars. Please register.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Guest users can't create new rooms. Please register to create room and start a chat.",
"Guest users can't upload files. Please register to upload.": "Guest users can't upload files. Please register to upload.",
"Guests can't use labs features. Please register.": "Guests can't use labs features. Please register.",
"Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.",
"had": "had",
"Hangup": "Hangup",
"Hide read receipts": "Hide read receipts",
"Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar",
"Historical": "Historical",
"Home": "Home",
"Homeserver is": "Homeserver is",
"Identity Server is": "Identity Server is",
"I have verified my email address": "I have verified my email address",
"Import": "Import",
"Import E2E room keys": "Import E2E room keys",
"Incoming call from %(name)s": "Incoming call from %(name)s",
"Incoming video call from %(name)s": "Incoming video call from %(name)s",
"Incoming voice call from %(name)s": "Incoming voice call from %(name)s",
"Incorrect username and/or password.": "Incorrect username and/or password.",
"Incorrect verification code": "Incorrect verification code",
"Interface Language": "Interface Language",
"Invalid alias format": "Invalid alias format",
"Invalid address format": "Invalid address format",
"Invalid Email Address": "Invalid Email Address",
"Invalid file%(extra)s": "Invalid file%(extra)s",
"%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.",
"Invite new room members": "Invite new room members",
"Invited": "Invited",
"Invites": "Invites",
"Invites user with given id to current room": "Invites user with given id to current room",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
"%(displayName)s is typing": "%(displayName)s is typing",
"Sign in with": "Sign in with",
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
"Join Room": "Join Room",
"joined and left": "joined and left",
"joined": "joined",
"%(targetName)s joined the room.": "%(targetName)s joined the room.",
"Joins room with given alias": "Joins room with given alias",
"Jump to first unread message.": "Jump to first unread message.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.",
"Kick": "Kick",
"Kicks user with given id": "Kicks user with given id",
"Labs": "Labs",
"Last seen": "Last seen",
"Leave room": "Leave room",
"left and rejoined": "left and rejoined",
"left": "left",
"%(targetName)s left the room.": "%(targetName)s left the room.",
"Level:": "Level:",
"List this room in %(domain)s's room directory?": "List this room in %(domain)s's room directory?",
"Local addresses for this room:": "Local addresses for this room:",
"Logged in as:": "Logged in as:",
"Login as guest": "Login as guest",
"Logout": "Logout",
"Low priority": "Low priority",
"%(senderName)s made future room history visible to": "%(senderName)s made future room history visible to",
"Manage Integrations": "Manage Integrations",
"Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled",
"matrix-react-sdk version:": "matrix-react-sdk version:",
"Members only": "Members only",
"Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present",
"Missing room_id in request": "Missing room_id in request",
"Missing user_id in request": "Missing user_id in request",
"Mobile phone number": "Mobile phone number",
"Mobile phone number (optional)": "Mobile phone number (optional)",
"Moderator": "Moderator",
"Must be viewing a room": "Must be viewing a room",
"Mute": "Mute",
"my Matrix ID": "my Matrix ID",
"Name": "Name",
"Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device",
"Never send encrypted messages to unverified devices in this room": "Never send encrypted messages to unverified devices in this room",
"Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device",
"New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"New Composer & Autocomplete": "New Composer & Autocomplete",
"New password": "New password",
"New passwords don't match": "New passwords don't match",
"New passwords must match each other.": "New passwords must match each other.",
"none": "none",
"not set": "not set",
"not specified": "not specified",
"Notifications": "Notifications",
"(not supported by this browser)": "(not supported by this browser)",
"<not supported>": "<not supported>",
"NOT verified": "NOT verified",
"No devices with registered encryption keys": "No devices with registered encryption keys",
"No display name": "No display name",
"No more results": "No more results",
"No results": "No results",
"No users have specific privileges in this room": "No users have specific privileges in this room",
"OK": "OK",
"olm version:": "olm version:",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Once encryption is enabled for a room it cannot be turned off again (for now)",
"Once you&#39;ve followed the link it contains, click below": "Once you&#39;ve followed the link it contains, click below",
"Only people who have been invited": "Only people who have been invited",
"Operation failed": "Operation failed",
"Otherwise, <a>click here</a> to send a bug report.": "Otherwise, <a>click here</a> to send a bug report.",
"Password": "Password",
"Password:": "Password:",
"Passwords can't be empty": "Passwords can't be empty",
"People": "People",
"Permissions": "Permissions",
"Phone": "Phone",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s placed a %(callType)s call.",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
"Please Register": "Please Register",
"Power level must be positive integer.": "Power level must be positive integer.",
"Press": "Press",
"Privacy warning": "Privacy warning",
"Private Chat": "Private Chat",
"Privileged Users": "Privileged Users",
"Profile": "Profile",
"Public Chat": "Public Chat",
"Reason": "Reason",
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
"Revoke Moderator": "Revoke Moderator",
"Refer a friend to Riot:": "Refer a friend to Riot:",
"Register": "Register",
"Registration required": "Registration required",
"rejected": "rejected",
"%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.",
"Reject invitation": "Reject invitation",
"Rejoin": "Rejoin",
"Remote addresses for this room:": "Remote addresses for this room:",
"Remove Contact Information?": "Remove Contact Information?",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.",
"Remove": "Remove",
"Remove %(threePid)s?": "Remove %(threePid)s?",
"%(senderName)s requested a VoIP conference.": "%(senderName)s requested a VoIP conference.",
"Report it": "Report it",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"restore": "restore",
"Results from DuckDuckGo": "Results from DuckDuckGo",
"Return to app": "Return to app",
"Return to login screen": "Return to login screen",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings",
"Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again",
"riot-web version:": "riot-web version:",
"Room %(roomId)s not visible": "Room %(roomId)s not visible",
"Room Colour": "Room Colour",
"Room contains unknown devices": "Room contains unknown devices",
"Room name (optional)": "Room name (optional)",
"%(roomName)s does not exist.": "%(roomName)s does not exist.",
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
"Rooms": "Rooms",
"Save": "Save",
"Scroll to bottom of page": "Scroll to bottom of page",
"Scroll to unread messages": "Scroll to unread messages",
"Search": "Search",
"Search failed": "Search failed",
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
"Searching known users": "Searching known users",
"Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s",
"Send a message (unencrypted)": "Send a message (unencrypted)",
"Send an encrypted message": "Send an encrypted message",
"Send anyway": "Send anyway",
"Sender device information": "Sender device information",
"Send Invites": "Send Invites",
"Send Reset Email": "Send Reset Email",
"sent an image": "sent an image",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.",
"sent a video": "sent a video",
"Server error": "Server error",
"Server may be unavailable or overloaded": "Server may be unavailable or overloaded",
"Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(",
"Server may be unavailable, overloaded, or the file too big": "Server may be unavailable, overloaded, or the file too big",
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
"Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.",
"Session ID": "Session ID",
"%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.",
"Set": "Set",
"Setting a user name will create a fresh account": "Setting a user name will create a fresh account",
"Settings": "Settings",
"Show panel": "Show panel",
"Show Text Formatting Toolbar": "Show Text Formatting Toolbar",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)",
"Signed Out": "Signed Out",
"Sign in": "Sign in",
"Sign out": "Sign out",
"since the point in time of selecting this option": "since the point in time of selecting this option",
"since they joined": "since they joined",
"since they were invited": "since they were invited",
"Some of your messages have not been sent.": "Some of your messages have not been sent.",
"Someone": "Someone",
"Sorry, this homeserver is using a login which is not recognised ": "Sorry, this homeserver is using a login which is not recognised ",
"Start a chat": "Start a chat",
"Start authentication": "Start authentication",
"Start Chat": "Start Chat",
"Submit": "Submit",
"Success": "Success",
"tag as %(tagName)s": "tag as %(tagName)s",
"tag direct chat": "tag direct chat",
"Tagged as: ": "Tagged as: ",
"The default role for new room members is": "The default role for new room members is",
"The main address for this room is": "The main address for this room is",
"The phone number entered looks invalid": "The phone number entered looks invalid",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.",
"This action cannot be performed by a guest user. Please register to be able to do this.": "This action cannot be performed by a guest user. Please register to be able to do this.",
"This email address is already in use": "This email address is already in use",
"This email address was not found": "This email address was not found",
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "The file '%(fileName)s' exceeds this home server's size limit for uploads",
"The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload",
"The remote side failed to pick up": "The remote side failed to pick up",
"This Home Server does not support login using email address.": "This Home Server does not support login using email address.",
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
"There was a problem logging in.": "There was a problem logging in.",
"This room has no local addresses": "This room has no local addresses",
"This room is not recognised.": "This room is not recognised.",
"These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways",
"The visibility of existing history will be unchanged": "The visibility of existing history will be unchanged",
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
"This phone number is already in use": "This phone number is already in use",
"This room": "This room",
"This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers",
"This room's internal ID is": "This room's internal ID is",
"times": "times",
"To ban users": "To ban users",
"to browse the directory": "to browse the directory",
"To configure the room": "To configure the room",
"to demote": "to demote",
"to favourite": "to favourite",
"To invite users into the room": "To invite users into the room",
"To kick users": "To kick users",
"To link to a room it must have <a>an address</a>.": "To link to a room it must have <a>an address</a>.",
"to make a room or": "to make a room or",
"To remove other users' messages": "To remove other users' messages",
"To reset your password, enter the email address linked to your account": "To reset your password, enter the email address linked to your account",
"to restore": "to restore",
"To send events of type": "To send events of type",
"To send messages": "To send messages",
"to start a chat with someone": "to start a chat with someone",
"to tag as %(tagName)s": "to tag as %(tagName)s",
"to tag direct chat": "to tag direct chat",
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
"Turn Markdown off": "Turn Markdown off",
"Turn Markdown on": "Turn Markdown on",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).",
"Unable to add email address": "Unable to add email address",
"Unable to remove contact information": "Unable to remove contact information",
"Unable to restore previous session": "Unable to restore previous session",
"Unable to verify email address.": "Unable to verify email address.",
"Unban": "Unban",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.",
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
"Unable to capture screen": "Unable to capture screen",
"Unable to enable Notifications": "Unable to enable Notifications",
"Unable to load device list": "Unable to load device list",
"Undecryptable": "Undecryptable",
"Unencrypted room": "Unencrypted room",
"unencrypted": "unencrypted",
"Unencrypted message": "Unencrypted message",
"unknown caller": "unknown caller",
"Unknown command": "Unknown command",
"unknown device": "unknown device",
"unknown error code": "unknown error code",
"Unknown room %(roomId)s": "Unknown room %(roomId)s",
"Unknown (user, device) pair:": "Unknown (user, device) pair:",
"unknown": "unknown",
"Unmute": "Unmute",
"Unnamed Room": "Unnamed Room",
"Unrecognised command:": "Unrecognised command:",
"Unrecognised room alias:": "Unrecognised room alias:",
"Unverified": "Unverified",
"Uploading %(filename)s and %(count)s others": {
"zero": "Uploading %(filename)s",
"one": "Uploading %(filename)s and %(count)s other",
"other": "Uploading %(filename)s and %(count)s others"
},
"uploaded a file": "uploaded a file",
"Upload avatar": "Upload avatar",
"Upload Failed": "Upload Failed",
"Upload Files": "Upload Files",
"Upload file": "Upload file",
"Upload new:": "Upload new:",
"Usage": "Usage",
"Use compact timeline layout": "Use compact timeline layout",
"Use with caution": "Use with caution",
"User ID": "User ID",
"User Interface": "User Interface",
"%(user)s is a": "%(user)s is a",
"User name": "User name",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)",
"Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s",
"Users": "Users",
"User": "User",
"Verification Pending": "Verification Pending",
"Verification": "Verification",
"verified": "verified",
"Verified": "Verified",
"Verified key": "Verified key",
"Video call": "Video call",
"Voice call": "Voice call",
"VoIP conference finished.": "VoIP conference finished.",
"VoIP conference started.": "VoIP conference started.",
"VoIP is unsupported": "VoIP is unsupported",
"(warning: cannot be disabled again!)": "(warning: cannot be disabled again!)",
"Warning!": "Warning!",
"WARNING: Device already verified, but keys do NOT MATCH!": "WARNING: Device already verified, but keys do NOT MATCH!",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!",
"Who can access this room?": "Who can access this room?",
"Who can read history?": "Who can read history?",
"Who would you like to add to this room?": "Who would you like to add to this room?",
"Who would you like to communicate with?": "Who would you like to communicate with?",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?",
"You already have existing direct chats with this user:": "You already have existing direct chats with this user:",
"You are already in a call.": "You are already in a call.",
"You're not in any rooms yet! Press": "You're not in any rooms yet! Press",
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
"You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.",
"You do not have permission to post to this room": "You do not have permission to post to this room",
"You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.",
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
"You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "You have entered an invalid contact. Try using their Matrix ID or email address.",
"You have no visible notifications": "You have no visible notifications",
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
"you must be a": "you must be a",
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"You need to be able to invite users to do that.": "You need to be able to invite users to do that.",
"You need to be logged in.": "You need to be logged in.",
"You need to enter a user name.": "You need to enter a user name.",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
"Your password has been reset": "Your password has been reset",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them",
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
"You should not yet trust it to secure data": "You should not yet trust it to secure data",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Your home server does not support device management.": "Your home server does not support device management.",
"Sun": "Sun",
"Mon": "Mon",
"Tue": "Tue",
"Wed": "Wed",
"Thu": "Thu",
"Fri": "Fri",
"Sat": "Sat",
"Jan": "Jan",
"Feb": "Feb",
"Mar": "Mar",
"Apr": "Apr",
"May": "May",
"Jun": "Jun",
"Jul": "Jul",
"Aug": "Aug",
"Sep": "Sep",
"Oct": "Oct",
"Nov": "Nov",
"Dec": "Dec",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Set a display name:",
"Set a Display Name": "Set a Display Name",
"Upload an avatar:": "Upload an avatar:",
"This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.",
"Missing password.": "Missing password.",
"Passwords don't match.": "Passwords don't match.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Password too short (min %(MIN_PASSWORD_LENGTH)s).",
"This doesn't look like a valid email address.": "This doesn't look like a valid email address.",
"This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "User names may only contain letters, numbers, dots, hyphens and underscores.",
"An unknown error occurred.": "An unknown error occurred.",
"I already have an account": "I already have an account",
"An error occurred: %(error_string)s": "An error occurred: %(error_string)s",
"Topic": "Topic",
"Make Moderator": "Make Moderator",
"Make this room private": "Make this room private",
"Share message history with new users": "Share message history with new users",
"Encrypt room": "Encrypt room",
"There are no visible files in this room": "There are no visible files in this room",
"Room": "Room",
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
"Auto-complete": "Auto-complete",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
"(~%(count)s results)": {
"one": "(~%(count)s result)",
"other": "(~%(count)s results)"
},
"Cancel": "Cancel",
"or": "or",
"Active call": "Active call",
"Monday": "Monday",
"Tuesday": "Tuesday",
"Wednesday": "Wednesday",
"Thursday": "Thursday",
"Friday": "Friday",
"Saturday": "Saturday",
"Sunday": "Sunday",
"bold": "bold",
"italic": "italic",
"strike": "strike",
"underline": "underline",
"code":"code",
"quote":"quote",
"bullet":"bullet",
"numbullet":"numbullet",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times",
"%(severalUsers)sjoined": "%(severalUsers)sjoined",
"%(oneUser)sjoined": "%(oneUser)sjoined",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sleft %(repeats)s times",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sleft %(repeats)s times",
"%(severalUsers)sleft": "%(severalUsers)sleft",
"%(oneUser)sleft": "%(oneUser)sleft",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sjoined and left %(repeats)s times",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sjoined and left %(repeats)s times",
"%(severalUsers)sjoined and left": "%(severalUsers)sjoined and left",
"%(oneUser)sjoined and left": "%(oneUser)sjoined and left",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sleft and rejoined %(repeats)s times",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sleft and rejoined %(repeats)s times",
"%(severalUsers)sleft and rejoined": "%(severalUsers)sleft and rejoined",
"%(oneUser)sleft and rejoined": "%(oneUser)sleft and rejoined",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejected their invitations %(repeats)s times",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejected their invitation %(repeats)s times",
"%(severalUsers)srejected their invitations": "%(severalUsers)srejected their invitations",
"%(oneUser)srejected their invitation": "%(oneUser)srejected their invitation",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)shad their invitations withdrawn %(repeats)s times",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)shad their invitation withdrawn %(repeats)s times",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)shad their invitations withdrawn",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)shad their invitation withdrawn",
"were invited %(repeats)s times": "were invited %(repeats)s times",
"was invited %(repeats)s times": "was invited %(repeats)s times",
"were invited": "were invited",
"was invited": "was invited",
"were banned %(repeats)s times": "were banned %(repeats)s times",
"was banned %(repeats)s times": "was banned %(repeats)s times",
"were banned": "were banned",
"was banned": "was banned",
"were unbanned %(repeats)s times": "were unbanned %(repeats)s times",
"was unbanned %(repeats)s times": "was unbanned %(repeats)s times",
"were unbanned": "were unbanned",
"was unbanned": "was unbanned",
"were kicked %(repeats)s times": "were kicked %(repeats)s times",
"was kicked %(repeats)s times": "was kicked %(repeats)s times",
"were kicked": "were kicked",
"was kicked": "was kicked",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)schanged their name %(repeats)s times",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)schanged their name %(repeats)s times",
"%(severalUsers)schanged their name": "%(severalUsers)schanged their name",
"%(oneUser)schanged their name": "%(oneUser)schanged their name",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)schanged their avatar %(repeats)s times",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times",
"%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar",
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
"Please select the destination room for this message": "Please select the destination room for this message",
"Create new room": "Create new room",
"Welcome page": "Welcome page",
"Room directory": "Room directory",
"Start chat": "Start chat",
"New Password": "New Password",
"Start automatically after system login": "Start automatically after system login",
"Desktop specific": "Desktop specific",
"Analytics": "Analytics",
"Opt out of analytics": "Opt out of analytics",
"Options": "Options",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
"Passphrases must match": "Passphrases must match",
"Passphrase must not be empty": "Passphrase must not be empty",
"Export room keys": "Export room keys",
"Confirm passphrase": "Confirm passphrase",
"Import room keys": "Import room keys",
"File to import": "File to import",
"This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.",
"The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.",
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
"You must join the room to see its files": "You must join the room to see its files",
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Start new chat": "Start new chat",
"Guest users can't invite users. Please register.": "Guest users can't invite users. Please register.",
"Failed to invite": "Failed to invite",
"Failed to invite user": "Failed to invite user",
"Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:",
"Confirm Removal": "Confirm Removal",
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
"Unknown error": "Unknown error",
"Incorrect password": "Incorrect password",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "This will make your account permanently unusable. You will not be able to re-register the same user ID.",
"This action is irreversible.": "This action is irreversible.",
"To continue, please enter your password.": "To continue, please enter your password.",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:",
"Device name": "Device name",
"Device Name": "Device Name",
"Device key": "Device key",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.",
"In future this verification process will be more sophisticated.": "In future this verification process will be more sophisticated.",
"Verify device": "Verify device",
"I verify that the keys match": "I verify that the keys match",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.",
"Unable to restore session": "Unable to restore session",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.",
"Continue anyway": "Continue anyway",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
"Unknown devices": "Unknown devices",
"Unknown Address": "Unknown Address",
"Unblacklist": "Unblacklist",
"Blacklist": "Blacklist",
"Unverify": "Unverify",
"Verify...": "Verify...",
"ex. @bob:example.com": "ex. @bob:example.com",
"Add User": "Add User",
"This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot",
"Sign in with CAS": "Sign in with CAS",
"Custom Server Options": "Custom Server Options",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.",
"This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.",
"Dismiss": "Dismiss",
"Please check your email to continue registration.": "Please check your email to continue registration.",
"Token incorrect": "Token incorrect",
"A text message has been sent to": "A text message has been sent to",
"Please enter the code it contains:": "Please enter the code it contains:",
"powered by Matrix": "powered by Matrix",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
"You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s",
"Default server": "Default server",
"Custom server": "Custom server",
"Home server URL": "Home server URL",
"Identity server URL": "Identity server URL",
"What does this mean?": "What does this mean?",
"Error decrypting audio": "Error decrypting audio",
"Error decrypting image": "Error decrypting image",
"Image '%(Body)s' cannot be displayed.": "Image '%(Body)s' cannot be displayed.",
"This image cannot be displayed.": "This image cannot be displayed.",
"Error decrypting video": "Error decrypting video",
"Add an Integration": "Add an Integration",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?",
"Removed or unknown message type": "Removed or unknown message type",
"Disable URL previews by default for participants in this room": "Disable URL previews by default for participants in this room",
"Disable URL previews for this room (affects only you)": "Disable URL previews for this room (affects only you)",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "URL previews are %(globalDisableUrlPreview)s by default for participants in this room.",
"URL Previews": "URL Previews",
"Enable URL previews for this room (affects only you)": "Enable URL previews for this room (affects only you)",
"Drop file here to upload": "Drop file here to upload",
" (unsupported)": " (unsupported)",
"Ongoing conference call%(supportedText)s. %(joinText)s": "Ongoing conference call%(supportedText)s. %(joinText)s",
"for %(amount)ss": "for %(amount)ss",
"for %(amount)sm": "for %(amount)sm",
"for %(amount)sh": "for %(amount)sh",
"for %(amount)sd": "for %(amount)sd",
"Online": "Online",
"Idle": "Idle",
"Offline": "Offline",
"Start chatting": "Start chatting",
"Start Chatting": "Start Chatting",
"Click on the button below to start chatting!": "Click on the button below to start chatting!",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName changed the room avatar to <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
"Username available": "Username available",
"Username not available": "Username not available",
"Something went wrong!": "Something went wrong!",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.",
"If you already have a Matrix account you can <a>log in</a> instead.": "If you already have a Matrix account you can <a>log in</a> instead."
}

828
src/i18n/strings/en_US.json Normal file
View File

@@ -0,0 +1,828 @@
{
"af": "Afrikaans",
"ar-ae": "Arabic (U.A.E.)",
"ar-bh": "Arabic (Bahrain)",
"ar-dz": "Arabic (Algeria)",
"ar-eg": "Arabic (Egypt)",
"ar-iq": "Arabic (Iraq)",
"ar-jo": "Arabic (Jordan)",
"ar-kw": "Arabic (Kuwait)",
"ar-lb": "Arabic (Lebanon)",
"ar-ly": "Arabic (Libya)",
"ar-ma": "Arabic (Morocco)",
"ar-om": "Arabic (Oman)",
"ar-qa": "Arabic (Qatar)",
"ar-sa": "Arabic (Saudi Arabia)",
"ar-sy": "Arabic (Syria)",
"ar-tn": "Arabic (Tunisia)",
"ar-ye": "Arabic (Yemen)",
"be": "Belarusian",
"bg": "Bulgarian",
"ca": "Catalan",
"cs": "Czech",
"da": "Danish",
"de-at": "German (Austria)",
"de-ch": "German (Switzerland)",
"de": "German",
"de-li": "German (Liechtenstein)",
"de-lu": "German (Luxembourg)",
"el": "Greek",
"en-au": "English (Australia)",
"en-bz": "English (Belize)",
"en-ca": "English (Canada)",
"en": "English",
"en-gb": "English (United Kingdom)",
"en-ie": "English (Ireland)",
"en-jm": "English (Jamaica)",
"en-nz": "English (New Zealand)",
"en-tt": "English (Trinidad)",
"en-us": "English (United States)",
"en-za": "English (South Africa)",
"es-ar": "Spanish (Argentina)",
"es-bo": "Spanish (Bolivia)",
"es-cl": "Spanish (Chile)",
"es-co": "Spanish (Colombia)",
"es-cr": "Spanish (Costa Rica)",
"es-do": "Spanish (Dominican Republic)",
"es-ec": "Spanish (Ecuador)",
"es-gt": "Spanish (Guatemala)",
"es-hn": "Spanish (Honduras)",
"es-mx": "Spanish (Mexico)",
"es-ni": "Spanish (Nicaragua)",
"es-pa": "Spanish (Panama)",
"es-pe": "Spanish (Peru)",
"es-pr": "Spanish (Puerto Rico)",
"es-py": "Spanish (Paraguay)",
"es": "Spanish (Spain)",
"es-sv": "Spanish (El Salvador)",
"es-uy": "Spanish (Uruguay)",
"es-ve": "Spanish (Venezuela)",
"et": "Estonian",
"eu": "Basque (Basque)",
"fa": "Farsi",
"fi": "Finnish",
"fo": "Faeroese",
"fr-be": "French (Belgium)",
"fr-ca": "French (Canada)",
"fr-ch": "French (Switzerland)",
"fr": "French",
"fr-lu": "French (Luxembourg)",
"ga": "Irish",
"gd": "Gaelic (Scotland)",
"he": "Hebrew",
"hi": "Hindi",
"hr": "Croatian",
"hu": "Hungarian",
"id": "Indonesian",
"is": "Icelandic",
"it-ch": "Italian (Switzerland)",
"it": "Italian",
"ja": "Japanese",
"ji": "Yiddish",
"ko": "Korean",
"lt": "Lithuanian",
"lv": "Latvian",
"mk": "Macedonian (FYROM)",
"ms": "Malaysian",
"mt": "Maltese",
"nl-be": "Dutch (Belgium)",
"nl": "Dutch",
"no": "Norwegian",
"pl": "Polish",
"pt-br": "Brazilian Portuguese",
"pt": "Portuguese",
"rm": "Rhaeto-Romanic",
"ro-mo": "Romanian (Republic of Moldova)",
"ro": "Romanian",
"ru-mo": "Russian (Republic of Moldova)",
"ru": "Russian",
"sb": "Sorbian",
"sk": "Slovak",
"sl": "Slovenian",
"sq": "Albanian",
"sr": "Serbian",
"sv-fi": "Swedish (Finland)",
"sv": "Swedish",
"sx": "Sutu",
"sz": "Sami (Lappish)",
"th": "Thai",
"tn": "Tswana",
"tr": "Turkish",
"ts": "Tsonga",
"uk": "Ukrainian",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamese",
"xh": "Xhosa",
"zh-cn": "Chinese (PRC)",
"zh-hk": "Chinese (Hong Kong SAR)",
"zh-sg": "Chinese (Singapore)",
"zh-tw": "Chinese (Taiwan)",
"zu": "Zulu",
"A registered account is required for this action": "A registered account is required for this action",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains",
"accept": "accept",
"%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.",
"Account": "Account",
"Access Token:": "Access Token:",
"Add a topic": "Add a topic",
"Add email address": "Add email address",
"Add phone number": "Add phone number",
"Admin": "Admin",
"VoIP": "VoIP",
"Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.",
"No Microphones detected": "No Microphones detected",
"No Webcams detected": "No Webcams detected",
"No media permissions": "No media permissions",
"You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam",
"Default Device": "Default Device",
"Microphone": "Microphone",
"Camera": "Camera",
"Advanced": "Advanced",
"Algorithm": "Algorithm",
"Hide removed messages": "Hide removed messages",
"Always show message timestamps": "Always show message timestamps",
"Authentication": "Authentication",
"all room members": "all room members",
"all room members, from the point they are invited": "all room members, from the point they are invited",
"all room members, from the point they joined": "all room members, from the point they joined",
"an address": "an address",
"and": "and",
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
"%(items)s and one other": "%(items)s and one other",
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
"and %(overflowCount)s others...": "and %(overflowCount)s others...",
"and one other...": "and one other...",
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
"%(names)s and one other are typing": "%(names)s and one other are typing",
"%(names)s and %(count)s others are typing": "%(names)s and %(count)s others are typing",
"An email has been sent to": "An email has been sent to",
"A new password must be entered.": "A new password must be entered.",
"%(senderName)s answered the call.": "%(senderName)s answered the call.",
"anyone": "anyone",
"An error has occurred.": "An error has occurred.",
"Anyone": "Anyone",
"Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests",
"Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests",
"Are you sure?": "Are you sure?",
"Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?",
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Are you sure you want to upload the following files?": "Are you sure you want to upload the following files?",
"Attachment": "Attachment",
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
"%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.",
"Ban": "Ban",
"Banned users": "Banned users",
"Bans user with given id": "Bans user with given id",
"Blacklisted": "Blacklisted",
"Bug Report": "Bug Report",
"Bulk Options": "Bulk Options",
"Call Timeout": "Call Timeout",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
"Can't load user settings": "Can't load user settings",
"Change Password": "Change Password",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Changes to who can read history will only apply to future messages in this room",
"Changes your display nickname": "Changes your display nickname",
"changing room on a RoomView is not supported": "changing room on a RoomView is not supported",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key",
"Clear Cache and Reload": "Clear Cache and Reload",
"Clear Cache": "Clear Cache",
"Click here": "Click here",
"Click here to fix": "Click here to fix",
"Click to mute audio": "Click to mute audio",
"Click to mute video": "Click to mute video",
"click to reveal": "click to reveal",
"Click to unmute video": "Click to unmute video",
"Click to unmute audio": "Click to unmute audio",
"Command error": "Command error",
"Commands": "Commands",
"Conference call failed.": "Conference call failed.",
"Conference calling is in development and may not be reliable.": "Conference calling is in development and may not be reliable.",
"Conference calls are not supported in encrypted rooms": "Conference calls are not supported in encrypted rooms",
"Conference calls are not supported in this client": "Conference calls are not supported in this client",
"Confirm password": "Confirm password",
"Confirm your new password": "Confirm your new password",
"Continue": "Continue",
"Could not connect to the integration server": "Could not connect to the integration server",
"Create an account": "Create an account",
"Create Room": "Create Room",
"Cryptography": "Cryptography",
"Current password": "Current password",
"Curve25519 identity key": "Curve25519 identity key",
"Custom level": "Custom level",
"/ddg is not a command": "/ddg is not a command",
"Deactivate Account": "Deactivate Account",
"Deactivate my account": "Deactivate my account",
"decline": "decline",
"Decrypt %(text)s": "Decrypt %(text)s",
"Decryption error": "Decryption error",
"(default: %(userName)s)": "(default: %(userName)s)",
"Delete": "Delete",
"demote": "demote",
"Deops user with given id": "Deops user with given id",
"Default": "Default",
"Device already verified!": "Device already verified!",
"Device ID": "Device ID",
"Device ID:": "Device ID:",
"device id: ": "device id: ",
"Device key:": "Device key:",
"Devices": "Devices",
"Devices will not yet be able to decrypt history from before they joined the room": "Devices will not yet be able to decrypt history from before they joined the room",
"Direct Chat": "Direct Chat",
"Direct chats": "Direct chats",
"disabled": "disabled",
"Disable inline URL previews by default": "Disable inline URL previews by default",
"Disinvite": "Disinvite",
"Display name": "Display name",
"Displays action": "Displays action",
"Don't send typing notifications": "Don't send typing notifications",
"Download %(text)s": "Download %(text)s",
"Drop here %(toAction)s": "Drop here %(toAction)s",
"Drop here to tag %(section)s": "Drop here to tag %(section)s",
"Ed25519 fingerprint": "Ed25519 fingerprint",
"Email": "Email",
"Email address": "Email address",
"Email address (optional)": "Email address (optional)",
"Email, name or matrix ID": "Email, name or matrix ID",
"Emoji": "Emoji",
"Enable encryption": "Enable encryption",
"enabled": "enabled",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Encrypted messages will not be visible on clients that do not yet implement encryption",
"Encrypted room": "Encrypted room",
"%(senderName)s ended the call.": "%(senderName)s ended the call.",
"End-to-end encryption information": "End-to-end encryption information",
"End-to-end encryption is in beta and may not be reliable": "End-to-end encryption is in beta and may not be reliable",
"Enter Code": "Enter Code",
"Error": "Error",
"Error decrypting attachment": "Error decrypting attachment",
"Event information": "Event information",
"Existing Call": "Existing Call",
"Export": "Export",
"Export E2E room keys": "Export E2E room keys",
"Failed to ban user": "Failed to ban user",
"Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?",
"Failed to change power level": "Failed to change power level",
"Failed to delete device": "Failed to delete device",
"Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s",
"Failed to join room": "Failed to join room",
"Failed to join the room": "Failed to join the room",
"Failed to kick": "Failed to kick",
"Failed to leave room": "Failed to leave room",
"Failed to load timeline position": "Failed to load timeline position",
"Failed to lookup current room": "Failed to lookup current room",
"Failed to mute user": "Failed to mute user",
"Failed to register as guest:": "Failed to register as guest:",
"Failed to reject invite": "Failed to reject invite",
"Failed to reject invitation": "Failed to reject invitation",
"Failed to save settings": "Failed to save settings",
"Failed to send email": "Failed to send email",
"Failed to send request.": "Failed to send request.",
"Failed to set avatar.": "Failed to set avatar.",
"Failed to set display name": "Failed to set display name",
"Failed to set up conference call": "Failed to set up conference call",
"Failed to toggle moderator status": "Failed to toggle moderator status",
"Failed to unban": "Failed to unban",
"Failed to upload file": "Failed to upload file",
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
"Failure to create room": "Failure to create room",
"Favourite": "Favorite",
"favourite": "favorite",
"Favourites": "Favorites",
"Fill screen": "Fill screen",
"Filter room members": "Filter room members",
"Forget room": "Forget room",
"Forgot your password?": "Forgot your password?",
"For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.",
"Found a bug?": "Found a bug?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s",
"Guest access is disabled on this Home Server.": "Guest access is disabled on this Home Server.",
"Guests can't set avatars. Please register.": "Guests can't set avatars. Please register.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Guest users can't create new rooms. Please register to create room and start a chat.",
"Guest users can't upload files. Please register to upload.": "Guest users can't upload files. Please register to upload.",
"Guests can't use labs features. Please register.": "Guests can't use labs features. Please register.",
"Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.",
"had": "had",
"Hangup": "Hangup",
"Hide read receipts": "Hide read receipts",
"Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar",
"Historical": "Historical",
"Homeserver is": "Homeserver is",
"Identity Server is": "Identity Server is",
"I have verified my email address": "I have verified my email address",
"Import": "Import",
"Import E2E room keys": "Import E2E room keys",
"Incorrect username and/or password.": "Incorrect username and/or password.",
"Incorrect verification code": "Incorrect verification code",
"Interface Language": "Interface Language",
"Invalid alias format": "Invalid alias format",
"Invalid address format": "Invalid address format",
"Invalid Email Address": "Invalid Email Address",
"Invalid file%(extra)s": "Invalid file%(extra)s",
"%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.",
"Invite new room members": "Invite new room members",
"Invited": "Invited",
"Invites": "Invites",
"Invites user with given id to current room": "Invites user with given id to current room",
"is a": "is a",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
"%(displayName)s is typing": "%(displayName)s is typing",
"Sign in with": "Sign in with",
"Join Room": "Join Room",
"joined and left": "joined and left",
"joined": "joined",
"%(targetName)s joined the room.": "%(targetName)s joined the room.",
"Joins room with given alias": "Joins room with given alias",
"Jump to first unread message.": "Jump to first unread message.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.",
"Kick": "Kick",
"Kicks user with given id": "Kicks user with given id",
"Labs": "Labs",
"Leave room": "Leave room",
"left and rejoined": "left and rejoined",
"left": "left",
"%(targetName)s left the room.": "%(targetName)s left the room.",
"Level": "Level",
"List this room in %(domain)s's room directory?": "List this room in %(domain)s's room directory?",
"Local addresses for this room:": "Local addresses for this room:",
"Logged in as:": "Logged in as:",
"Login as guest": "Login as guest",
"Logout": "Logout",
"Low priority": "Low priority",
"%(senderName)s made future room history visible to": "%(senderName)s made future room history visible to",
"Manage Integrations": "Manage Integrations",
"Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled",
"matrix-react-sdk version:": "matrix-react-sdk version:",
"Members only": "Members only",
"Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present",
"Missing room_id in request": "Missing room_id in request",
"Missing user_id in request": "Missing user_id in request",
"Mobile phone number": "Mobile phone number",
"Mobile phone number (optional)": "Mobile phone number (optional)",
"Moderator": "Moderator",
"Must be viewing a room": "Must be viewing a room",
"Mute": "Mute",
"my Matrix ID": "my Matrix ID",
"Name": "Name",
"Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device",
"Never send encrypted messages to unverified devices in this room": "Never send encrypted messages to unverified devices in this room",
"Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device",
"New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"New Composer & Autocomplete": "New Composer & Autocomplete",
"New password": "New password",
"New passwords don't match": "New passwords don't match",
"New passwords must match each other.": "New passwords must match each other.",
"none": "none",
"not set": "not set",
"not specified": "not specified",
"Notifications": "Notifications",
"(not supported by this browser)": "(not supported by this browser)",
"<not supported>": "<not supported>",
"NOT verified": "NOT verified",
"No devices with registered encryption keys": "No devices with registered encryption keys",
"No more results": "No more results",
"No results": "No results",
"No users have specific privileges in this room": "No users have specific privileges in this room",
"OK": "OK",
"olm version:": "olm version:",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Once encryption is enabled for a room it cannot be turned off again (for now)",
"Once you&#39;ve followed the link it contains, click below": "Once you&#39;ve followed the link it contains, click below",
"Only people who have been invited": "Only people who have been invited",
"Operation failed": "Operation failed",
"Password": "Password",
"Password:": "Password:",
"Passwords can't be empty": "Passwords can't be empty",
"People": "People",
"Permissions": "Permissions",
"Phone": "Phone",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s placed a %(callType)s call.",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
"Please Register": "Please Register",
"Power level must be positive integer.": "Power level must be positive integer.",
"Press": "Press",
"Privacy warning": "Privacy warning",
"Privileged Users": "Privileged Users",
"Profile": "Profile",
"Reason": "Reason",
"Revoke Moderator": "Revoke Moderator",
"Refer a friend to Riot:": "Refer a friend to Riot:",
"Register": "Register",
"Registration required": "Registration required",
"rejected": "rejected",
"%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.",
"Reject invitation": "Reject invitation",
"Remote addresses for this room:": "Remote addresses for this room:",
"Remove Contact Information?": "Remove Contact Information?",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.",
"Remove": "Remove",
"Remove %(threePid)s?": "Remove %(threePid)s?",
"%(senderName)s requested a VoIP conference.": "%(senderName)s requested a VoIP conference.",
"Report it": "Report it",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"restore": "restore",
"Results from DuckDuckGo": "Results from DuckDuckGo",
"Return to app": "Return to app",
"Return to login screen": "Return to login screen",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings",
"Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again",
"riot-web version:": "riot-web version:",
"Room %(roomId)s not visible": "Room %(roomId)s not visible",
"Room Colour": "Room Color",
"Room name (optional)": "Room name (optional)",
"Rooms": "Rooms",
"Save": "Save",
"Scroll to bottom of page": "Scroll to bottom of page",
"Scroll to unread messages": "Scroll to unread messages",
"Search": "Search",
"Search failed": "Search failed",
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
"Send a message (unencrypted)": "Send a message (unencrypted)",
"Send an encrypted message": "Send an encrypted message",
"Sender device information": "Sender device information",
"Send Invites": "Send Invites",
"Send Reset Email": "Send Reset Email",
"sent an image": "sent an image",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.",
"sent a video": "sent a video",
"Server error": "Server error",
"Server may be unavailable or overloaded": "Server may be unavailable or overloaded",
"Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(",
"Server may be unavailable, overloaded, or the file too big": "Server may be unavailable, overloaded, or the file too big",
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
"Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.",
"Session ID": "Session ID",
"%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.",
"Setting a user name will create a fresh account": "Setting a user name will create a fresh account",
"Settings": "Settings",
"Show panel": "Show panel",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)",
"Signed Out": "Signed Out",
"Sign in": "Sign in",
"Sign out": "Sign out",
"since the point in time of selecting this option": "since the point in time of selecting this option",
"since they joined": "since they joined",
"since they were invited": "since they were invited",
"Some of your messages have not been sent.": "Some of your messages have not been sent.",
"Someone": "Someone",
"Sorry, this homeserver is using a login which is not recognised ": "Sorry, this homeserver is using a login which is not recognized ",
"Start a chat": "Start a chat",
"Start Chat": "Start Chat",
"Submit": "Submit",
"Success": "Success",
"tag as %(tagName)s": "tag as %(tagName)s",
"tag direct chat": "tag direct chat",
"Tagged as: ": "Tagged as: ",
"The default role for new room members is": "The default role for new room members is",
"The main address for this room is": "The main address for this room is",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.",
"This action cannot be performed by a guest user. Please register to be able to do this.": "This action cannot be performed by a guest user. Please register to be able to do this.",
"This email address is already in use": "This email address is already in use",
"This email address was not found": "This email address was not found",
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "The file '%(fileName)s' exceeds this home server's size limit for uploads",
"The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload",
"The remote side failed to pick up": "The remote side failed to pick up",
"This Home Server does not support login using email address.": "This Home Server does not support login using email address.",
"There was a problem logging in.": "There was a problem logging in.",
"This room has no local addresses": "This room has no local addresses",
"This room is not recognised.": "This room is not recognized.",
"These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways",
"The visibility of existing history will be unchanged": "The visibility of existing history will be unchanged",
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
"this invitation?": "this invitation?",
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
"This phone number is already in use": "This phone number is already in use",
"This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers",
"This room's internal ID is": "This room's internal ID is",
"times": "times",
"To ban users": "To ban users",
"to browse the directory": "to browse the directory",
"To configure the room": "To configure the room",
"to demote": "to demote",
"to favourite": "to favorite",
"To invite users into the room": "To invite users into the room",
"to join the discussion": "to join the discussion",
"To kick users": "To kick users",
"To link to a room it must have": "To link to a room it must have",
"to make a room or": "to make a room or",
"To remove other users' messages": "To remove other users' messages",
"To reset your password, enter the email address linked to your account": "To reset your password, enter the email address linked to your account",
"to restore": "to restore",
"To send events of type": "To send events of type",
"To send messages": "To send messages",
"to start a chat with someone": "to start a chat with someone",
"to tag as %(tagName)s": "to tag as %(tagName)s",
"to tag direct chat": "to tag direct chat",
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
"Turn Markdown off": "Turn Markdown off",
"Turn Markdown on": "Turn Markdown on",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).",
"Unable to add email address": "Unable to add email address",
"Unable to remove contact information": "Unable to remove contact information",
"Unable to restore previous session": "Unable to restore previous session",
"Unable to verify email address.": "Unable to verify email address.",
"Unban": "Unban",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.",
"Unable to capture screen": "Unable to capture screen",
"Unable to enable Notifications": "Unable to enable Notifications",
"Unable to load device list": "Unable to load device list",
"Unencrypted room": "Unencrypted room",
"unencrypted": "unencrypted",
"Unknown command": "Unknown command",
"unknown device": "unknown device",
"unknown error code": "unknown error code",
"Unknown room %(roomId)s": "Unknown room %(roomId)s",
"Unknown (user, device) pair:": "Unknown (user, device) pair:",
"unknown": "unknown",
"Unmute": "Unmute",
"Unrecognised command:": "Unrecognized command:",
"Unrecognised room alias:": "Unrecognized room alias:",
"uploaded a file": "uploaded a file",
"Upload avatar": "Upload avatar",
"Upload Failed": "Upload Failed",
"Upload Files": "Upload Files",
"Upload file": "Upload file",
"Usage": "Usage",
"Use compact timeline layout": "Use compact timeline layout",
"Use with caution": "Use with caution",
"User ID": "User ID",
"User Interface": "User Interface",
"User name": "User name",
"Users": "Users",
"User": "User",
"Verification Pending": "Verification Pending",
"Verification": "Verification",
"verified": "verified",
"Verified key": "Verified key",
"Video call": "Video call",
"Voice call": "Voice call",
"VoIP conference finished.": "VoIP conference finished.",
"VoIP conference started.": "VoIP conference started.",
"VoIP is unsupported": "VoIP is unsupported",
"(warning: cannot be disabled again!)": "(warning: cannot be disabled again!)",
"Warning!": "Warning!",
"WARNING: Device already verified, but keys do NOT MATCH!": "WARNING: Device already verified, but keys do NOT MATCH!",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!",
"Who can access this room?": "Who can access this room?",
"Who can read history?": "Who can read history?",
"Who would you like to add to this room?": "Who would you like to add to this room?",
"Who would you like to communicate with?": "Who would you like to communicate with?",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.",
"Would you like to": "Would you like to",
"You are already in a call.": "You are already in a call.",
"You're not in any rooms yet! Press": "You're not in any rooms yet! Press",
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
"You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.",
"You do not have permission to post to this room": "You do not have permission to post to this room",
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "You have entered an invalid contact. Try using their Matrix ID or email address.",
"You have no visible notifications": "You have no visible notifications",
"you must be a": "you must be a",
"You need to be able to invite users to do that.": "You need to be able to invite users to do that.",
"You need to be logged in.": "You need to be logged in.",
"You need to enter a user name.": "You need to enter a user name.",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a one-off; sorry for the inconvenience.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
"Your password has been reset": "Your password has been reset",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them",
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
"You should not yet trust it to secure data": "You should not yet trust it to secure data",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Sun": "Sun",
"Mon": "Mon",
"Tue": "Tue",
"Wed": "Wed",
"Thu": "Thu",
"Fri": "Fri",
"Sat": "Sat",
"Jan": "Jan",
"Feb": "Feb",
"Mar": "Mar",
"Apr": "Apr",
"May": "May",
"Jun": "Jun",
"Jul": "Jul",
"Aug": "Aug",
"Sep": "Sep",
"Oct": "Oct",
"Nov": "Nov",
"Dec": "Dec",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Set a display name:",
"Set a Display Name": "Set a Display Name",
"Upload an avatar:": "Upload an avatar:",
"This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.",
"Missing password.": "Missing password.",
"Passwords don't match.": "Passwords don't match.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Password too short (min %(MIN_PASSWORD_LENGTH)s).",
"This doesn't look like a valid email address.": "This doesn't look like a valid email address.",
"This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "User names may only contain letters, numbers, dots, hyphens and underscores.",
"An unknown error occurred.": "An unknown error occurred.",
"I already have an account": "I already have an account",
"An error occurred: %(error_string)s": "An error occurred: %(error_string)s",
"Topic": "Topic",
"Make Moderator": "Make Moderator",
"Make this room private": "Make this room private",
"Share message history with new users": "Share message history with new users",
"Encrypt room": "Encrypt room",
"There are no visible files in this room": "There are no visible files in this room",
"Room": "Room",
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
"Auto-complete": "Auto-complete",
"Resend all": "Resend all",
"(~%(searchCount)s results)": "(~%(searchCount)s results)",
"Cancel": "Cancel",
"cancel all": "cancel all",
"or": "or",
"now. You can also select individual messages to resend or cancel.": "now. You can also select individual messages to resend or cancel.",
"Active call": "Active call",
"Monday": "Monday",
"Tuesday": "Tuesday",
"Wednesday": "Wednesday",
"Thursday": "Thursday",
"Friday": "Friday",
"Saturday": "Saturday",
"Sunday": "Sunday",
"bold": "bold",
"italic": "italic",
"strike": "strike",
"underline": "underline",
"code": "code",
"quote": "quote",
"bullet": "bullet",
"numbullet": "numbullet",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times",
"%(severalUsers)sjoined": "%(severalUsers)sjoined",
"%(oneUser)sjoined": "%(oneUser)sjoined",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sleft %(repeats)s times",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sleft %(repeats)s times",
"%(severalUsers)sleft": "%(severalUsers)sleft",
"%(oneUser)sleft": "%(oneUser)sleft",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sjoined and left %(repeats)s times",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sjoined and left %(repeats)s times",
"%(severalUsers)sjoined and left": "%(severalUsers)sjoined and left",
"%(oneUser)sjoined and left": "%(oneUser)sjoined and left",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sleft and rejoined %(repeats)s times",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sleft and rejoined %(repeats)s times",
"%(severalUsers)sleft and rejoined": "%(severalUsers)sleft and rejoined",
"%(oneUser)sleft and rejoined": "%(oneUser)sleft and rejoined",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejected their invitations %(repeats)s times",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejected their invitation %(repeats)s times",
"%(severalUsers)srejected their invitations": "%(severalUsers)srejected their invitations",
"%(oneUser)srejected their invitation": "%(oneUser)srejected their invitation",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)shad their invitations withdrawn %(repeats)s times",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)shad their invitation withdrawn %(repeats)s times",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)shad their invitations withdrawn",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)shad their invitation withdrawn",
"were invited %(repeats)s times": "were invited %(repeats)s times",
"was invited %(repeats)s times": "was invited %(repeats)s times",
"were invited": "were invited",
"was invited": "was invited",
"were banned %(repeats)s times": "were banned %(repeats)s times",
"was banned %(repeats)s times": "was banned %(repeats)s times",
"were banned": "were banned",
"was banned": "was banned",
"were unbanned %(repeats)s times": "were unbanned %(repeats)s times",
"was unbanned %(repeats)s times": "was unbanned %(repeats)s times",
"were unbanned": "were unbanned",
"was unbanned": "was unbanned",
"were kicked %(repeats)s times": "were kicked %(repeats)s times",
"was kicked %(repeats)s times": "was kicked %(repeats)s times",
"were kicked": "were kicked",
"was kicked": "was kicked",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)schanged their name %(repeats)s times",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)schanged their name %(repeats)s times",
"%(severalUsers)schanged their name": "%(severalUsers)schanged their name",
"%(oneUser)schanged their name": "%(oneUser)schanged their name",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)schanged their avatar %(repeats)s times",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times",
"%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar",
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
"Please select the destination room for this message": "Please select the destination room for this message",
"Start automatically after system login": "Start automatically after system login",
"Desktop specific": "Desktop specific",
"Analytics": "Analytics",
"Opt out of analytics": "Opt out of analytics",
"Options": "Options",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
"Passphrases must match": "Passphrases must match",
"Passphrase must not be empty": "Passphrase must not be empty",
"Export room keys": "Export room keys",
"Enter passphrase": "Enter passphrase",
"Confirm passphrase": "Confirm passphrase",
"Import room keys": "Import room keys",
"File to import": "File to import",
"This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.",
"The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.",
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
"You must join the room to see its files": "You must join the room to see its files",
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Start new chat": "Start new chat",
"Guest users can't invite users. Please register.": "Guest users can't invite users. Please register.",
"Failed to invite": "Failed to invite",
"Failed to invite user": "Failed to invite user",
"Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:",
"Confirm Removal": "Confirm Removal",
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
"Unknown error": "Unknown error",
"Incorrect password": "Incorrect password",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "This will make your account permanently unusable. You will not be able to re-register the same user ID.",
"This action is irreversible.": "This action is irreversible.",
"To continue, please enter your password.": "To continue, please enter your password.",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:",
"Device name": "Device name",
"Device key": "Device key",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.",
"In future this verification process will be more sophisticated.": "In future this verification process will be more sophisticated.",
"Verify device": "Verify device",
"I verify that the keys match": "I verify that the keys match",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.",
"Unable to restore session": "Unable to restore session",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.",
"Continue anyway": "Continue anyway",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
"Unknown devices": "Unknown devices",
"Unknown Address": "Unknown Address",
"Unblacklist": "Unblacklist",
"Blacklist": "Blacklist",
"Unverify": "Unverify",
"Verify...": "Verify...",
"ex. @bob:example.com": "ex. @bob:example.com",
"Add User": "Add User",
"This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot",
"Sign in with CAS": "Sign in with CAS",
"Custom Server Options": "Custom Server Options",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.",
"This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.",
"Dismiss": "Dismiss",
"Please check your email to continue registration.": "Please check your email to continue registration.",
"Token incorrect": "Token incorrect",
"A text message has been sent to": "A text message has been sent to",
"Please enter the code it contains:": "Please enter the code it contains:",
"powered by Matrix": "powered by Matrix",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
"You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s",
"Default server": "Default server",
"Custom server": "Custom server",
"Home server URL": "Home server URL",
"Identity server URL": "Identity server URL",
"What does this mean?": "What does this mean?",
"Error decrypting audio": "Error decrypting audio",
"Error decrypting image": "Error decrypting image",
"Image '%(Body)s' cannot be displayed.": "Image '%(Body)s' cannot be displayed.",
"This image cannot be displayed.": "This image cannot be displayed.",
"Error decrypting video": "Error decrypting video",
"Add an Integration": "Add an Integration",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?",
"Removed or unknown message type": "Removed or unknown message type",
"Disable URL previews by default for participants in this room": "Disable URL previews by default for participants in this room",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "URL previews are %(globalDisableUrlPreview)s by default for participants in this room.",
"URL Previews": "URL Previews",
"Enable URL previews for this room (affects only you)": "Enable URL previews for this room (affects only you)",
"Drop file here to upload": "Drop file here to upload",
" (unsupported)": " (unsupported)",
"Ongoing conference call%(supportedText)s. %(joinText)s": "Ongoing conference call%(supportedText)s. %(joinText)s",
"for %(amount)ss": "for %(amount)ss",
"for %(amount)sm": "for %(amount)sm",
"for %(amount)sh": "for %(amount)sh",
"for %(amount)sd": "for %(amount)sd",
"Online": "Online",
"Idle": "Idle",
"Offline": "Offline",
"Disable URL previews for this room (affects only you)": "Disable URL previews for this room (affects only you)",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName changed the room avatar to <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s"
}

331
src/i18n/strings/es.json Normal file
View File

@@ -0,0 +1,331 @@
{
"af": "Africano",
"ar-ae": "Árabe (Emiratos Árabes Unidos)",
"ar-bh": "Árabe (Baréin)",
"ar-dz": "Árabe (Argelia)",
"ar-eg": "Árabe (Egipto)",
"ar-iq": "Árabe (Irak)",
"ar-jo": "Árabe (Jordania)",
"ar-kw": "Árabe (Kuwait)",
"ar-lb": "Árabe (Líbano)",
"ar-ly": "Árabe (Libia)",
"ar-ma": "Árabe (Marruecos)",
"ar-om": "Árabe (Omán)",
"ar-qa": "Árabe (Catar)",
"ar-sa": "Árabe (Arabia Saudita)",
"ar-sy": "Árabe (Siria)",
"ar-tn": "Árabe (Túnez)",
"ar-ye": "Árabe (Yemen)",
"be": "Bielorrusia",
"bg": "Bulgaria",
"ca": "Catalán",
"cs": "Checo",
"da": "Danés",
"de-at": "Alemán (Austria)",
"de-ch": "Alemán (Suiza)",
"de": "Alemán",
"de-li": "Alemán (Liechtenstein)",
"de-lu": "Alemán (Luxemburgo)",
"el": "Griego",
"en-au": "Inglés (Australia)",
"en-bz": "Inglés (Belice)",
"en-ca": "Inglés (Canadá)",
"en": "Inglés",
"en-gb": "Inglés (Reino Unido)",
"en-ie": "Inglés (Irlanda)",
"en-jm": "Inglés (Jamaica)",
"en-nz": "Inglés (Nueva Zelanda)",
"en-tt": "Inglés (Trinidad y Tobago)",
"en-us": "Inglés (Estados Unidos)",
"en-za": "Inglés (Sudáfrica)",
"es-ar": "Español (Argentina)",
"es-bo": "Español (Bolivia)",
"es-cl": "Español (Chile)",
"es-co": "Español (Colombia)",
"es-cr": "Español (Costa Rica)",
"es-do": "Español (República Dominicana)",
"es-ec": "Español (Ecuador)",
"es-gt": "Español (Guatemala)",
"es-hn": "Español (Honduras)",
"es-mx": "Español (México)",
"es-ni": "Español (Nicaragua)",
"es-pa": "Español (Panamá)",
"es-pe": "Español (Perú)",
"es-pr": "Español (Puerto Rico)",
"es-py": "Español (Paraguay)",
"es": "Español (España)",
"es-sv": "Español (El Salvador)",
"es-uy": "Español (Uruguay)",
"es-ve": "Español (Venezuela)",
"et": "Estonio",
"eu": "Vasco",
"fa": "Persa",
"fi": "Finés",
"fo": "Feroés",
"fr-be": "Francés (Bélgica)",
"fr-ca": "Francés (Canadá)",
"fr-ch": "Francés (Suiza)",
"fr": "Francés",
"fr-lu": "Francés (Luxemburgo)",
"ga": "Irlandés",
"gd": "Gaélico (Escocia)",
"he": "Hebreo",
"hi": "Hindi",
"hr": "Croata",
"hu": "Húngaro",
"id": "Indonesio",
"is": "Islandés",
"it-ch": "Italiano (Suiza)",
"it": "Italiano",
"ja": "Japonés",
"ji": "Yidis",
"ko": "Coreano",
"lt": "Lituano",
"lv": "Letón",
"mk": "Macedonio",
"ms": "Malayo",
"mt": "Maltés",
"nl-be": "Holandés (Bélgica)",
"nl": "Holandés",
"no": "Noruego",
"pl": "Polaco",
"pt-br": "Portugués (Brasil)",
"pt": "Portugués",
"rm": "Retorrománico",
"ro-mo": "Rumano (República de Moldavia)",
"ro": "Rumano",
"ru-mo": "Ruso (República de Moldavia)",
"ru": "Ruso",
"sb": "Sorbio",
"sk": "Eslovaco",
"sl": "Esloveno",
"sq": "Albanés",
"sr": "Serbio",
"sv-fi": "Sueco (Finlandia)",
"sv": "Sueco",
"sx": "Sotho",
"sz": "Sami (Lapón)",
"th": "Tailandés",
"tn": "Setsuana",
"tr": "Turco",
"ts": "Songa",
"uk": "Ucraniano",
"ur": "Urdú",
"ve": "Venda",
"vi": "Vietnamita",
"xh": "Josa",
"zh-cn": "Chino Mandarín",
"zh-hk": "Chino (Hong Kong RAE)",
"zh-sg": "Chino (Singapur)",
"zh-tw": "Chino (Taiwanés)",
"zu": "Zulú",
"A registered account is required for this action": "Una cuenta registrada es necesaria para esta acción",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Un mensaje de texto ha sido enviado a +%(msisdn)s. Por favor ingrese el código de verificación que lo contiene",
"accept": "Aceptar",
"%(targetName)s accepted an invitation.": "%(targetName)s ha aceptado una invitación.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s ha aceptado la invitación para %(displayName)s.",
"Account": "Cuenta",
"Access Token:": "Token de Acceso:",
"Add email address": "Agregar correo eléctronico",
"Add phone number": "Agregar número telefónico",
"Admin": "Administrador",
"Advanced": "Avanzado",
"Algorithm": "Algoritmo",
"Always show message timestamps": "Siempre mostrar la hora del mensaje",
"Authentication": "Autenticación",
"all room members": "Todos los miembros de la sala",
"all room members, from the point they are invited": "Todos los miembros de la sala, desde el momento en que son invitados",
"all room members, from the point they joined": "Todos los miembros de la sala, desde el momento en que se han unido",
"an address": "una dirección",
"and": "y",
"%(items)s and %(remaining)s others": "%(items)s y %(remaining)s otros",
"%(items)s and one other": "%(items)s y otro",
"%(items)s and %(lastItem)s": "%(items)s y %(lastItem)s",
"and %(overflowCount)s others...": "y %(overflowCount)s otros...",
"and one other...": "y otro...",
"%(names)s and %(lastPerson)s are typing": "%(names)s y %(lastPerson)s están escribiendo",
"%(names)s and one other are typing": "%(names)s y otro están escribiendo",
"%(names)s and %(count)s others are typing": "%(names)s y %(count)s otros están escribiendo",
"An email has been sent to": "Un correo ha sido enviado a",
"A new password must be entered.": "Una nueva clave debe ser ingresada.",
"%(senderName)s answered the call.": "%(senderName)s atendió la llamada.",
"anyone": "nadie",
"An error has occurred.": "Un error ha ocurrido.",
"Anyone who knows the room's link, apart from guests": "Nadie quien sepa el enlace de la sala, aparte de los invitados",
"Anyone who knows the room's link, including guests": "Nadie quien sepa del enlace de la sala, incluyendo los invitados",
"Are you sure?": "¿Estás seguro?",
"Are you sure you want to reject the invitation?": "¿Estás seguro que quieres rechazar la invitación?",
"Are you sure you want upload the following files?": "¿Estás seguro que quieres subir los siguientes archivos?",
"Attachment": "Adjunto",
"Autoplay GIFs and videos": "Reproducir automáticamente GIFs y videos",
"%(senderName)s banned %(targetName)s.": "%(senderName)s ha bloqueado a %(targetName)s.",
"Ban": "Bloquear",
"Banned users": "Usuarios bloqueados",
"Bans user with given id": "Bloquear usuario por ID",
"Blacklisted": "En lista negra",
"Bug Report": "Reporte de error",
"Bulk Options": "Opciones masivas",
"Call Timeout": "Tiempo de espera de la llamada",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "No se puede conectar con el servidor - Por favor verifique su conexión y asegúrese de que su <a>certificado SSL del servidor</a> sea confiable.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "No se puede conectar al servidor via HTTP, cuando es necesario un enlace HTTPS en la barra de direcciones de tu navegador. Ya sea usando HTTPS o <a>habilitando los scripts inseguros</a>.",
"Can't load user settings": "No se puede cargar las configuraciones del usuario",
"Change Password": "Cambiar clave",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s ha cambiado su nombre de %(oldDisplayName)s a %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s ha cambiado su foto de perfil.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ha cambiado el nivel de acceso de %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s ha cambiado el nombre de la sala a %(roomName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s ha cambiado el tema de la sala a \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Cambios para quien pueda leer el historial solo serán aplicados a futuros mensajes en la sala",
"Changes your display nickname": "Cambia la visualización de tu apodo",
"changing room on a RoomView is not supported": "cambiando la sala en un RoomView no esta soportado",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "El cambio de contraseña restablecerá actualmente todas las claves de cifrado de extremo a extremo de todos los dispositivos, haciendo que el historial de chat cifrado sea ilegible, a menos que primero exporte las claves de la habitación y vuelva a importarlas después. En el futuro esto será mejorado.",
"Claimed Ed25519 fingerprint key": "Clave Ed25519 es necesaria",
"Clear Cache and Reload": "Borrar caché y recargar",
"Clear Cache": "Borrar caché",
"Click here": "Haz clic aquí",
"Click here to fix": "Haz clic aquí para arreglar",
"Click to mute audio": "Haz clic para silenciar audio",
"Click to mute video": "Haz clic para silenciar video",
"click to reveal": "Haz clic para ver",
"Click to unmute video": "Haz clic para activar sonido del video",
"Click to unmute audio": "Haz clic para activar sonido de audio",
"Command error": "Error de comando",
"Commands": "Comandos",
"Conference call failed.": "La llamada de conferencia falló.",
"Conference calling is in development and may not be reliable.": "La llamada en conferencia esta en desarrollo y no podría ser segura.",
"Conference calls are not supported in encrypted rooms": "Las llamadas en conferencia no son soportadas en salas encriptadas",
"Conference calls are not supported in this client": "Las llamadas en conferencia no son soportadas en este navegador",
"Confirm password": "Confirmar clave",
"Confirm your new password": "Confirma tu nueva clave",
"Continue": "Continuar",
"Could not connect to the integration server": "No se pudo conectar al servidor de integración",
"Create an account": "Crear una cuenta",
"Create Room": "Crear una sala",
"Cryptography": "Criptografía",
"Current password": "Clave actual",
"Curve25519 identity key": "Clave de identidad Curve25519",
"/ddg is not a command": "/ddg no es un comando",
"Deactivate Account": "Desactivar Cuenta",
"Deactivate my account": "Desactivar mi cuenta",
"decline": "rechazar",
"Decrypt %(text)s": "Descifrar %(text)s",
"Decryption error": "Error al decifrar",
"Delete": "Eliminar",
"demote": "degradar",
"Deops user with given id": "Deops usuario con ID dado",
"Default": "Por defecto",
"Device ID": "ID del dispositivo",
"Devices": "Dispositivos",
"Devices will not yet be able to decrypt history from before they joined the room": "Los dispositivos aun no serán capaces de descifrar el historial antes de haberse unido a la sala",
"Direct Chat": "Conversación directa",
"Direct chats": "Conversaciones directas",
"Disable inline URL previews by default": "Deshabilitar previsualizacón de enlaces por defecto",
"Disinvite": "Deshacer invitación",
"Display name": "Nombre para mostrar",
"Displays action": "Mostrar acción",
"Don't send typing notifications": "No enviar notificaciones cuando se escribe",
"Download %(text)s": "Descargar %(text)s",
"Drop here %(toAction)s": "Suelta aquí %(toAction)s",
"Drop here to tag %(section)s": "Suelta aquí para etiquetar %(section)s",
"Ed25519 fingerprint": "Clave de cifrado Ed25519",
"Email": "Correo electrónico",
"Email address": "Dirección de correo electrónico",
"Email, name or matrix ID": "Correo electrónico, nombre o Matrix ID",
"Emoji": "Emoticones",
"Enable encryption": "Habilitar encriptación",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Los mensajes encriptados no serán visibles en navegadores que no han implementado aun la encriptación",
"Encrypted room": "Sala encriptada",
"%(senderName)s ended the call.": "%(senderName)s terminó la llamada.",
"End-to-end encryption information": "Información de encriptación de extremo a extremo",
"End-to-end encryption is in beta and may not be reliable": "Encriptación de extremo a extremo, esta en version beta y no podría ser confiable",
"Enter Code": "Ingresar Código",
"Error": "Error",
"Error decrypting attachment": "Error al descifrar adjunto",
"Event information": "Información del evento",
"Existing Call": "Llamada existente",
"Export E2E room keys": "Exportar claves E2E de la sala",
"Failed to ban user": "Bloqueo del usuario falló",
"Failed to change password. Is your password correct?": "Falló al cambiar la clave, ¿Está correcta tu clave?",
"Failed to change power level": "Falló al cambiar de nivel de acceso",
"Failed to delete device": "Falló al borrar el dispositivo",
"Failed to forget room %(errCode)s": "Falló al olvidar la sala %(errCode)s",
"Failed to join room": "Falló al unirse a la sala",
"Failed to join the room": "Falló al unirse a la sala",
"Failed to kick": "Falló al expulsar",
"Failed to leave room": "Falló al dejar la sala",
"Failed to load timeline position": "Falló al cargar el historico",
"Failed to lookup current room": "Falló al buscar la actual sala",
"Failed to mute user": "Falló al silenciar el usuario",
"Failed to reject invite": "Falló al rechazar invitación",
"Failed to reject invitation": "Falló al rechazar la invitación",
"Failed to save settings": "Falló al guardar la configuración",
"Failed to send email": "Falló al enviar el correo",
"Failed to send request.": "Falló al enviar la solicitud.",
"Failed to set avatar.": "Falló al establecer el avatar.",
"Failed to set display name": "Falló al establecer el nombre a mostrar",
"Failed to set up conference call": "Falló al configurar la llamada en conferencia",
"Failed to toggle moderator status": "Falló al cambiar estatus de moderador",
"Failed to unban": "Falló al desbloquear",
"Failed to upload file": "Falló al subir archivo",
"Failed to verify email address: make sure you clicked the link in the email": "Falló al verificar el correo electrónico: Asegúrese hacer clic en el enlace del correo",
"Failure to create room": "Falló al crear sala",
"Favourite": "Favorito",
"favourite": "favorito",
"Favourites": "Favoritos",
"Fill screen": "Llenar pantalla",
"Filter room members": "Filtrar los miembros de la sala",
"Forget room": "Olvidar sala",
"Forgot your password?": "¿Olvidaste tu clave?",
"For security, this session has been signed out. Please sign in again.": "Por seguridad, esta sesión ha sido cerrada. Por favor inicia sesión nuevamente.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Por seguridad, al cerrar la sesión borrará cualquier clave de encriptación de extremo a extremo en este navegador. Si quieres ser capaz de descifrar tu historial de conversación, para las futuras sesiones en Riot, por favor exporta las claves de la sala para protegerlas.",
"Found a bug?": "¿Encontraste un error?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s a %(toPowerLevel)s",
"Guests can't set avatars. Please register.": "Invitados no puedes establecer avatares. Por favor regístrate.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Usuarios invitados no pueden crear nuevas salas. Por favor regístrate para crear la sala y iniciar la conversación.",
"Guest users can't upload files. Please register to upload.": "Usuarios invitados no puedes subir archivos. Por favor regístrate para subir tus archivos.",
"Guests can't use labs features. Please register.": "Invitados no puedes usar las características en desarrollo. Por favor regístrate.",
"Guests cannot join this room even if explicitly invited.": "Invitados no pueden unirse a esta sala aun cuando han sido invitados explícitamente.",
"had": "tuvo",
"Hangup": "Colgar",
"Hide read receipts": "Ocultar mensajes leídos",
"Hide Text Formatting Toolbar": "Ocultar barra de herramientas de formato de texto",
"Historical": "Histórico",
"Homeserver is": "El servidor es",
"Identity Server is": "La identidad del servidor es",
"I have verified my email address": "He verificado mi dirección de correo electrónico",
"Import E2E room keys": "Importar claves E2E de la sala",
"Incorrect verification code": "Verificación de código incorrecta",
"Interface Language": "Idioma de la interfaz",
"Invalid alias format": "Formato de alias inválido",
"Invalid address format": "Formato de dirección inválida",
"Invalid Email Address": "Dirección de correo electrónico inválida",
"Invalid file%(extra)s": "Archivo inválido %(extra)s",
"%(senderName)s invited %(targetName)s.": "%(senderName)s ha invitado a %(targetName)s.",
"Invite new room members": "Invitar nuevos miembros a la sala",
"Invites": "Invitar",
"Invites user with given id to current room": "Invitar a usuario con ID dado a esta sala",
"is a": "es un",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' no es un formato válido para una dirección",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' no es un formato válido para un alias",
"%(displayName)s is typing": "%(displayName)s esta escribiendo",
"Sign in with": "Quiero iniciar sesión con",
"Join Room": "Unirte a la sala",
"joined and left": "unido y dejado",
"joined": "unido",
"%(targetName)s joined the room.": "%(targetName)s se ha unido a la sala.",
"Joins room with given alias": "Unirse a la sala con el alias dado",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s ha expulsado a %(targetName)s.",
"Kick": "Expulsar",
"Kicks user with given id": "Expulsar usuario con ID dado",
"Labs": "Laboratorios",
"Leave room": "Dejar sala",
"left and rejoined": "dejado y reunido",
"left": "dejado",
"%(targetName)s left the room.": "%(targetName)s ha dejado la sala.",
"Level": "Nivel",
"Local addresses for this room:": "Direcciones locales para esta sala:",
"Logged in as:": "Sesión iniciada como:",
"Login as guest": "Iniciar sesión como invitado",
"Logout": "Cerrar Sesión",
"Low priority": "Baja prioridad"
}

862
src/i18n/strings/fr.json Normal file
View File

@@ -0,0 +1,862 @@
{
"af": "Afrikaans",
"ar-ae": "Arabic (U.A.E.)",
"ar-bh": "Arabic (Bahrain)",
"ar-dz": "Arabic (Algeria)",
"ar-eg": "Arabic (Egypt)",
"ar-iq": "Arabic (Iraq)",
"ar-jo": "Arabic (Jordan)",
"ar-kw": "Arabic (Kuwait)",
"ar-lb": "Arabic (Lebanon)",
"ar-ly": "Arabic (Libya)",
"ar-ma": "Arabic (Morocco)",
"ar-om": "Arabic (Oman)",
"ar-qa": "Arabic (Qatar)",
"ar-sa": "Arabic (Saudi Arabia)",
"ar-sy": "Arabic (Syria)",
"ar-tn": "Arabic (Tunisia)",
"ar-ye": "Arabic (Yemen)",
"be": "Belarusian",
"bg": "Bulgarian",
"ca": "Catalan",
"cs": "Czech",
"da": "Danish",
"de-at": "German (Austria)",
"de-ch": "German (Switzerland)",
"de": "German",
"de-li": "German (Liechtenstein)",
"de-lu": "German (Luxembourg)",
"el": "Greek",
"en-au": "English (Australia)",
"en-bz": "English (Belize)",
"en-ca": "English (Canada)",
"en": "English",
"en-gb": "English (United Kingdom)",
"en-ie": "English (Ireland)",
"en-jm": "English (Jamaica)",
"en-nz": "English (New Zealand)",
"en-tt": "English (Trinidad)",
"en-us": "English (United States)",
"en-za": "English (South Africa)",
"es-ar": "Spanish (Argentina)",
"es-bo": "Spanish (Bolivia)",
"es-cl": "Spanish (Chile)",
"es-co": "Spanish (Colombia)",
"es-cr": "Spanish (Costa Rica)",
"es-do": "Spanish (Dominican Republic)",
"es-ec": "Spanish (Ecuador)",
"es-gt": "Spanish (Guatemala)",
"es-hn": "Spanish (Honduras)",
"es-mx": "Spanish (Mexico)",
"es-ni": "Spanish (Nicaragua)",
"es-pa": "Spanish (Panama)",
"es-pe": "Spanish (Peru)",
"es-pr": "Spanish (Puerto Rico)",
"es-py": "Spanish (Paraguay)",
"es": "Spanish (Spain)",
"es-sv": "Spanish (El Salvador)",
"es-uy": "Spanish (Uruguay)",
"es-ve": "Spanish (Venezuela)",
"et": "Estonian",
"eu": "Basque (Basque)",
"fa": "Farsi",
"fi": "Finnish",
"fo": "Faeroese",
"fr-be": "French (Belgium)",
"fr-ca": "French (Canada)",
"fr-ch": "French (Switzerland)",
"fr": "French",
"fr-lu": "French (Luxembourg)",
"ga": "Irish",
"gd": "Gaelic (Scotland)",
"he": "Hebrew",
"hi": "Hindi",
"hr": "Croatian",
"hu": "Hungarian",
"id": "Indonesian",
"is": "Icelandic",
"it-ch": "Italian (Switzerland)",
"it": "Italian",
"ja": "Japanese",
"ji": "Yiddish",
"ko": "Coréen",
"lt": "Lithuanian",
"lv": "Latvian",
"mk": "Macedonian (FYROM)",
"ms": "Malaysian",
"mt": "Maltese",
"nl-be": "Dutch (Belgium)",
"nl": "Dutch",
"no": "Norwegian",
"pl": "Polish",
"pt-br": "Brazilian Portuguese",
"pt": "Portuguese",
"rm": "Rhaeto-Romanic",
"ro-mo": "Romanian (Republic of Moldova)",
"ro": "Romanian",
"ru-mo": "Russian (Republic of Moldova)",
"ru": "Russian",
"sb": "Sorbian",
"sk": "Slovak",
"sl": "Slovenian",
"sq": "Albanian",
"sr": "Serbe",
"sv-fi": "Swedish (Finland)",
"sv": "Swedish",
"sx": "Sutu",
"sz": "Sami (Lappish)",
"th": "Thai",
"tn": "Tswana",
"tr": "Turkish",
"ts": "Tsonga",
"uk": "Ukrainian",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamese",
"xh": "Xhosa",
"zh-cn": "Chinese (PRC)",
"zh-hk": "Chinese (Hong Kong SAR)",
"zh-sg": "Chinese (Singapore)",
"zh-tw": "Chinese (Taiwan)",
"zu": "Zulu",
"anyone": "n'importe qui",
"Direct Chat": "Conversation Directe",
"Direct chats": "Conversations directes",
"Disable inline URL previews by default": "Désactiver laperçu des URLs",
"Disinvite": "Désinviter",
"Display name": "Nom d'affichage",
"Displays action": "Affiche l'action",
"Don't send typing notifications": "Ne pas envoyer les notifications de saisie",
"Download %(text)s": "Télécharger %(text)s",
"Drop here %(toAction)s": "Déposer ici %(toAction)s",
"Drop here to tag %(section)s": "Déposer ici pour marque comme %(section)s",
"Ed25519 fingerprint": "Empreinte Ed25519",
"Email Address": "Adresse e-mail",
"Email, name or matrix ID": "E-mail, nom ou identifiant Matrix",
"Emoji": "Emoticône",
"Enable encryption": "Activer l'encryption",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Les messages encryptés ne seront pas visibles dans les clients qui nimplémentent pas encore lencryption",
"Encrypted room": "Salon encrypté",
"%(senderName)s ended the call.": "%(senderName)s a terminé lappel.",
"End-to-end encryption information": "Information sur l'encryption bout-en-bout",
"End-to-end encryption is in beta and may not be reliable": "Lencryption bout-en-bout est en bêta et risque de ne pas être fiable",
"Enter Code": "Entrer le code",
"Error": "Erreur",
"Event information": "Information de l'événement",
"Existing Call": "Appel en cours",
"Export E2E room keys": "Exporter les clés d'encryption du salon",
"Failed to ban user": "Échec lors du bannissement de l'utilisateur",
"Failed to change password. Is your password correct?": "Échec du changement de mot de passe. Votre mot de passe est-il correct ?",
"Failed to change power level": "Échec du changement de niveau d'autorité",
"Failed to delete device": "Échec de la suppression de l'appareil",
"Failed to forget room %(errCode)s": "Échec lors de l'oubli du salon %(errCode)s",
"Please Register": "Veuillez vous enregistrer",
"Remove": "Supprimer",
"was banned": "a été banni(e)",
"was invited": "a été invité(e)",
"was kicked": "a été expulsé(e)",
"was unbanned": "a été amnistié(e)",
"Monday": "Lundi",
"Tuesday": "Mardi",
"Wednesday": "Mercredi",
"Thursday": "Jeudi",
"Friday": "Vendredi",
"Saturday": "Samedi",
"Sunday": "Dimanche",
"bold": "gras",
"italic": "italique",
"strike": "barré",
"underline": "souligné",
"Favourite": "Favoris",
"Notifications": "Notifications",
"Settings": "Paramètres",
"Failed to join the room": "Échec de l'adhésion au salon",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Un message texte a été envoyé à +%(msisdn)s. Merci d'entrer le code de vérification qu'il contient",
"accept": "Accepter",
"%(targetName)s accepted an invitation.": "%(targetName)s a accepté une invitation.",
"Account": "Compte",
"Add email address": "Ajouter une adresse e-mail",
"Add phone number": "Ajouter un numéro",
"Admin": "Admin",
"Advanced": "Avancé",
"Algorithm": "Algorithme",
"all room members": "tous les membres du salon",
"all room members, from the point they are invited": "tous les membres du salon, depuis le moment où ils ont été invités",
"all room members, from the point they joined": "tous les membres du salon, depuis le moment où ils ont joint",
"an address": "une adresse",
"and": "et",
"%(items)s and %(remaining)s others": "%(items)s et %(remaining)s autres",
"%(items)s and one other": "%(items)s et un autre",
"%(items)s and %(lastItem)s": "%(items)s et %(lastItem)s",
"and %(overflowCount)s others...": "et %(overflowCount)s autres...",
"and one other...": "et un autre...",
"%(names)s and %(lastPerson)s are typing": "%(names)s et %(lastPerson)s sont en train de taper",
"%(names)s and one other are typing": "%(names)s et un autre sont en train de taper",
"%(names)s and %(count)s others are typing": "%(names)s et %(count)s d'autres sont en train de taper",
"An email has been sent to": "Un e-mail a été envoyé à",
"A new password must be entered.": "Un nouveau mot de passe doit être entré.",
"Anyone who knows the room's link, apart from guests": "Tout ceux qui connaissent le lien du salon, à part les visiteurs",
"Anyone who knows the room's link, including guests": "Tout ceux qui connaissent le lien du salon, y compris les visiteurs",
"Are you sure?": "Êtes-vous sûr ?",
"Are you sure you want to reject the invitation?": "Êtes-vous sûr de vouloir rejeter l'invitation ?",
"Are you sure you want to upload the following files?": "Êtes-vous sûr de vouloir télécharger les fichiers suivants ?",
"Attachment": "Pièce jointe",
"Autoplay GIFs and videos": "Jouer automatiquement les GIFs et vidéos",
"%(senderName)s banned %(targetName)s.": "%(senderName)s a banni %(targetName)s.",
"Ban": "Bannir",
"Banned users": "Utilisateurs bannis",
"Bans user with given id": "Utilisateurs bannis avec un identifiant donné",
"Blacklisted": "Sur liste noire",
"Bug Report": "Rapport d'erreur",
"Call Timeout": "Délai dappel expiré",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Connexion au Home Server impossible - merci de vérifier votre connectivité et que le <a>certificat SSL de votre Home Server</a> est de confiance.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Impossible de se connecter au homeserver en HTTP si l'URL dans la barre de votre explorateur est en HTTPS. Utilisez HTTPS ou <a>activez le support des scripts non-vérifiés</a>.",
"Can't load user settings": "Impossible de charger les paramètres utilisateur",
"Change Password": "Changer le mot de passe",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s a changé son nom daffichage de %(oldDisplayName)s en %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s a changé sa photo de profil.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s a changé le niveau de pouvoir de %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s a changé le nom du salon en %(roomName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s a changé le sujet du salon en \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Les changements de visibilité de lhistorique de ce salon ne sappliquent quaux messages futurs",
"Changes your display nickname": "Change votre nom d'affichage",
"Claimed Ed25519 fingerprint key": "Clé empreinte Ed25519 revendiquée",
"Clear Cache and Reload": "Vider le cache et rafraîchir",
"Clear Cache": "Vider le cache",
"Click here": "Cliquer ici",
"Click here to fix": "Cliquer ici pour réparer",
"Click to mute audio": "Cliquer pour couper le son",
"Click to mute video": "Cliquer ici pour couper la vidéo",
"click to reveal": "cliquer pour dévoiler",
"Click to unmute video": "Cliquer pour rétablir la vidéo",
"Click to unmute audio": "Cliquer pour rétablir le son",
"Command error": "Erreur de commande",
"Commands": "Commandes",
"Conference call failed.": "Échec de la conférence.",
"Conference calling is in development and may not be reliable.": "Les appels en conférence sont encore en développement et sont potentiellement peu fiables.",
"Conference calls are not supported in encrypted rooms": "Les appels en conférence ne sont pas supportés dans les salons encryptés",
"Conference calls are not supported in this client": "Les appels en conférence ne sont pas supportés avec ce client",
"Confirm password": "Confirmer le mot de passe",
"Confirm your new password": "Confirmer votre nouveau mot de passe",
"Continue": "Continuer",
"Could not connect to the integration server": "Impossible de se connecter au serveur d'intégration",
"Create an account": "Créer un compte",
"Create Room": "Créer un salon",
"Cryptography": "Encryption",
"Current password": "Mot de passe actuel",
"Curve25519 identity key": "Clé didentité Curve25519",
"/ddg is not a command": "/ddg n'est pas une commande",
"Deactivate Account": "Supprimer le compte",
"Deactivate my account": "Supprimer mon compte",
"decline": "décliner",
"Decrypt %(text)s": "Décrypter %(text)s",
"Decryption error": "Erreur de décryptage",
"Delete": "Supprimer",
"demote": "rétrograder",
"Deops user with given id": "Retire les privilèges dopérateur dun utilisateur avec un ID donné",
"Device ID": "Identifiant de l'appareil",
"Devices": "Appareils",
"Devices will not yet be able to decrypt history from before they joined the room": "Les appareils ne seront pas capables de décrypter lhistorique précédant leur adhésion au salon",
"ml": "Malayalam",
"Failed to join room": "Échec lors de ladhésion au salon",
"Failed to kick": "Échec lors de l'expulsion",
"Failed to leave room": "Échec du départ",
"Failed to load timeline position": "Erreur lors du chargement de la position dans la chronologie",
"Failed to lookup current room": "Échec lors de la recherche du salon actuel",
"Failed to mute user": "Échec lors de l'interruption de l'utilisateur",
"Failed to reject invite": "Échec lors du rejet de l'invitation",
"Failed to reject invitation": "Échec lors du rejet de l'invitation",
"Failed to save settings": "Échec lors de la sauvegarde des paramètres",
"Failed to send email": "Échec lors de lenvoi de le-mail",
"Failed to send request.": "Erreur lors de l'envoi de la requête.",
"Failed to set display name": "Échec lors de l'enregistrement du nom d'affichage",
"Failed to set up conference call": "Échec lors de létablissement de lappel",
"Failed to toggle moderator status": "Échec lors de létablissement du statut de modérateur",
"A registered account is required for this action": "Il est nécessaire davoir un compte enregistré pour effectuer cette action",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s a accepté linvitation de %(displayName)s.",
"Access Token:": "Jeton daccès :",
"Always show message timestamps": "Toujours afficher l'heure des messages",
"Authentication": "Authentification",
"%(senderName)s answered the call.": "%(senderName)s a répondu à lappel.",
"An error has occurred.": "Une erreur est survenue.",
"Email": "E-mail",
"Failed to unban": "Échec de l'amnistie",
"Failed to upload file": "Échec du téléchargement",
"Failed to verify email address: make sure you clicked the link in the email": "Échec de la vérification de ladresse e-mail: vérifiez que vous avez bien cliqué sur le lien dans le-mail",
"Failure to create room": "Échec de la création du salon",
"favourite": "favoris",
"Favourites": "Favoris",
"Fill screen": "Plein écran",
"Filter room members": "Filtrer les membres par nom",
"Forget room": "Oublier le salon",
"Forgot your password?": "Mot de passe perdu ?",
"For security, this session has been signed out. Please sign in again.": "Par sécurité, la session a expiré. Merci de vous authentifer à nouveau.",
"Found a bug?": "Trouvé un problème ?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s à %(toPowerLevel)s",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Les visiteurs ne peuvent créer de nouveaux salons. Merci de vous enregistrer pour commencer une discussion.",
"Guest users can't upload files. Please register to upload.": "Les visiteurs ne peuvent telécharger de fichiers. Merci de vous enregistrer pour télécharger.",
"had": "avait",
"Hangup": "Raccrocher",
"Hide read receipts": "Cacher les accusés de réception",
"Hide Text Formatting Toolbar": "Cacher la barre de formattage de texte",
"Historical": "Historique",
"Homeserver is": "Le homeserver est",
"Identity Server is": "Le serveur d'identité est",
"I have verified my email address": "Jai vérifié mon adresse e-mail",
"Import E2E room keys": "Importer les clés dencryption bout-en-bout",
"Incorrect verification code": "Code de vérification incorrect",
"Interface Language": "Langue de l'interface",
"Invalid alias format": "Format de l'alias invalide",
"Invalid address format": "Format d'adresse invalide",
"Invalid Email Address": "Adresse e-mail invalide",
"%(senderName)s invited %(targetName)s.": "%(senderName)s a invité %(targetName)s.",
"Invite new room members": "Inviter de nouveaux membres",
"Invited": "Invités",
"Invites": "Invitations",
"Invites user with given id to current room": "Inviter lutilisateur avec un ID donné dans le salon actuel",
"is a": "est un",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' n'est pas un format valide pour une adresse",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' n'est pas un format valide pour un alias",
"%(displayName)s is typing": "%(displayName)s est en train de taper",
"Sign in with": "Je veux m'identifier avec",
"Join Room": "Rejoindre le salon",
"joined and left": "a joint et quitté",
"joined": "a joint",
"%(targetName)s joined the room.": "%(targetName)s a joint le salon.",
"Joins room with given alias": "Joint le salon avec l'alias défini",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s a expulsé %(targetName)s.",
"Kick": "Expluser",
"Kicks user with given id": "Expulse l'utilisateur and l'ID donné",
"Labs": "Laboratoire",
"Leave room": "Quitter le salon",
"left and rejoined": "a quitté et rejoint",
"left": "a quitté",
"%(targetName)s left the room.": "%(targetName)s a quitté le salon.",
"Level": "Niveau",
"Local addresses for this room:": "Adresse locale pour ce salon :",
"Logged in as:": "Identifié en tant que :",
"Login as guest": "S'identifier en tant que visiteur",
"Logout": "Se déconnecter",
"Low priority": "Priorité basse",
"%(senderName)s made future room history visible to": "%(senderName)s a rendu l'historique visible de",
"Manage Integrations": "Gestion des intégrations",
"Markdown is disabled": "Le formatage \"Markdown\" est désactivé",
"Markdown is enabled": "Le formatage “Markdown” est activé",
"matrix-react-sdk version:": "Version du matrix-react-sdk :",
"Members only": "Membres uniquement",
"Message not sent due to unknown devices being present": "Message non-envoyé à cause de la présence dappareils non-vérifiés",
"Missing room_id in request": "Absence du room_id dans la requête",
"Missing user_id in request": "Absence du user_id dans la requête",
"Mobile phone number": "Numéro de téléphone mobile",
"Moderator": "Modérateur",
"Must be viewing a room": "Doit être en train de visualiser un salon",
"my Matrix ID": "mon identifiant Matrix",
"Name": "Nom",
"Never send encrypted messages to unverified devices from this device": "Ne jamais envoyer de message encryptés aux appareils non-vérifiés depuis cet appareil",
"Never send encrypted messages to unverified devices in this room": "Ne jamais envoyer de message encryptés aux appareils non-vérifiés dans ce salon",
"Never send encrypted messages to unverified devices in this room from this device": "Ne jamais envoyer de message encryptés aux appareils non-vérifiés dans ce salon depuis cet appareil",
"New address (e.g. #foo:%(localDomain)s)": "Nouvelle adresse (par ex. #foo:%(localDomain)s)",
"New Composer & Autocomplete": "Nouveau compositeur & Autocomplétion",
"New password": "Nouveau mot de passe",
"New passwords don't match": "Les mots de passe ne correspondent pas",
"New passwords must match each other.": "Les nouveaux mots de passe doivent être identiques.",
"none": "aucun",
"not set": "non défini",
"not specified": "non spécifié",
"(not supported by this browser)": "(non supporté par cet explorateur)",
"<not supported>": "<non supporté>",
"NOT verified": "NON vérifié",
"No devices with registered encryption keys": "Pas dappareil avec des clés dencryption enregistrées",
"No more results": "Fin des résultats",
"No results": "Pas de résultats",
"unknown error code": "Code erreur inconnu",
"OK": "OK",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Une fois le chiffrement activé dans un salon il ne peut pas être désactivé (pour le moment)",
"Only people who have been invited": "Seul les personnes ayant été invitées",
"or": "ou",
"Password": "Mot de passe",
"Passwords can't be empty": "Le mot de passe ne peut pas être vide",
"People": "Personnes",
"Permissions": "Permissions",
"Phone": "Numéro de téléphone",
"Operation failed": "L'opération a échoué",
"Bulk Options": "Options de masse",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changer le mot de passe actuellement réinitialise les clés dencryption sur tous les appareils, rendant lhistorique encrypté illisible, à moins dexporter les clés du salon en avance de phase puis de les ré-importer. Ceci sera amélioré prochainement.",
"Default": "Défaut",
"Email address": "Adresse e-mail",
"Error decrypting attachment": "Erreur lors du déchiffrement de la pièce jointe",
"Failed to set avatar.": "Erreur lors de la définition de la photo de profil.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Par sécurité une deconnexion supprimera toutes les clés dencryption de cet explorateur. Si vous voulez être capable de décrypter lhistorique de votre conversation lors de sessions futures de Riot, merci dexporter les clés pour le salon.",
"Guests can't set avatars. Please register.": "Les visiteurs ne peuvent définir de photo de profil. Merci de vous enregistrer.",
"Guests can't use labs features. Please register.": "Les visiteurs ne peuvent utiliser les fonctionalités du laboratoire. Merci de vous enregistrer.",
"Guests cannot join this room even if explicitly invited.": "Les visiteurs ne peuvent rejoindre ce salon, même si explicitement invités.",
"Invalid file%(extra)s": "Fichier %(extra)s invalide",
"Mute": "Couper le son",
"No users have specific privileges in this room": "Aucun utilisateur na de privilège spécifique dans ce salon",
"olm version:": "version de olm :",
"Once you&#39;ve followed the link it contains, click below": "Une fois que vous aurez suivi le lien quil contient, cliquez ci-dessous",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s a placé un appel %(callType)s.",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Veuillez vérifier vos e-mails et cliquer sur le lien que vous avez reçu. Puis cliquez sur continuer.",
"Power level must be positive integer.": "Le niveau d'autorité doit être un entier positif.",
"Press": "Cliquer",
"Privacy warning": "Alerte de confidentialité",
"Privileged Users": "Utilisateur Privilégié",
"Profile": "Profil",
"Reason": "Raison",
"Revoke Moderator": "Révoquer le Modérateur",
"Refer a friend to Riot:": "Recommander Riot à un ami :",
"Registration required": "Inscription requise",
"rejected": "rejeté",
"%(targetName)s rejected the invitation.": "%(targetName)s a rejeté linvitation.",
"Reject invitation": "Rejeter l'invitation",
"Remove Contact Information?": "Supprimer les informations du contact ?",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s a supprimé son nom daffichage (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s a supprimé sa photo de profil.",
"Remove %(threePid)s?": "Supprimer %(threePid)s ?",
"%(senderName)s requested a VoIP conference.": "%(senderName)s a demandé une conférence audio.",
"Report it": "Le rapporter",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Réinitialiser le mot de passe va réinitialiser les clés dencryption sur tous les appareils, rendant lhistorique encrypté illisible, à moins que vous ayez exporté les clés du salon en avance de phase puis que vous les ayez ré-importées. Cela sera amélioré prochainement.",
"restore": "restorer",
"Return to app": "Retourner à lapplication",
"Return to login screen": "Retourner à lécran didentification",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot na pas la permission de vous envoyer des notifications - Merci de vérifier les paramètres de votre explorateur",
"Riot was not given permission to send notifications - please try again": "Riot na pas reçu la permission de vous envoyer des notifications - Merci dessayer à nouveau",
"riot-web version:": "Version de riot-web :",
"Room %(roomId)s not visible": "Le salon %(roomId)s n'est pas visible",
"Room Colour": "Couleur du salon",
"Room name (optional)": "Nom du salon (optionnel)",
"Rooms": "Salons",
"Scroll to bottom of page": "Aller en bas de la page",
"Scroll to unread messages": "Aller aux messages non-lus",
"Search": "Rechercher",
"Search failed": "Erreur lors de la recherche",
"Searches DuckDuckGo for results": "Recherche des résultats dans DuckDuckGo",
"Send a message (unencrypted)": "Envoyer un message (non-encrypté)",
"Send an encrypted message": "Envoyer un message non-encrypté",
"Sender device information": "Information de l'appareil de l'expéditeur",
"Send Invites": "Envoyer les invitations",
"Send Reset Email": "Envoyer l'e-mail de réinitialisation",
"sent an image": "a envoyé une image",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s a envoyé une image.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s a invité %(targetDisplayName)s à rejoindre le salon.",
"sent a video": "a envoyé une vidéo",
"Server error": "Erreur du serveur",
"Server may be unavailable or overloaded": "Le serveur semble être inaccessible ou surchargé",
"Server may be unavailable, overloaded, or search timed out :(": "Le serveur semble être inaccessible, surchargé ou la recherche a expiré :(",
"Server may be unavailable, overloaded, or the file too big": "Le serveur semble être inaccessible, surchargé ou le fichier trop important",
"Server may be unavailable, overloaded, or you hit a bug.": "Le serveur semble être indisponible, surchargé, ou vous avez rencontré un problème.",
"Server unavailable, overloaded, or something else went wrong.": "Le serveur semble être inaccessible, surchargé ou quelque chose s'est mal passé.",
"Session ID": "Identifiant de session",
"%(senderName)s set a profile picture.": "%(senderName)s a défini une photo de profil.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s a défini son nom daffichage comme %(displayName)s.",
"Show panel": "Dévoiler le panneau",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Afficher lheure au format am/pm (par ex. 2:30pm)",
"Signed Out": "Déconnecté",
"Sign in": "S'identifier",
"Sign out": "Se Déconnecter",
"since the point in time of selecting this option": "depuis le moment où cette option a été sélectionnée",
"since they joined": "depuis quils ont rejoint le salon",
"since they were invited": "depuis quils ont été invités",
"Some of your messages have not been sent.": "Certains de vos messages nont pas été envoyés.",
"Someone": "Quelqu'un",
"Sorry, this homeserver is using a login which is not recognised ": "Désolé, ce homeserver utilise un identifiant qui nest pas reconnu ",
"Start a chat": "Démarrer une conversation",
"Start Chat": "Démarrer une conversation",
"Submit": "Soumettre",
"Success": "Succès",
"tag as %(tagName)s": "marquer comme %(tagName)s",
"tag direct chat": "marquer comme conversation directe",
"The default role for new room members is": "Le rôle par défaut des nouveaux membres est",
"The main address for this room is": "L'adresse principale pour ce salon est",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Cette action ne peut être effectuée par un visiteur. Merci de vous enregistrer afin de pouvoir effectuer cette action.",
"This email address is already in use": "Cette adresse e-mail est déjà utilisée",
"This email address was not found": "Cette adresse e-mail na pas été trouvée",
"%(actionVerb)s this person?": "%(actionVerb)s cette personne ?",
"The email address linked to your account must be entered.": "Ladresse e-mail liée à votre compte doit être entrée.",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "Le fichier '%(fileName)s' dépasse la taille limite autorisée pour les téléchargements sur ce homeserver",
"The file '%(fileName)s' failed to upload": "Le fichier '%(fileName)s' na pas pu être téléchargé",
"The remote side failed to pick up": "Le correspondant na pas décroché",
"This room has no local addresses": "Ce salon n'a pas d'adresse locale",
"This room is not recognised.": "Ce salon n'a pas été reconnu.",
"These are experimental features that may break in unexpected ways": "Ces fonctionnalités sont expérimentales et risquent de mal fonctionner",
"The visibility of existing history will be unchanged": "La visibilité de lhistorique existant sera inchangée",
"This doesn't appear to be a valid email address": "Cette adresse na pas lair dêtre valide",
"this invitation?": "cette invitation ?",
"This is a preview of this room. Room interactions have been disabled": "Ceci est un aperçu du salon. Les interactions avec le salon ont été désactivées",
"This phone number is already in use": "Ce numéro de téléphone est déja utilisé",
"This room is not accessible by remote Matrix servers": "Ce salon nest pas accessible par les serveurs Matrix distants",
"This room's internal ID is": "L'identifiant interne de ce salon est",
"times": "fois",
"To ban users": "Pour bannir des utilisateurs",
"to browse the directory": "pour parcourir le répertoire",
"To configure the room": "Pour configurer le salon",
"to demote": "pour réduire la priorité",
"to favourite": "pour marquer comme favori",
"To invite users into the room": "Pour inviter des utilisateurs dans le salon",
"to join the discussion": "pour rejoindre la discussion",
"To kick users": "Pour expulser des utilisateurs",
"To link to a room it must have": "Pour avoir un lien vers un salon, il doit avoir",
"to make a room or": "pour créer un salon ou",
"To remove other users' messages": "Pour supprimer les messages des autres utilisateurs",
"To reset your password, enter the email address linked to your account": "Pour réinitialiser votre mot de passe, merci dentrer ladresse email liée à votre compte",
"to restore": "restaurer",
"To send events of type": "Pour envoyer des évènements du type",
"To send messages": "Pour envoyer des messages",
"to start a chat with someone": "pour démarrer une conversation avec quelquun",
"to tag as %(tagName)s": "pour marquer comme %(tagName)s",
"to tag direct chat": "pour marquer comme conversation directe",
"To use it, just wait for autocomplete results to load and tab through them.": "Pour lutiliser, attendez simplement que les résultats de lauto-complétion saffichent et défilez avec la touche Tab.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Une tentative de chargement dun point donné dans la chronologie de ce salon a été effectuée, mais vous navez pas la permission de voir le message en question.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Une tentative de chargement dun point donné dans la chronologie de ce salon a été effectuée, mais il na pas été trouvé.",
"Turn Markdown off": "Désactiver le formatage Markdown",
"Turn Markdown on": "Activer le formatage Markdown",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s a activé lencryption bout-en-bout (algorithme %(algorithm)s).",
"Unable to add email address": "Impossible d'ajouter l'adresse e-mail",
"Unable to remove contact information": "Impossible de supprimer les informations du contact",
"Unable to restore previous session": "Impossible de rétablir la session précédente",
"Unable to verify email address.": "Impossible de vérifier ladresse e-mail.",
"Unban": "Amnistier (annuler le bannissement)",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s a amnistié %(targetName)s.",
"Unable to capture screen": "Impossible de capturer l'écran",
"Unable to enable Notifications": "Impossible d'activer les notifications",
"Unable to load device list": "Impossible de charger la liste d'appareils",
"Unencrypted room": "Salon non-encrypté",
"unencrypted": "non-encrypté",
"Unknown command": "Commande inconnue",
"unknown device": "appareil inconnu",
"Unknown room %(roomId)s": "Salon inconnu %(roomId)s",
"unknown": "inconnu",
"Unmute": "Activer le son",
"uploaded a file": "télécharger un fichier",
"Upload avatar": "Télécharger une photo de profil",
"Upload Failed": "Erreur lors du téléchargement",
"Upload Files": "Télécharger les fichiers",
"Upload file": "Télécharger un fichier",
"Usage": "Utilisation",
"Use with caution": "Utiliser avec prudence",
"User ID": "Identifiant d'utilisateur",
"User Interface": "Interface utilisateur",
"User name": "Nom d'utilisateur",
"Users": "Utilisateurs",
"User": "Utilisateur",
"Verification Pending": "Vérification en cours",
"Verification": "Vérification",
"verified": "vérifié",
"Video call": "Appel vidéo",
"Voice call": "Appel audio",
"VoIP conference finished.": "Conférence audio terminée.",
"VoIP conference started.": "Conférence audio démarrée.",
"VoIP is unsupported": "Appels voix non supportés",
"(warning: cannot be disabled again!)": "(attention: ne peut être désactivé !)",
"Warning!": "Attention !",
"Who can access this room?": "Qui peut accéder au salon ?",
"Who can read history?": "Qui peut lire l'historique ?",
"Who would you like to add to this room?": "Qui voulez-vous inviter dans ce salon ?",
"Who would you like to communicate with?": "Avec qui voulez-vous communiquer ?",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s a révoqué linvitation de %(targetName)s.",
"Would you like to": "Voulez-vous",
"You are already in a call.": "Vous êtes déjà dans un appel.",
"You're not in any rooms yet! Press": "Vous nêtes dans aucun salon ! Cliquez",
"You are trying to access %(roomName)s.": "Vous essayez d'accéder à %(roomName)s.",
"You cannot place a call with yourself.": "Vous ne pouvez pas passer d'appel avec vous-même.",
"You cannot place VoIP calls in this browser.": "Vous ne pouvez pas passer d'appel voix dans cet explorateur.",
"You do not have permission to post to this room": "Vous navez pas la permission de poster dans ce salon",
"You have been invited to join this room by %(inviterName)s": "Vous avez été invité à joindre ce salon par %(inviterName)s",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "Vous avez été déconnecté de tous vos appareils et ne recevrez plus de notifications. Pour réactiver les notificationsm identifiez vous à nouveau sur tous les appareils",
"You have no visible notifications": "Vous n'avez pas de notifications visibles",
"you must be a": "vous devez être un",
"You need to be able to invite users to do that.": "Vous devez être capable dinviter des utilisateurs pour faire ça.",
"You need to be logged in.": "Vous devez être connecté.",
"You need to enter a user name.": "Vous devez entrer un nom dutilisateur.",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "Vous devez vous connecter à nouveau pour générer les clés dencryption pour cet appareil, et soumettre la clé publique à votre homeserver. Cette action ne se reproduira pas; veuillez nous excuser pour la gêne occasionnée.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Votre adresse e-mail ne semble pas associée à un identifiant Matrix sur ce homeserver.",
"Your password has been reset": "Votre mot de passe a été réinitialisé",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Votre mot de passe a été mis à jour avec succès. Vous ne recevrez plus de notification sur vos appareils jusquà ce que vous vous identifiez à nouveau",
"You seem to be in a call, are you sure you want to quit?": "Vous semblez avoir un appel en cours, êtes-vous sûr(e) de vouloir quitter ?",
"You seem to be uploading files, are you sure you want to quit?": "Vous semblez être en train de télécharger des fichiers, êtes-vous sûr(e) de vouloir quitter ?",
"You should not yet trust it to secure data": "Vous ne pouvez pas encore lui faire confiance pour sécuriser vos données",
"changing room on a RoomView is not supported": "changer de salon sur un RoomView n'est pas supporté",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Vous ne pourrez pas défaire ce changement car vous promouvez lutilisateur aux mêmes pouvoirs que vous.",
"Sun": "Dim",
"Mon": "Lun",
"Tue": "Mar",
"Wed": "Mer",
"Thu": "Jeu",
"Fri": "Ven",
"Sat": "Sam",
"Jan": "Jan",
"Feb": "Fev",
"Mar": "Mar",
"Apr": "Avr",
"May": "Mai",
"Jun": "Jun",
"Jul": "Jul",
"Aug": "Aou",
"Sep": "Sep",
"Oct": "Oct",
"Nov": "Nov",
"Dec": "Dec",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s %(day)s %(monthName)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s %(day)s %(monthName)s %(fullYear)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Définir un nom daffichage :",
"Upload an avatar:": "Télécharger une photo de profil :",
"This server does not support authentication with a phone number.": "Ce serveur ne supporte pas lidentification avec un numéro de téléphone.",
"Missing password.": "Mot de passe manquant.",
"Passwords don't match.": "Les mots de passe ne correspondent pas.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Mot de passe trop court (min %(MIN_PASSWORD_LENGTH)s).",
"This doesn't look like a valid email address.": "Cela ne semble pas être une adresse e-mail valide.",
"This doesn't look like a valid phone number.": "Cela ne semble pas être un numéro de téléphone valide.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "Les noms dutilisateurs ne peuvent contenir que des lettres, chiffres, points et tirets hauts ou bas.",
"An unknown error occurred.": "Une erreur inconnue est survenue.",
"I already have an account": "Jai déjà un compte",
"An error occurred: %(error_string)s": "Une erreur est survenue : %(error_string)s",
"Topic": "Sujet",
"Make Moderator": "Nommer modérateur",
"Make this room private": "Rendre ce salon privé",
"Share message history with new users": "Partager lhistorique des messages avec les nouveaux utilisateurs",
"Encrypt room": "Encrypter le salon",
"There are no visible files in this room": "Il n'y a pas de fichier visible dans ce salon",
"Room": "Salon",
"Connectivity to the server has been lost.": "La connectivité au serveur a été perdue.",
"Sent messages will be stored until your connection has returned.": "Les messages envoyé seront stockés jusquà ce que votre connection revienne.",
"Auto-complete": "Auto-complétion",
"Resend all": "Tout renvoyer",
"(~%(searchCount)s results)": "(~%(searchCount)s résultats)",
"Cancel": "Annuler",
"cancel all": "tout annuler",
"now. You can also select individual messages to resend or cancel.": "maintenant. Vous pouvez aussi sélectionner individuellement les messages que vous voulez renvoyer ou annuler.",
"Active call": "Appel en cours",
"code": "code",
"quote": "citer",
"bullet": "puce",
"numbullet": "liste numérotée",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sont rejoint le salon %(repeats)s fois",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sa rejoint le salon %(repeats)s fois",
"%(severalUsers)sjoined": "%(severalUsers)sont rejoint le salon",
"%(oneUser)sjoined": "%(oneUser)sa rejoint le salon",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sont quitté le salon %(repeats)s fois",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sa quitté le salon %(repeats)s fois",
"%(severalUsers)sleft": "%(severalUsers)sont quitté le salon",
"%(oneUser)sleft": "%(oneUser)sont quitté le salon",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sont rejoint et quitté le salon %(repeats)s fois",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sa rejoint et quitté le salon%(repeats)s fois",
"%(severalUsers)sjoined and left": "%(severalUsers)sont rejoint et quitté le salon",
"%(oneUser)sjoined and left": "%(oneUser)sa rejoint et quitté le salon",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sont quitté et à nouveau joint le salon %(repeats)s fois",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sa quitté et à nouveau joint le salon %(repeats)s fois",
"%(severalUsers)sleft and rejoined": "%(severalUsers)sont quitté et à nouveau joint le salon",
"%(oneUser)sleft and rejoined": "%(oneUser)sa quitté et à nouveau joint le salon",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)sotn rejeté leurs invitations %(repeats)s fois",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)sa rejeté son invitation %(repeats)s fois",
"%(severalUsers)srejected their invitations": "%(severalUsers)sont rejeté leurs invitations",
"%(oneUser)srejected their invitation": "%(oneUser)sa rejeté son invitation",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)sont vu leurs invitations rétractées %(repeats)s fois",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)sa vu son invitation rétractée %(repeats)s fois",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)sont vu leurs invitations rétractées",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)sa vu son invitation rétractée",
"were invited %(repeats)s times": "ont été invité(e)s %(repeats)s fois",
"was invited %(repeats)s times": "a été invité(e) %(repeats)s fois",
"were invited": "ont été invité(e)s",
"were banned %(repeats)s times": "ont été banni(e)s %(repeats)s fois",
"was banned %(repeats)s times": "été banni(e) %(repeats)s fois",
"were banned": "ont été banni(e)s",
"were unbanned %(repeats)s times": "ont été amnistié(e)s %(repeats)s fois",
"was unbanned %(repeats)s times": "a été amnistié(e) %(repeats)s fois",
"were unbanned": "ont été amnistié(e)s",
"were kicked %(repeats)s times": "ont été expulsé(e)s %(repeats)s fois",
"was kicked %(repeats)s times": "a été expulsé(e) %(repeats)s fois",
"were kicked": "ont été expulsé(e)s",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)sont changé leur nom %(repeats)s fois",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)sa changé son nom %(repeats)s times",
"%(severalUsers)schanged their name": "%(severalUsers)sont changé leur nom",
"%(oneUser)schanged their name": "%(oneUser)sa changé son nom",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)sont changé leur photo de profil %(repeats)s fois",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)sa changé sa photo de profil %(repeats)s fois",
"%(severalUsers)schanged their avatar": "%(severalUsers)sont changé leur photo de profil",
"%(oneUser)schanged their avatar": "%(oneUser)sa changé sa photo de profil",
"Please select the destination room for this message": "Merci de sélectionner un salon de destination pour ce message",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s a supprimé le nom du salon.",
"Analytics": "Outils d'analyse",
"Opt out of analytics": "Refuser de participer",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot recueille des données anonymes qui nous permettent danalyser et améliorer lapplication.",
"Passphrases must match": "Les phrases secrètes doivent être identiques",
"Passphrase must not be empty": "La phrase secrète ne doit pas être vide",
"Export room keys": "Exporter les clés du salon",
"Enter passphrase": "Entrer la phrase secrète",
"Confirm passphrase": "Confirmer la phrase secrète",
"Import room keys": "Importer les clés du salon",
"File to import": "Fichier à importer",
"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.": "Ce processus vous permet dexporter dans un fichier local les clés pour les messages que vous avez reçus dans des salons encryptés. Il sera ensuite possible dimporter ce fichier dans un autre client Matrix, afin de permettre à ce client de pouvoir décrypter ces messages.",
"The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Le fichier exporté permettra à tout ceux qui peuvent le lire de décrypter tous les messages encrypté auxquels vous avez accès, vous devez donc être vigilant et le stocker dans un endroit sûr. Afin de protéger ce fichier, entrez ci-dessous une phrase secrète qui sera utilisée pour encrypter les données exportées. Seule lutilisation de la même phrase secrète permettra de décrypter et importer les données.",
"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.": "Ce processus vous permet dimporter les clés dencryption que vous avez précédemment exportées depuis un autre client Matrix. Vous serez alors capable de décrypter nimporte quel messages que lautre client peut décrypter.",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Le fichier exporté est protégé par une phrase secrète. Vous devez entrer cette phrase secrète ici pour décrypter le fichier.",
"You must join the room to see its files": "Vous devez joindre le salon pour voir ses fichiers",
"Reject all %(invitedRooms)s invites": "Rejeter la totalité des %(invitedRooms)s invitations",
"Start new chat": "Démarrer une nouvelle conversation",
"Guest users can't invite users. Please register.": "Les visiteurs ne peuvent inviter dautres utilisateurs. Merci de vous enregistrer.",
"Failed to invite": "Echec de l'invitation",
"Failed to invite user": "Echec lors de l'invitation de l'utilisateur",
"Failed to invite the following users to the %(roomName)s room:": "Echec lors de linvitation des utilisateurs suivants dans le salon %(roomName)s :",
"Confirm Removal": "Confirmer la suppression",
"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.": "Êtes vous sûr de vouloir supprimer cet événement ? Notez que si vous supprimez le changement de nom dun salon ou la mise a jour du sujet dun salon, il est possible que le changement soit annulé.",
"Unknown error": "Erreur inconnue",
"Incorrect password": "Mot de passe incorrect",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Ceci rendra votre compte inutilisable de manière permanente. Vous ne pourrez pas enregistrer à nouveau le même identifiant utilisateur.",
"This action is irreversible.": "Cette action est irreversible.",
"To continue, please enter your password.": "Pour continuer, merci d'entrer votre mot de passe.",
"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:": "Pour vérifier que vous pouvez faire confiance à cet appareil, merci de contacter son propriétaire par un autre moyen (par ex. en personne ou par téléphone) et demandez lui si la clé quil/elle voit dans ses Paramètres Utilisateur pour cet appareil correspond à la clé ci-dessous :",
"Device name": "Nom de l'appareil",
"Device key": "Clé de l'appareil",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Si les clés correspondent, cliquer sur le bouton Vérifier ci-dessous. Si non, alors quelquun dautre est en train dintercepter cet appareil et vous devriez certainement cliquer sur le bouton Blacklister' (Ajouter à la liste noire) à la place.",
"In future this verification process will be more sophisticated.": "À lavenir ce processus de vérification sera simplifié et plus sophistiqué.",
"Verify device": "Vérifier cet appareil",
"I verify that the keys match": "Jai vérifié que les clés correspondaient",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Nous avons rencontré une erreur en essayant de rétablir votre session précédente. Si vous continuez, vous devrez vous identifier à nouveau et lhistorique encrypté de vos conversations sera illisible.",
"Unable to restore session": "Impossible de restaurer la session",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Si vous avez utilisé une version plus récente de Riot précédemment, votre session risque dêtre incompatible avec cette version. Fermez cette fenêtre et retournez à la version plus récente.",
"Continue anyway": "Continuer quand même",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Votre nom daffichage est la manière dont vous allez apparaître pour les autres quand vous parlerez dans les salons. Que voulez-vous quil soit ?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Vous êtes en train dajouter à la liste noire des appareils non-vérifiés ; pour envoyer des messages à ces appareils vous devez les vérifier.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Nous vous recommandons deffectuer le process de vérification pour tous les appareils afin de confirmer quils appartiennent à leurs propriétaires légitimes, mais vous pouvez renvoyer le(s) message(s) sans vérifier si vous préférez.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contient des appareils que vous n'avez encore jamais vus.",
"Unknown devices": "Appareils inconnus",
"Unknown Address": "Adresse inconnue",
"Unblacklist": "Réhabiliter",
"Blacklist": "Blacklister",
"Unverify": "Non-vérifié",
"Verify...": "Vérifier...",
"ex. @bob:example.com": "ex. @bob:exemple.com",
"Add User": "Ajouter l'utilisateur",
"This Home Server would like to make sure you are not a robot": "Ce homeserver veut vérifier que vous nêtes pas un robot",
"Sign in with CAS": "S'identifier avec CAS",
"Custom Server Options": "Options de serveur personnalisées",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Vous pouvez utiliser les options de serveur personnalisées pour vous identifier auprès dun autre serveur Matrix en spécifiant lURL dun homeserver différent.",
"This allows you to use this app with an existing Matrix account on a different home server.": "Cela vous permet dutiliser lapplication avec un compte Matrix existant sur un homeserver différent.",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Vous pouvez également configurer un serveur didentité différent mais cela risque entre autres dempêcher les interactions par e-mail avec les autres utilisateurs.",
"Dismiss": "Ignorer",
"Please check your email to continue registration.": "Merci de vérifier votre e-mail afin de continuer votre inscription.",
"Token incorrect": "Jeton incorrect",
"A text message has been sent to": "Un message texte a été envoyé au",
"Please enter the code it contains:": "Merci d'entre le code qu'il contient :",
"powered by Matrix": "propulsé par Matrix",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Si vous nentrez pas dadresse e-mail, vous ne pourrez pas réinitialiser votre mot de passe. Êtes vous sûr ?",
"You are registering with %(SelectedTeamName)s": "Vous vous enregistrez auprès de %(SelectedTeamName)s",
"Default server": "Serveur par défaut",
"Custom server": "Serveur personnalisé",
"Home server URL": "URL du homeserver",
"Identity server URL": "URL du serveur didentité",
"What does this mean?": "Quest ce que cela signifie ?",
"Error decrypting audio": "Erreur lors de la décryption de laudio",
"Error decrypting image": "Erreur lors de la décryption de limage",
"Image '%(Body)s' cannot be displayed.": "L'image '%(Body)s' ne peut être affichée.",
"This image cannot be displayed.": "Cette image ne peut être affichée.",
"Error decrypting video": "Erreur lors de la décryption de la vidéo",
"Add an Integration": "Ajouter une intégration",
"URL Previews": "Aperçus d'URL",
"Disable URL previews by default for participants in this room": "Désactiver les aperçus d'URL par défaut pour les participants de ce salon",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "Les aperçus d'URL sont %(globalDisableUrlPreview)s par défaut pour les participants de ce salon.",
"Enable URL previews for this room (affects only you)": "Activer les aperçus d'URL pour ce salon (n'affecte que vous)",
"Drop file here to upload": "Déposer le fichier ici pour le télécharger",
" (unsupported)": " (non supporté)",
"Ongoing conference call%(supportedText)s. %(joinText)s": "Appel conférence en cours%(supportedText)s. %(joinText)s",
"Online": "En ligne",
"Offline": "Déconnecté",
"Disable URL previews for this room (affects only you)": "Désactiver les aperçus d'URL pour ce salon (n'affecte que vous)",
"Desktop specific": "Spécifique à l'application de bureau",
"Start automatically after system login": "Démarrer automatiquement après la phase d'authentification du système",
"Idle": "Inactif",
"Jump to first unread message.": "Aller au premier message non-lu.",
"Options": "Options",
"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?": "Vous êtes sur le point daccéder à un site tiers afin de pouvoir vous identifier pour utiliser %(integrationsUrl)s. Voulez vous continuer ?",
"Removed or unknown message type": "Type de message inconnu ou supprimé",
"disabled": "désactivé",
"enabled": "activé",
"Set a Display Name": "Définir un nom daffichage",
"for %(amount)ss": "depuis %(amount)ss",
"for %(amount)sm": "depuis %(amount)sm",
"for %(amount)sh": "depuis %(amount)sh",
"for %(amount)sd": "depuis %(amount)sj",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName a changé limage de profil du salon en <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s a supprimé limage de profil du salon.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s a changé limage de profil de %(roomName)s",
"Device already verified!": "Appareil déjà vérifié !",
"Export": "Exporter",
"Failed to register as guest:": "Échec de linscription en tant que visiteur :",
"Guest access is disabled on this Home Server.": "Laccès en tant que visiteur est désactivé sur ce serveur.",
"Import": "Importer",
"Incorrect username and/or password.": "Nom dutilisateur et/ou mot de passe incorrect.",
"Results from DuckDuckGo": "Résultats de DuckDuckGo",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Les clés de signature que vous avez transmises correspondent aux clés que vous avez reçues de lappareil %(deviceId)s de %(userId)s. Lappareil est vérifié.",
"This Home Server does not support login using email address.": "Ce serveur ne supporte pas lidentification par e-mail.",
"There was a problem logging in.": "Un problème a été rencontré lors de lidentification.",
"Unknown (user, device) pair:": "Couple (utilisateur, appareil) inconnu :",
"Unrecognised command:": "Commande non-reconnue :",
"Unrecognised room alias:": "Alias de salon non-reconnu :",
"Use compact timeline layout": "Utiliser l'affichage compact",
"Verified key": "Clé vérifiée",
"WARNING: Device already verified, but keys do NOT MATCH!": "ATTENTION : Appareil déjà vérifié mais les clés NE CORRESPONDENT PAS !",
"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!": "ATTENTION : ERREUR DE VÉRIFICATION DES CLÉS ! La clé de signature pour %(userId)s et l'appareil %(deviceId)s est “%(fprint)s” et ne correspond pas à la clé “%(fingerprint)s” qui a été fournie. Cela peut signifier que vos communications sont interceptées !",
"VoIP": "Voix sur IP",
"Missing Media Permissions, click here to request.": "Manque de permission pour les médias, cliquer ici pour les demander.",
"No Microphones detected": "Aucun micro détecté",
"No Webcams detected": "Aucune webcam détectée",
"No media permissions": "Pas de permission pour les médias",
"You may need to manually permit Riot to access your microphone/webcam": "Il est possible que vous deviez manuellement permettre à Riot daccéder à votre micro/webcam",
"Default Device": "Appareil par défaut",
"Microphone": "Micro",
"Camera": "Caméra",
"Add a topic": "Ajouter un sujet",
"Anyone": "N'importe qui",
"Are you sure you want to leave the room '%(roomName)s'?": "Êtes-vous sûr de vouloir quitter le salon '%(roomName)s' ?",
"Custom level": "Niveau personnalisé",
"(default: %(userName)s)": "(défaut : %(userName)s)",
"Device ID:": "Identifiant de l'appareil :",
"device id: ": "Identifiant appareil : ",
"Device key:": "Clé de lappareil :",
"Email address (optional)": "Adresse e-mail (facultatif)",
"List this room in %(domain)s's room directory?": "Lister ce salon dans le répertoire de %(domain)s ?",
"Mobile phone number (optional)": "Numéro de téléphone (facultatif)",
"Password:": "Mot de passe :",
"Register": "S'inscrire",
"Remote addresses for this room:": "Supprimer l'adresse pour ce salon :",
"Save": "Enregistrer",
"Setting a user name will create a fresh account": "Définir un nouveau nom dutilisateur va créer un nouveau compte",
"Tagged as: ": "Étiquetter comme : ",
"You have <a>disabled</a> URL previews by default.": "Vous avez <a>désactivé</a> les aperçus dURL par défaut.",
"You have <a>enabled</a> URL previews by default.": "Vous avez <a>activé</a> les aperçus dURL par défaut.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Vous avez entré un contact invalide. Essayez dutiliser leur identifiant Matrix ou leur adresse email.",
"Hide removed messages": "Cacher les messages supprimés",
"Add": "Ajouter",
"%(count)s new messages.one": "%(count)s nouveau message",
"%(count)s new messages.other": "%(count)s nouveaux messages",
"Disable markdown formatting": "Désactiver le formattage markdown",
"Error: Problem communicating with the given homeserver.": "Erreur: Problème de communication avec le homeserveur.",
"Failed to fetch avatar URL": "Échec lors de la récupération de lURL de lavatar",
"The phone number entered looks invalid": "Le numéro de téléphone entré semble être invalide",
"Guest users can't upload files. Please register to upload.": "Les visiteurs ne peuvent pas télécharger de fichier. Veuillez vous enregistrer pour télécharger.",
"Some of your messages have not been sent.": "Certains de vos messages nont pas été envoyés.",
"This room is private or inaccessible to guests. You may be able to join if you register.": "Ce salon est privé ou interdits aux visiteurs. Vous pourrez peut-être le joindre si vous vous enregistrez.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Un instant donné de la chronologie na pu être chargé car vous navez pas la permission de le visualiser.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Un instant donné de la chronologie na pu être chargé car il na pas pu être trouvé.",
"Uploading %(filename)s and %(count)s others.zero": "Téléchargement de %(filename)s",
"Uploading %(filename)s and %(count)s others.one": "Téléchargement de %(filename)s et %(count)s autre",
"Uploading %(filename)s and %(count)s others.other": "Téléchargement de %(filename)s et %(count)s autres",
"You must <a>register</a> to use this functionality": "Vous devez vous <a>inscrire</a> pour utiliser cette fonctionnalité",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Tout renvoyer</a> or <a>tout annuler</a> maintenant. Vous pouvez aussi sélectionner des messages individuels à envoyer ou annuler.",
"Create new room": "Créer un nouveau salon",
"Welcome page": "Page d'accueil",
"Room directory": "Répertoire des salons",
"Start chat": "Démarrer une discussion",
"New Password": "Nouveau mot de passe",
"Start chatting": "Démarrer une discussion",
"Start Chatting": "Démarrer une discussion",
"Click on the button below to start chatting!": "Cliquer sur le bouton ci-dessous pour commencer une discussion !",
"Create a new chat or reuse an existing one": "Démarrer une nouvelle discussion ou en réutiliser une existante",
"You already have existing direct chats with this user:": "Vous avez déjà des discussions en cours avec cet utilisateur :",
"Username available": "Nom d'utilisateur disponible",
"Username not available": "Nom d'utilisateur indisponible",
"Something went wrong!": "Quelque chose sest mal passé !",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "Cela sera le nom de votre compte sur le serveur <span></span>, ou vous pouvez sélectionner un <a>autre serveur</a>.",
"If you already have a Matrix account you can <a>log in</a> instead.": "Si vous avez déjà un compte Matrix vous pouvez vous <a>identifier</a> à la place."
}

28
src/i18n/strings/hu.json Normal file
View File

@@ -0,0 +1,28 @@
{
"Cancel": "Mégse",
"Search": "Keresés",
"OK": "Rendben",
"Custom Server Options": "Egyedi szerver beállítások",
"Direct Chat": "Közvetlen csevegés",
"Dismiss": "Eltűntet",
"Drop here %(toAction)s": "%(toAction)s -t húzd ide",
"Error": "Hiba",
"Failed to forget room %(errCode)s": "Nem lehet eltávolítani a szobát: %(errCode)s",
"Failed to join the room": "Nem lehet csatlakozni a szobához",
"Favourite": "Kedvenc",
"Mute": "Elnémít",
"Notifications": "Értesítések",
"Operation failed": "Művelet sikertelen",
"Please Register": "Regisztrálj",
"powered by Matrix": "Matrixon alapul",
"Remove": "Töröl",
"Settings": "Beállítások",
"unknown error code": "ismeretlen hiba kód",
"Sunday": "Vasárnap",
"Monday": "Hétfő",
"Tuesday": "Kedd",
"Wednesday": "Szerda",
"Thursday": "Csütörtök",
"Friday": "Péntek",
"Saturday": "Szombat"
}

2
src/i18n/strings/ml.json Normal file
View File

@@ -0,0 +1,2 @@
{
}

203
src/i18n/strings/nl.json Normal file
View File

@@ -0,0 +1,203 @@
{
"af": "Afrikaans",
"ar-ae": "Arabisch (U.A.E.)",
"Direct Chat": "Privé gesprek",
"ar-bh": "Arabisch (Bahrain)",
"ar-dz": "Arabisch (Algerije)",
"ar-eg": "Arabisch (Egypte)",
"ar-iq": "Arabisch (Irak)",
"ar-jo": "Arabisch (Jordanië)",
"ar-kw": "Arabisch (Koeweit)",
"ar-lb": "Arabisch (Libanon)",
"ar-ly": "Arabisch (Libië)",
"ar-ma": "Arabisch (Marokko)",
"ar-om": "Arabisch (Oman)",
"ar-qa": "Arabisch (Katar)",
"ar-sa": "Arabisch (Saoedi-Arabië)",
"ar-sy": "Arabisch (Syrië)",
"ar-tn": "Arabisch (Tunesië)",
"ar-ye": "Arabisch (Jemen)",
"be": "Wit-Russisch",
"bg": "Bulgaars",
"ca": "Catalaans",
"cs": "Tsjechisch",
"da": "Deens",
"de-at": "Duits (Oostenrijk)",
"de-ch": "Duits (Zwitserland)",
"de": "Duits",
"de-li": "Duits (Liechtenstein)",
"de-lu": "Duits (Luxemburg)",
"el": "Grieks",
"en-au": "Engels (Australië)",
"en-bz": "Engels (Belize)",
"en-ca": "Engels (Canada)",
"en": "Engels",
"en-gb": "Engels (Verenigd Koningkrijk)",
"en-ie": "Engels (Ierland)",
"en-jm": "Engels (Jamaica)",
"en-nz": "Engels (Nieuw Zeeland)",
"en-tt": "Engels (Trinidad)",
"en-us": "Engels (Verenigde Staten)",
"en-za": "Engels (Zuid-Afrika)",
"es-ar": "Spaans (Argentinië)",
"es-bo": "Spaans (Bolivië)",
"es-cl": "Spaans (Chili)",
"es-co": "Spaans (Colombia)",
"es-cr": "Spaans (Costa Rica)",
"es-do": "Spaans (Dominicaanse Republiek)",
"es-ec": "Spaans (Ecuador)",
"es-gt": "Spaans (Guatemala)",
"es-hn": "Spaans (Honduras)",
"es-mx": "Spaans (Mexico)",
"es-ni": "Spaans (Nicaragua)",
"es-pa": "Spaans (Panama)",
"es-pe": "Spaans (Peru)",
"es-pr": "Spaans (Puerto Rico)",
"es-py": "Spaans (Paraguay)",
"es": "Spaans (Spanje)",
"es-sv": "Spaans (El Salvador)",
"es-uy": "Spaans (Uruguay)",
"es-ve": "Spaans (Venezuela)",
"et": "Estlands",
"eu": "Baskisch (Bask)",
"fa": "Farsi",
"fi": "Fins",
"fo": "Faeroesisch",
"fr-be": "Frans (België)",
"fr-ca": "Frans (Canada)",
"fr-ch": "Frans (Zwitserland)",
"fr": "Frans",
"fr-lu": "Frans (Luxemburg)",
"ga": "Iers",
"gd": "Keltisch (Schotland)",
"he": "Hebreeuws",
"hi": "Hindi",
"hr": "Kroatisch",
"hu": "Hongaars",
"id": "Indonesisch",
"is": "IJslands",
"it-ch": "Italiaans (Zwitserland)",
"it": "Italiaans",
"ja": "Japans",
"ji": "Jiddisch",
"ko": "Koreaans",
"lt": "Litouws",
"lv": "Lets",
"mk": "Macedonië (FYROM)",
"ms": "Maleisisch",
"mt": "Maltees",
"nl-be": "Nederlands (België)",
"nl": "Nederlands",
"no": "Noors",
"pl": "Pools",
"pt-br": "Braziliaans Portugees",
"pt": "Portugees",
"rm": "Rhetoromaans",
"ro-mo": "Roemeens (Moldavië)",
"ro": "Roemeens",
"ru-mo": "Russisch (Moldavië)",
"ru": "Russisch",
"sb": "Sorbisch",
"sk": "Slovaaks",
"sl": "Sloveens",
"sq": "Albanees",
"sr": "Servisch",
"sv-fi": "Zweeds (Finland)",
"sv": "Zweeds",
"sx": "Sutu",
"sz": "Sami (Lapland)",
"th": "Thais",
"tn": "Tswana",
"tr": "Turks",
"ts": "Tsonga",
"uk": "Oekraïens",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamees",
"xh": "Xhosa",
"zh-cn": "Chinees (PRC)",
"zh-hk": "Chinees (Hong Kong SAR)",
"zh-sg": "Chinees (Singapore)",
"zh-tw": "Chinees (Taiwan)",
"zu": "Zulu",
"A registered account is required for this action": "Voor deze actie is een geregistreerd account nodig",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Voer alsjeblieft de verificatiecode in die is verstuurd naar +%(msisdn)s",
"accept": "accepteer",
"%(targetName)s accepted an invitation.": "%(targetName)s heeft een uitnodiging geaccepteerd.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s heeft de uitnodiging voor %(displayName)s geaccepteerd.",
"Account": "Account",
"Access Token:": "Toegangstoken:",
"Add email address": "Voeg een email address toe",
"Add phone number": "Voeg een telefoonnummer toe",
"Admin": "Beheerder",
"Advanced": "Geavanceerd",
"Algorithm": "Algoritme",
"Always show message timestamps": "Laat altijd tijdstempels van berichten zien",
"Authentication": "Authenticatie",
"all room members": "alle kamer leden",
"all room members, from the point they are invited": "alle kamer leden, vanaf het moment dat ze uitgenodigt zijn",
"all room members, from the point they joined": "alle kamer leden, vanaf het moment dat ze toegetreden zijn",
"an address": "een adres",
"and": "en",
"%(items)s and %(remaining)s others": "%(items)s en %(remaining)s andere",
"%(items)s and one other": "%(items)s en één andere",
"%(items)s and %(lastItem)s": "%(items)s en %(lastItem)s",
"and %(overflowCount)s others...": "en %(overflowCount)s andere...",
"and one other...": "en één andere...",
"%(names)s and %(lastPerson)s are typing": "%(names)s en %(lastPerson)s zijn aan het typen",
"%(names)s and one other are typing": "%(names)s en één andere zijn aan het typen",
"%(names)s and %(count)s others are typing": "%(names)s en %(count)s andere zijn aan het typen",
"An email has been sent to": "Er is een email verzonden naar",
"A new password must be entered.": "Er moet een nieuw wachtwoord worden ingevoerd.",
"%(senderName)s answered the call.": "%(senderName)s heeft deelgenomen aan het audio gesprek.",
"anyone": "iedereen",
"An error has occurred.": "Er is een fout opgetreden.",
"Anyone who knows the room's link, apart from guests": "Iedereen die de kamer link weet, behalve gasten",
"Anyone who knows the room's link, including guests": "Iedereen die de kamer link weet, inclusief gasten",
"Are you sure?": "Weet je het zeker?",
"Are you sure you want to reject the invitation?": "Weet je zeker dat je de uitnodiging wilt weigeren?",
"Are you sure you want upload the following files?": "Weet je zeker dat je de volgende bestanden wilt uploaden?",
"Attachment": "Bijlage",
"Autoplay GIFs and videos": "Start GIFs en videos automatisch",
"%(senderName)s banned %(targetName)s.": "%(senderName)s heeft %(targetName)s verbannen.",
"Ban": "Verban",
"Banned users": "Verbannen gebruikers",
"Bans user with given id": "Verbant de gebruiker met de gegeven id",
"Blacklisted": "Op de zwarte lijst",
"Bug Report": "Bug report",
"Bulk Options": "Bulk opties",
"Call Timeout": "Gesprek time-out",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Kan niet met de homeserver verbinden - controleer alsjeblieft je verbinding en wees zeker dat je <a>homeserver's SSL certificaat</a> vertrouwd wordt.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Kan niet met de homeserver verbinden via HTTP wanneer er een HTTPS URL in je browser balk staat. Gebruik HTTPS of <a>activeer onveilige scripts</a>.",
"Can't load user settings": "Kan de gebruiker instellingen niet laden",
"Change Password": "Wachtwoord veranderen",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s heeft zijn of haar weergave naam veranderd van %(oldDisplayName)s naar %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s heeft zijn of haar profiel foto veranderd.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s heeft de rank van %(powerLevelDiffText)s gewijzigd.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s heeft de kamer naam van %(roomName)s gewijzigd.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s heeft het onderwerp gewijzigd naar \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Veranderingen aan wie de historie kan lezen worden alleen maar toegepast op toekomstige berichten in deze kamer",
"Changes your display nickname": "Verandert jouw weergavenaam",
"changing room on a RoomView is not supported": "veranderen van een kamer in een RoomView wordt niet ondersteund",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Het veranderen van het wachtwoord zal op het moment alle eind-tot-eind encryptie sleutels resetten, wat alle versleutelde chat geschiedenis onleesbaar zou maken, behalve als je eerst je kamer sleutels exporteert en achteraf opnieuw importeert. Dit zal worden verbeterd in de toekomst.",
"Clear Cache and Reload": "Legen cache en herlaad",
"Clear Cache": "Legen cache",
"Click here": "Klik hier",
"Click here to fix": "Klik hier om op te lossen",
"Click to mute audio": "Klik om audio te dempen",
"Click to mute video": "Klik om de video te dempen",
"click to reveal": "klik om te laten zien",
"Click to unmute video": "Klik om de demping van de video op te heffen",
"Click to unmute audio": "Klik om het dempen van het geluid op te heffen",
"Command error": "Opdracht fout",
"Commands": "Opdrachten",
"Conference call failed.": "Conferentie gesprek mislukt.",
"Conference calling is in development and may not be reliable.": "Conferentie gesprekken zijn nog in ontwikkelingen en kunnen onbetrouwbaar zijn.",
"Conference calls are not supported in encrypted rooms": "Conferentie gesprekken worden niet ondersteunt in versleutelde kamers",
"Conference calls are not supported in this client": "Conferentie gesprekken worden niet ondersteunt in deze client",
"Confirm password": "Bevestigen wachtwoord",
"Confirm your new password": "Bevestig je nieuwe wachtwoord",
"Continue": "Doorgaan",
"Could not connect to the integration server": "Mislukt om te verbinden met de integratie server",
"Cancel": "Annuleer"
}

879
src/i18n/strings/pt.json Normal file
View File

@@ -0,0 +1,879 @@
{
"accept": "aceitar",
"accepted an invitation": "aceitou um convite",
"accepted the invitation for": "aceitou o convite para",
"Account": "Conta",
"Add email address": "Adicionar endereço de email",
"Add phone number": "Adicionar número de telefone",
"Admin": "Administrador/a",
"Advanced": "Avançado",
"Algorithm": "Algoritmo",
"all room members, from the point they are invited.": "todos os membros da sala, a partir de quando foram convidados",
"all room members, from the point they joined.": "todos os membros da sala, a partir de quando entraram",
"all room members": "todas as pessoas da sala",
"an address": "um endereço",
"and": "e",
"An email has been sent to": "Um email foi enviado para",
"New passwords don't match": "As novas senhas não conferem",
"A new password must be entered.": "Uma nova senha precisa ser informada.",
"answered the call.": "respondeu à chamada.",
"Anyone who knows the room's link, apart from guests": "Qualquer pessoa que tenha o link da sala, exceto visitantes",
"Anyone who knows the room's link, including guests": "Qualquer pessoa que tenha o link da sala, incluindo visitantes",
"Are you sure you want to leave the room?": "Você tem certeza que deseja sair da sala?",
"Are you sure you want to reject the invitation?": "Você tem certeza que deseja rejeitar este convite?",
"Are you sure you want to upload the following files?": "Você tem certeza que deseja enviar os seguintes arquivos?",
"banned": "baniu",
"Banned users": "Usuárias/os banidas/os",
"Bans user with given id": "Banir usuários com o identificador informado",
"Blacklisted": "Bloqueado",
"Bug Report": "Repotar problemas de funcionamento",
"Bulk Options": "Opcões de Batelada",
"Can't load user settings": "Não é possível carregar configurações de usuário",
"changed avatar": "mudou sua imagem de perfil (avatar)",
"changed name": "mudou seu nome",
"changed their display name from": "mudou seu nome para",
"changed their profile picture": "alterou sua foto de perfil",
"changed the power level of": "mudou o nível de permissões de",
"changed the room name to": "mudou o nome da sala para",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s mudou o tópico para \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "As mudanças sobre quem pode ler o histórico da sala só serão aplicadas às mensagens futuras nesta sala",
"Changes your display nickname": "Troca o seu apelido",
"Claimed Ed25519 fingerprint key": "Chave reivindicada da Impressão Digital Ed25519",
"Clear Cache and Reload": "Limpar Memória Cache e Recarregar",
"Clear Cache": "Limpar Memória Cache",
"Click here": "Clique aqui",
"Click here to fix": "Clique aqui para resolver isso",
"Commands": "Comandos",
"Confirm password": "Confirme a nova senha",
"Confirm your new password": "Confirme a nova senha",
"Continue": "Continuar",
"Could not connect to the integration server": "Não foi possível conectar ao servidor de integrações",
"Create an account": "Criar uma conta",
"Create a new account": "Criar uma conta",
"Create Room": "Criar Sala",
"Cryptography": "Criptografia",
"Current password": "Senha atual",
"Curve25519 identity key": "Chave de Indetificação Curve25519",
"Deactivate Account": "Desativar conta",
"Deactivate my account": "Desativar minha conta",
"decline": "rejeitar",
"Decryption error": "Erro de descriptografia",
"Default": "Padrão",
"demote": "reduzir prioridade",
"Deops user with given id": "Retirar função de moderador do usuário com o identificador informado",
"Device ID": "Identificador do dispositivo",
"Devices will not yet be able to decrypt history from before they joined the room": "Os dispositivos não serão ainda capazes de descriptografar o histórico anterior à sua entrada na sala",
"Direct Chat": "Conversa pessoal",
"Disable inline URL previews by default": "Desabilitar visualizações prévias por padrão",
"Display name": "Nome",
"Displays action": "Visualizar atividades",
"Ed25519 fingerprint": "Impressão Digital Ed25519",
"Email Address": "endereço de email",
"Email, name or matrix ID": "Email, nome ou ID matrix",
"Emoji": "Emoji",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Mensagens criptografadas não serão visíveis em clientes que ainda não implementaram criptografia",
"Encrypted room": "Sala criptografada",
"Encryption is enabled in this room": "Criptografia está habilitada nesta sala",
"Encryption is not enabled in this room": "Criptografia não está habilitada nesta sala",
"ended the call.": "chamada encerrada.",
"End-to-end encryption information": "Informação criptografada ponta-a-ponta",
"End-to-end encryption is in beta and may not be reliable": "A criptografia ponta a ponta está em estágio beta e não deve ser totalmente confiável",
"Error": "Erro",
"Event information": "Informação do evento",
"Export E2E room keys": "Exportar chaves ponta-a-ponta da sala",
"Failed to change password. Is your password correct?": "Não foi possível modificar a senha. A senha informada está correta?",
"Failed to forget room": "Não foi possível esquecer a sala",
"Failed to leave room": "Falha ao tentar deixar a sala",
"Failed to reject invitation": "Falha ao tentar rejeitar convite",
"Failed to send email: ": "Falha ao tentar enviar email",
"Failed to set avatar.": "Falha ao tentar definir foto do perfil.",
"Failed to unban": "Não foi possível desfazer o banimento",
"Failed to upload file": "Falha ao enviar o arquivo",
"favourite": "favoritar",
"Favourite": "Favorito",
"Favourites": "Favoritos",
"Filter room members": "Filtrar integrantes da sala",
"Forget room": "Esquecer sala",
"Forgot your password?": "Esqueceu sua senha?",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Por segurança, deslogar irá remover qualquer chave de criptografia ponta-a-ponta deste navegador. Caso deseje descriptografar o histórico das suas conversas E2E em sessões Riot futuras, por favor exporte as chaves da sala para sua garantia.",
"For security, this session has been signed out. Please sign in again.": "Por questões de segurança, esta sessão foi encerrada. Por gentileza conecte-se novamente.",
"Found a bug?": "Encontrou um problema de funcionamento do sistema?",
"Guests cannot join this room even if explicitly invited.": "Visitantes não podem entrar nesta sala, mesmo se forem explicitamente convidadas/os.",
"Guests can't set avatars. Please register.": "Convidados não podem definir uma foto do perfil. Por favor, registre-se.",
"Guests can't use labs features. Please register.": "Convidados não podem usar as funcionalidades de laboratório (lab), por gentileza se registre.",
"Guest users can't upload files. Please register to upload.": "Usuários não podem fazer envio de arquivos. Por favor se cadastre para enviar arquivos.",
"had": "teve",
"Hangup": "Desligar",
"Historical": "Histórico",
"Homeserver is": "Servidor padrão é",
"Identity Server is": "O servidor de identificação é",
"I have verified my email address": "Eu verifiquei o meu endereço de email",
"Import E2E room keys": "Importar chave de criptografia ponta-a-ponta (E2E) da sala",
"Invalid Email Address": "Endereço de email inválido",
"invited": "convidou",
"Invite new room members": "Convidar novas pessoas para ingressar na sala",
"Invites": "Convidar",
"Invites user with given id to current room": "Convidar usuários com um dado identificador para esta sala",
"is a": "é um(a)",
"Sign in with": "Quero entrar",
"joined and left": "entrou e saiu",
"joined": "entrou",
"joined the room": "entrou na sala",
"Joins room with given alias": "Entra na sala com o nome informado",
"Kicks user with given id": "Remove usuário com o identificador informado",
"Labs": "Laboratório",
"Leave room": "Sair da sala",
"left and rejoined": "saiu e entrou novamente",
"left": "saiu",
"left the room": "saiu da sala",
"Logged in as": "Logado como",
"Login as guest": "Entrar como visitante",
"Logout": "Sair",
"Low priority": "Baixa prioridade",
"made future room history visible to": "deixou o histórico futuro da sala visível para",
"Manage Integrations": "Gerenciar integrações",
"Members only": "Apenas integrantes da sala",
"Mobile phone number": "Telefone celular",
"Moderator": "Moderador/a",
"my Matrix ID": "com meu ID do Matrix",
"Name": "Nome",
"Never send encrypted messages to unverified devices from this device": "Nunca envie mensagens criptografada para um dispositivo não verificado a partir deste dispositivo",
"Never send encrypted messages to unverified devices in this room from this device": "Nunca envie mensagens criptografadas para dispositivos não verificados nesta sala a partir deste dispositivo",
"New password": "Nova senha",
"New passwords must match each other.": "As novas senhas informadas precisam ser idênticas.",
"none": "nenhum",
"Notifications": "Notificações",
" (not supported by this browser)": "não suportado por este navegador",
"<not supported>": "<não suportado>",
"NOT verified": "NÃO verificado",
"No users have specific privileges in this room": "Nenhum/a usuário/a possui privilégios específicos nesta sala",
"olm version: ": "Versão do olm: ",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Assim que a criptografia é ativada para uma sala, ela não poderá ser desativada novamente (ainda)",
"Once you&#39;ve followed the link it contains, click below": "Quando você tiver clicado no link que está no email, clique o botão abaixo",
"Only people who have been invited": "Apenas pessoas que tenham sido convidadas",
"or": "ou",
"other": "outro",
"others": "outros",
"Password": "Senha",
"Passwords can't be empty": "As senhas não podem estar em branco",
"People": "Pessoas",
"Permissions": "Permissões",
"Phone": "Telefone",
"placed a": "iniciou uma",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Por favor verifique seu email e clique no link enviado. Quando finalizar este processo, clique para continuar.",
"Please Register": "Por favor registe-se",
"Privacy warning": "Alerta sobre privacidade",
"Privileged Users": "Usuárias/os privilegiadas/os",
"Profile": "Perfil",
"Refer a friend to Riot:": "Indicar um amigo para participar",
"rejected": "recusou",
"rejected the invitation.": "rejeitou o convite.",
"Reject invitation": "Rejeitar convite",
"Remove Contact Information?": "Remover informação de contato?",
"removed their display name": "removeu seu nome",
"removed their profile picture": "removeu sua foto de perfil",
"Remove": "Remover",
"requested a VoIP conference": "requisitou uma conferência VoIP",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Atualmente, ao alterar sua senha, você também zera todas as chaves de criptografia ponta-a-ponta em todos os dipositivos, fazendo com que o histórico de conversas da sala não possa mais ser lido, a não ser que você antes exporte suas chaves de sala e as reimporte após a alteração da senha. No futuro, isso será melhorado.",
"restore": "restaurar",
"Return to app": "Retornar ao aplicativo",
"Return to login screen": "Retornar à tela de login",
"Room Colour": "Cores da sala",
"Room name (optional)": "Título da Sala (opcional)",
"Rooms": "Salas",
"Scroll to bottom of page": "Ir para o fim da página",
"Scroll to unread messages": "Rolar para baixo para ver as mensagens não lidas",
"Searches DuckDuckGo for results": "Buscar por resultados no buscador DuckDuckGo",
"Send a message (unencrypted)": "Enviar uma mensagem",
"Send an encrypted message": "Enviar uma mensagem criptografada",
"Sender device information": "Informação do dispositivo emissor",
"Send Invites": "Enviar convites",
"Send Reset Email": "Enviar email para redefinição de senha",
"sent an image": "enviou uma imagem",
"sent an invitation to": "enviou um convite para",
"sent a video": "enviou um vídeo",
"Server may be unavailable or overloaded": "Servidor pode estar indisponível ou sobrecarregado",
"Server may be unavailable, overloaded, or you hit a bug.": "O servidor pode estar indisponível ou sobrecarregado, ou então você encontrou uma falha no sistema.",
"Session ID": "Identificador de sessão",
"set a profile picture": "colocou uma foto de perfil",
"set their display name to": "configurou seu nome para",
"Settings": "Configurações",
"Show panel": "Mostrar painel",
"Signed Out": "Deslogar",
"Sign in": "Entrar",
"Sign out": "Sair",
"since the point in time of selecting this option": "a partir do momento em que você selecionar esta opção",
"since they joined": "desde que entraram na sala",
"since they were invited": "desde que foram convidadas/os",
"Someone": "Alguém",
"Sorry, this homeserver is using a login which is not recognised ": "Desculpe, o servidor padrão está usando um login de acesso que não é válido ",
"Start a chat": "Começar uma conversa",
"Start Chat": "Começar conversa",
"Success": "Sucesso",
"tag as": "etiquetar como",
"tag direct chat": "definir como conversa pessoal",
"The default role for new room members is": "O papel padrão para novas/os integrantes da sala é",
"The email address linked to your account must be entered.": "O endereço de email relacionado a sua conta precisa ser informado.",
"their invitations": "seus convites",
"their invitation": "seu convite",
"These are experimental features that may break in unexpected ways. Use with caution": "Estes são recursos experimentais que podem não funcionar corretamente. Use com cuidado.",
"The visibility of existing history will be unchanged": "A visibilidade do histórico atual não será alterada",
"This doesn't appear to be a valid email address": "Este não aparenta ser um endereço de email válido",
"this invitation?": "este convite?",
"This is a preview of this room. Room interactions have been disabled": "Esta é uma pré visualização desta sala. As interações com a sala estão desabilitadas",
"This room is not accessible by remote Matrix servers": "Esta sala não é acessível para servidores Matrix remotos",
"This room's internal ID is": "O ID interno desta sala é",
"times": "vezes",
"To ban users": "Para banir usuárias/os",
"To configure the room": "Para poder configurar a sala",
"To invite users into the room": "Para convidar usuárias/os para esta sala",
"to join the discussion": "para se juntar à conversa",
"To kick users": "Para poder remover pessoas da sala",
"To link to a room it must have": "Para fazer um link para uma sala, ela deve ter",
"To redact messages": "Para poder apagar mensagens",
"To reset your password, enter the email address linked to your account": "Para redefinir sua senha, entre com o email da sua conta",
"To send events of type": "Para enviar eventos do tipo",
"To send messages": "Para enviar mensagens",
"turned on end-to-end encryption (algorithm": "acionou a encriptação ponta-a-ponta (algoritmo",
"Unable to add email address": "Não foi possível adicionar endereço de email",
"Unable to remove contact information": "Não foi possível remover informação de contato",
"Unable to verify email address.": "Não foi possível verificar o endereço de email.",
"Unban": "Desfazer banimento",
"Unencrypted room": "Sala não criptografada",
"unencrypted": "não criptografado",
"unknown device": "dispositivo desconhecido",
"unknown error code": "código de erro desconhecido",
"unknown": "desconhecido",
"Upload avatar": "Enviar icone de perfil de usuário",
"uploaded a file": "enviou um arquivo",
"Upload Files": "Enviar arquivos",
"Upload file": "Enviar arquivo",
"User ID": "Identificador de Usuário",
"User Interface": "Interface de usuário",
"User name": "Nome de usuária/o",
"Users": "Usuários",
"User": "Usuária/o",
"Verification Pending": "Verificação pendente",
"Verification": "Verificação",
"verified": "verificado",
"Video call": "Chamada de vídeo",
"Voice call": "Chamada de voz",
"VoIP conference finished.": "Conferência VoIP encerrada.",
"VoIP conference started.": "Conferência VoIP iniciada.",
"(warning: cannot be disabled again!)": "(atenção: esta operação não poderá ser desfeita depois!)",
"Warning": "Atenção!",
"was banned": "banida/o",
"was invited": "convidada/o",
"was kicked": "retirada/o da sala",
"was unbanned": "des-banida/o",
"was": "foi",
"were": "foram",
"Who can access this room?": "Quem pode acessar esta sala?",
"Who can read history?": "Quem pode ler o histórico da sala?",
"Who would you like to add to this room?": "Quais pessoas você gostaria de adicionar a esta sala?",
"Who would you like to communicate with?": "Com quem você gostaria de se comunicar?",
"withdrawn": "retirado",
"Would you like to": "Você gostaria de",
"You are trying to access": "Você está tentando acessar a sala",
"You do not have permission to post to this room": "Você não tem permissão de postar nesta sala",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "Você foi desconectada/o de todos os dispositivos e portanto não receberá mais notificações no seu celular ou no computador. Para reativar as notificações, entre novamente em cada um dos dispositivos que costuma usar",
"You have no visible notifications": "Voce não possui notificações visíveis",
"you must be a": "você precisa ser",
"Your password has been reset": "Sua senha foi redefinida",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Sua senha foi alterada com sucesso. Você não receberá notificações em outros dispositivos até que você logue novamente por eles",
"You should not yet trust it to secure data": "Você não deve confiar nela ainda para preservar seus dados",
"Sun": "Dom",
"Mon": "Seg",
"Tue": "Ter",
"Wed": "Qua",
"Thu": "Qui",
"Fri": "Sex",
"Sat": "Sáb",
"Jan": "Jan",
"Feb": "Fev",
"Mar": "Mar",
"Apr": "Abr",
"May": "Mai",
"Jun": "Jun",
"Jul": "Jul",
"Aug": "Ago",
"Sep": "Set",
"Oct": "Out",
"Nov": "Nov",
"Dec": "Dez",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s de %(monthName)s às %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s às %(time)s",
"en": "Inglês",
"pt-br": "Português do Brasil",
"de": "Alemão",
"da": "Dinamarquês",
"ru": "Russo",
"%(targetName)s accepted an invitation.": "%(targetName)s aceitou um convite.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s aceitou o convite para %(displayName)s.",
"all room members, from the point they are invited": "todas/os as/os integrantes da sala, a partir do momento em que foram convidadas/os",
"all room members, from the point they joined": "todas/os as/os integrantes da sala, a partir do momento em que entraram na sala",
"%(names)s and %(lastPerson)s are typing": "%(names)s e %(lastPerson)s estão escrevendo",
"%(names)s and one other are typing": "%(names)s e uma outra pessoa estão escrevendo",
"%(names)s and %(count)s others are typing": "%(names)s e %(count)s outras pessoas estão escrevendo",
"%(senderName)s answered the call.": "%(senderName)s atendeu à chamada.",
"anyone": "qualquer pessoa",
"%(senderName)s banned %(targetName)s.": "%(senderName)s removeu %(targetName)s da sala.",
"Call Timeout": "Tempo esgotado. Chamada encerrada",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s mudou seu nome público de %(oldDisplayName)s para %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s alterou sua imagem de perfil.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s alterou o nível de permissões de %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s alterou o nome da sala para %(roomName)s.",
"click to reveal": "clique para ver",
"Conference call failed.": "Chamada de conferência falhou.",
"Conference calling is in development and may not be reliable.": "Chamadas de conferência estão em desenvolvimento e portanto podem não funcionar.",
"Conference calls are not supported in encrypted rooms": "Chamadas de conferência não são possíveis em salas criptografadas",
"Conference calls are not supported in this client": "Chamadas de conferência não são possíveis neste navegador",
"/ddg is not a command": "/ddg não é um comando",
"Drop here %(toAction)s": "Arraste aqui para %(toAction)s",
"Drop here to tag %(section)s": "Arraste aqui para marcar como %(section)s",
"%(senderName)s ended the call.": "%(senderName)s finalizou a chamada.",
"Existing Call": "Chamada em andamento",
"Failed to lookup current room": "Não foi possível buscar na sala atual",
"Failed to send email": "Não foi possível enviar email",
"Failed to send request.": "Não foi possível mandar requisição.",
"Failed to set up conference call": "Não foi possível montar a chamada de conferência",
"Failed to verify email address: make sure you clicked the link in the email": "Não foi possível verificar o endereço de email: verifique se você realmente clicou no link que está no seu email",
"Failure to create room": "Não foi possível criar a sala",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s para %(toPowerLevel)s",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Visitantes não podem criar novas salas. Por favor, registre-se para criar uma sala e iniciar uma conversa.",
"%(senderName)s invited %(targetName)s.": "%(senderName)s convidou %(targetName)s.",
"%(displayName)s is typing": "%(displayName)s está escrevendo",
"%(targetName)s joined the room.": "%(targetName)s entrou na sala.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s removeu %(targetName)s da sala.",
"%(targetName)s left the room.": "%(targetName)s saiu da sala.",
"%(senderName)s made future room history visible to": "%(senderName)s deixou o histórico futuro da sala visível para",
"Missing room_id in request": "Faltou o id da sala na requisição",
"Missing user_id in request": "Faltou o id de usuário na requisição",
"Must be viewing a room": "Tem que estar visualizando uma sala",
"New Composer & Autocomplete": "Nova ferramenta de formatação de mensagens e autocompletar",
"(not supported by this browser)": "(não é compatível com este navegador)",
"olm version": "versão olm",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s fez uma chamada de %(callType)s.",
"Power level must be positive integer.": "O nível de permissões tem que ser um número inteiro e positivo.",
"Press": "Aperte",
"Reason": "Razão",
"%(targetName)s rejected the invitation.": "%(targetName)s recusou o convite.",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removeu o seu nome público (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s removeu sua imagem de perfil.",
"%(senderName)s requested a VoIP conference.": "%(senderName)s está solicitando uma conferência de voz.",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot não tem permissões para enviar notificações a você - por favor, verifique as configurações do seu navegador",
"Riot was not given permission to send notifications - please try again": "Riot não tem permissões para enviar notificações a você - por favor, tente novamente",
"Room %(roomId)s not visible": "A sala %(roomId)s não está visível",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s enviou uma imagem.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s enviou um convite para %(targetDisplayName)s entrar na sala.",
"%(senderName)s set a profile picture.": "%(senderName)s definiu uma imagem de perfil.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s definiu seu nome público para %(displayName)s.",
"tag as %(tagName)s": "marcar como %(tagName)s",
"This email address is already in use": "Este endereço de email já está sendo usado",
"This email address was not found": "Este endereço de email não foi encontrado",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "O arquivo '%(fileName)s' ultrapassa o limite de tamanho que nosso servidor permite enviar",
"The file '%(fileName)s' failed to upload": "Não foi possível enviar o arquivo '%(fileName)s",
"The remote side failed to pick up": "Houve alguma falha que não permitiu a outra pessoa atender à chamada",
"This room is not recognised.": "Esta sala não é reconhecida.",
"These are experimental features that may break in unexpected ways": "Estas são funcionalidades experimentais que podem apresentar falhas",
"This phone number is already in use": "Este número de telefone já está sendo usado",
"to browse the directory": "para navegar na lista pública de salas",
"to demote": "para reduzir prioridade",
"to favourite": "para favoritar",
"to make a room or": "para criar uma sala ou",
"To remove other users' messages": "Para apagar mensagens de outras pessoas",
"to restore": "para restaurar",
"to start a chat with someone": "para iniciar uma conversa com alguém",
"to tag as %(tagName)s": "para marcar como %(tagName)s",
"to tag direct chat": "para marcar a conversa como pessoal",
"To use it, just wait for autocomplete results to load and tab through them.": "Para usar esta funcionalidade, espere o carregamento dos resultados de autocompletar e então escolha entre as opções.",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s ativou criptografia ponta a ponta (algoritmo %(algorithm)s).",
"Unable to restore previous session": "Não foi possível restaurar a sessão anterior",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s desfez o banimento de %(targetName)s.",
"Unable to capture screen": "Não foi possível capturar a imagem da tela",
"Unable to enable Notifications": "Não foi possível ativar as notificações",
"Upload Failed": "O envio falhou",
"Usage": "Uso",
"Use with caution": "Use com cautela",
"VoIP is unsupported": "Chamada de voz não permitida",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s desfez o convite a %(targetName)s.",
"You are already in a call.": "Você já está em uma chamada.",
"You're not in any rooms yet! Press": "Você ainda não está em nenhuma sala! Pressione",
"You are trying to access %(roomName)s.": "Você está tentando acessar a sala %(roomName)s.",
"You cannot place a call with yourself.": "Você não pode iniciar uma chamada.",
"You cannot place VoIP calls in this browser.": "Você não pode fazer chamadas de voz neste navegador.",
"You need to be able to invite users to do that.": "Para fazer isso, você tem que ter permissão para convidar outras pessoas.",
"You need to be logged in.": "Você tem que estar logado.",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "É necessário que você faça login novamente para poder gerar as chaves de criptografia ponta-a-ponta para este dispositivo e então enviar sua chave pública para o servidor. Pedimos desculpas pela inconveniência, é preciso fazer isso apenas única uma vez.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "O seu endereço de email não parece estar associado a uma conta de usuária/o Matrix neste servidor.",
"Set a display name:": "Defina um nome público para você:",
"Upload an avatar:": "Envie uma imagem de perfil para identificar você:",
"This server does not support authentication with a phone number.": "Este servidor não permite a autenticação através de números de telefone.",
"Missing password.": "Faltou a senha.",
"Passwords don't match.": "As senhas não conferem.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "A senha é muito curta (o mínimo é de %(MIN_PASSWORD_LENGTH)s caracteres).",
"This doesn't look like a valid email address.": "Este endereço de email não parece ser válido.",
"This doesn't look like a valid phone number.": "Este número de telefone não parece ser válido.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "Nomes de usuária/o podem conter apenas letras, números, pontos, hífens e linha inferior (_).",
"An unknown error occurred.": "Um erro desconhecido ocorreu.",
"I already have an account": "Eu já tenho uma conta",
"An error occurred: %(error_string)s": "Um erro ocorreu: %(error_string)s",
"Topic": "Tópico",
"Make this room private": "Tornar esta sala privada",
"Share message history with new users": "Compartilhar histórico de mensagens com novas/os usuárias/os",
"Encrypt room": "Criptografar esta sala",
"There are no visible files in this room": "Não há arquivos públicos nesta sala",
"Error changing language": "Erro ao mudar de idioma",
"Riot was unable to find the correct Data for the selected Language.": "Não foi possível encontrar os dados para o idioma selecionado.",
"Connectivity to the server has been lost.": "A conexão com o servidor foi perdida. Verifique sua conexão de internet.",
"Sent messages will be stored until your connection has returned.": "Imagens enviadas ficarão armazenadas até que sua conexão seja reestabelecida.",
"Auto-complete": "Autocompletar",
"Resend all": "Reenviar todas as mensagens",
"cancel all": "cancelar todas",
"now. You can also select individual messages to resend or cancel.": "agora. Você também pode escolher mensagens individuais e definir se vai reenviar ou cancelar o envio.",
"Active call": "Chamada ativa",
"af": "Afrikaans",
"ar-ae": "Árabe (E.A.U.)",
"ar-bh": "Árabe (Bahrain)",
"ar-dz": "Árabe (Argélia)",
"Sunday": "Domingo",
"Monday": "Segunda-feira",
"ar-eg": "Árabe (Egito)",
"ar-tn": "Árabe (Tunísia)",
"be": "Bielorrusso",
"bg": "Búlgaro",
"ca": "Catalão",
"cs": "Checo",
"el": "Grego",
"en-au": "Inglês (Austrália)",
"en-ca": "Inglês (Canadá)",
"en-gb": "Inglês (Reino Unido)",
"en-ie": "Inglês (Irlanda)",
"en-nz": "Inglês (Nova Zelândia)",
"en-us": "Inglês (Estados Unidos)",
"es-ar": "Espanhol (Argentina)",
"es-py": "Espanhol (Paraguai)",
"es": "Espanhol (Espanha)",
"et": "Estônia",
"fa": "Farsi",
"fi": "Finlandês",
"fr-be": "Francês (Bélgica)",
"fr-ca": "Francês (Canadá)",
"fr-ch": "Francês (Suíça)",
"fr": "Francês",
"ga": "Irlandês",
"he": "Hebreu",
"hi": "Hindu",
"hr": "Croácia",
"hu": "Hungria",
"id": "Indonésio",
"is": "Islandês",
"it": "Italiano",
"ja": "Japonês",
"ji": "Ídiche",
"lt": "Lituânia",
"lv": "Letão",
"ms": "Malaio",
"mt": "Maltês",
"nl-be": "Holandês (Bélgica)",
"nl": "Holandês",
"no": "Norueguês",
"pl": "Polonês",
"pt": "Português (Portugal)",
"rm": "Romanche",
"ro": "Romeno",
"sk": "Eslovaco",
"sl": "Esloveno",
"sq": "Albanês",
"sr": "Sérvio",
"sv": "Suécia",
"th": "Tailandês",
"tn": "Tsuana",
"tr": "Turquia",
"ts": "Tsonga",
"uk": "Ucraniano",
"ur": "Urdu",
"vi": "Vietnamita",
"xh": "Xhosa",
"zu": "Zulu",
"Failed to forget room %(errCode)s": "Falha ao esquecer a sala %(errCode)s",
"Failed to join the room": "Falha ao entrar na sala",
"Tuesday": "Terça-feira",
"Wednesday": "Quarta-feira",
"Thursday": "Quinta-feira",
"Friday": "Sexta-feira",
"Saturday": "Sábado",
"ar-iq": "Árabe (Iraque)",
"ar-jo": "Árabe (Jordânia)",
"ar-kw": "Árabe (Kuwait)",
"ar-lb": "Árabe (Líbano)",
"ar-ly": "Árabe (Líbia)",
"ar-ma": "Árabe (Marrocos)",
"ar-om": "Árabe (Omã)",
"ar-qa": "Árabe (Catar)",
"ar-sa": "Árabe (Arábia Saudita)",
"ar-sy": "Árabe (Síria)",
"ar-ye": "Árabe (Iémen)",
"de-at": "Alemão (Áustria)",
"de-ch": "Alemão (Suíça)",
"de-li": "Alemão (Liechtenstein)",
"de-lu": "Alemão (Luxemburgo)",
"en-bz": "Inglês (Belize)",
"en-jm": "Inglês (Jamaica)",
"en-tt": "Inglês (Trindade)",
"en-za": "English (África do Sul)",
"es-bo": "Espanhol (Bolívia)",
"es-cl": "Espanhol (Chile)",
"es-co": "Espanhol (Colômbia)",
"es-cr": "Espanhol (Costa Rica)",
"es-do": "Espanhol (República Dominicana)",
"es-ec": "Espanhol (Equador)",
"es-gt": "Espanhol (Guatemala)",
"es-hn": "Espanhol (Honduras)",
"es-mx": "Espanhol (México)",
"es-ni": "Espanhol (Nicarágua)",
"es-pa": "Espanhol (Panamá)",
"%(oneUser)schanged their avatar": "%(oneUser)salterou sua imagem pública",
"es-pe": "Espanhol (Peru)",
"es-pr": "Espanhol (Porto Rico)",
"es-sv": "Espanhol (El Salvador)",
"es-uy": "Espanhol (Uruguai)",
"es-ve": "Espanhol (Venezuela)",
"eu": "Basco (Basco)",
"fr-lu": "Francês (Luxemburgo)",
"gd": "Galês (Escócia)",
"it-ch": "Italiano (Suíça)",
"ko": "Coreano",
"mk": "Macedônio (República da Macedônia)",
"ro-mo": "Romano (Moldávia)",
"ru-mo": "Russo (Moldávia)",
"sb": "Sorábio",
"sv-fi": "Sueco (Finlândia)",
"zh-cn": "Chinês (República Popular da China)",
"zh-hk": "Chinês (Hong Kong SAR)",
"zh-sg": "Chinês (Singapura)",
"zh-tw": "Chinês (Taiwan)",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Uma mensagem de texto foi enviada para +%(msisdn)s. Gentileza entrar com o código de verificação que contém",
"%(items)s and %(remaining)s others": "%(items)s e %(remaining)s outros",
"%(items)s and one other": "%(items)s e um outro",
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
"and %(overflowCount)s others...": "e %(overflowCount)s outros...",
"and one other...": "e um outro...",
"Are you sure?": "Você tem certeza?",
"Attachment": "Anexo",
"Autoplay GIFs and videos": "Reproduzir automaticamente GIFs e videos",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s de %(monthName)s de %(fullYear)s às %(time)s",
"fo": "Feroês",
"sx": "Sutu",
"sz": "Sami (Lappish)",
"ve": "Venda",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Não consigo conectar ao servidor padrão - favor checar sua conexão à internet e verificar se o certificado SSL do seu <a>servidor padrão</a> é confiável.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Não consigo conectar ao servidor padrão através de HTTP quando uma URL HTTPS está na barra de endereços do seu navegador. Use HTTPS ou então <a>habilite scripts não seguros no seu navegador</a>.",
"Change Password": "Alterar senha",
"changing room on a RoomView is not supported": "mudar a sala em uma 'RoomView' não é permitido",
"Click to mute audio": "Clique para colocar o áudio no mudo",
"Click to mute video": "Clique para desabilitar imagens de vídeo",
"Click to unmute video": "Clique para voltar a mostrar imagens de vídeo",
"Click to unmute audio": "Clique para retirar áudio do mudo",
"Command error": "Erro de comando",
"Decrypt %(text)s": "Descriptografar %(text)s",
"Delete": "Apagar",
"Devices": "Dispositivos",
"Direct chats": "Conversas pessoais",
"Disinvite": "Desconvidar",
"Don't send typing notifications": "Não enviar notificação de estar digitando",
"Download %(text)s": "Baixar %(text)s",
"Enable encryption": "Habilitar criptografia",
"Enter Code": "Entre com o código",
"Failed to ban user": "Não foi possível banir o/a usuário/a",
"Failed to change power level": "Não foi possível mudar o nível de permissões",
"Failed to delete device": "Não foi possível remover o dispositivo",
"Failed to join room": "Não foi possível ingressar na sala",
"Failed to kick": "Não foi possível remover usuária/o",
"Failed to load timeline position": "Não foi possível carregar a posição na linha do tempo",
"Failed to mute user": "Não foi possível remover notificações da/do usuária/o",
"Failed to reject invite": "Não foi possível rejeitar o convite",
"Failed to save settings": "Não foi possível salvar as configurações",
"Failed to set display name": "Houve falha ao definir o nome público",
"Failed to toggle moderator status": "Houve falha ao alterar o status de moderador/a",
"Fill screen": "Tela cheia",
"Hide read receipts": "Ocultar recebimentos de leitura",
"Hide Text Formatting Toolbar": "Ocultar a barra de formatação de texto",
"Incorrect verification code": "Código de verificação incorreto",
"Invalid alias format": "Formato de alias é inválido",
"Invalid address format": "Formato de endereço é inválido",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' não é um formato válido para um endereço",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' não é um formato válido para um alias",
"Join Room": "Ingressar na sala",
"Jump to first unread message.": "Ir diretamente para a primeira das mensagens não lidas.",
"Kick": "Remover",
"Level": "Nível",
"Local addresses for this room:": "Endereço local desta sala:",
"Markdown is disabled": "A formatação 'Markdown' está desabilitada",
"Markdown is enabled": "A formatação 'Markdown' está habilitada",
"Message not sent due to unknown devices being present": "A mensagem não foi enviada por causa da presença de dispositivos desconhecidos",
"Never send encrypted messages to unverified devices in this room": "Nunca envie mensagens criptografadas para dispositivos não verificados nesta sala",
"New address (e.g. #foo:%(localDomain)s)": "Novo endereço (p.ex: #algo:%(localDomain)s)",
"not set": "não definido",
"not specified": "não especificado",
"No devices with registered encryption keys": "Não há dispositivos com chaves de criptografia registradas",
"No more results": "Não há mais resultados",
"No results": "Sem resultados",
"OK": "Ok",
"Revoke Moderator": "Retirar status de moderador",
"Search": "Pesquisar",
"Search failed": "Busca falhou",
"Server error": "Erro no servidor",
"Server may be unavailable, overloaded, or search timed out :(": "O servidor pode estar indisponível, sobrecarregado, ou a busca ultrapassou o tempo limite :(",
"Server may be unavailable, overloaded, or the file too big": "O servidor pode estar indisponível, sobrecarregado, ou o arquivo é muito grande",
"Server unavailable, overloaded, or something else went wrong.": "O servidor pode estar indisponível, sobrecarregado, ou alguma outra coisa não funcionou.",
"Some of your messages have not been sent.": "Algumas das suas mensagens não foram enviadas.",
"Submit": "Enviar",
"The main address for this room is": "O endereço principal desta sala é",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Esta ação não pode ser realizada por um/a usuário/a visitante. Por favor, registre-se para poder fazer isso.",
"%(actionVerb)s this person?": "%(actionVerb)s esta pessoa?",
"This room has no local addresses": "Esta sala não tem endereços locais",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas parece que você não tem permissões para ver a mensagem em questão.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas não o encontrei.",
"Turn Markdown off": "Desabilitar a formatação 'Markdown'",
"Turn Markdown on": "Habilitar a marcação 'Markdown'",
"Unable to load device list": "Não foi possível carregar a lista de dispositivos",
"Unknown command": "Comando desconhecido",
"Unknown room %(roomId)s": "A sala %(roomId)s é desconhecida",
"You have been invited to join this room by %(inviterName)s": "Você foi convidada/o por %(inviterName)s a ingressar nesta sala",
"You seem to be in a call, are you sure you want to quit?": "Parece que você está em uma chamada. Tem certeza que quer sair?",
"You seem to be uploading files, are you sure you want to quit?": "Parece que você está enviando arquivos. Tem certeza que quer sair?",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Você não poderá desfazer esta mudança, pois estará dando a este(a) usuário(a) o mesmo nível de permissões que você.",
"Make Moderator": "Tornar moderador(a)",
"Room": "Sala",
"(~%(searchCount)s results)": "(±%(searchCount)s resultados)",
"Cancel": "Cancelar",
"bold": "negrito",
"italic": "itálico",
"strike": "tachado",
"underline": "sublinhado",
"code": "código de programação",
"quote": "citação",
"bullet": "marcador de lista",
"numbullet": "marcador de numeração",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)singressaram %(repeats)s vezes",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)singressou %(repeats)s vezes",
"%(severalUsers)sjoined": "%(severalUsers)singressaram",
"%(oneUser)sjoined": "%(oneUser)singressou",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)ssaíram %(repeats)s vezes",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)ssaiu %(repeats)s vezes",
"%(severalUsers)sleft": "%(severalUsers)ssaíram",
"%(oneUser)sleft": "%(oneUser)ssaiu",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)singressaram e saíram %(repeats)s vezes",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)singressou e saiu %(repeats)s vezes",
"%(severalUsers)sjoined and left": "%(severalUsers)singressaram e saíram",
"%(oneUser)sjoined and left": "%(oneUser)singressou e saiu",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)ssaíram e entraram novamente %(repeats)s vezes",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)ssaiu e entrou novamente %(repeats)s vezes",
"%(severalUsers)sleft and rejoined": "%(severalUsers)ssaíram e entraram novamente",
"%(oneUser)sleft and rejoined": "%(oneUser)ssaiu e entrou novamente",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejeitaram seus convites %(repeats)s vezes",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejeitou seu convite %(repeats)s vezes",
"%(severalUsers)srejected their invitations": "%(severalUsers)srejeitaram seus convites",
"%(oneUser)srejected their invitation": "%(oneUser)srejeitou seu convite",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)stiveram seus convites desfeitos %(repeats)s vezes",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)steve seu convite desfeito %(repeats)s vezes",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)stiveram seus convites desfeitos",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)steve seu convite desfeito",
"were invited %(repeats)s times": "foram convidadas(os) %(repeats)s vezes",
"was invited %(repeats)s times": "foi convidada(o) %(repeats)s vezes",
"were invited": "foram convidadas(os)",
"were banned %(repeats)s times": "foram banidas(os) %(repeats)s vezes",
"was banned %(repeats)s times": "foi banida(o) %(repeats)s vezes",
"were banned": "foram banidas(os)",
"were unbanned %(repeats)s times": "tiveram banimento desfeito %(repeats)s vezes",
"was unbanned %(repeats)s times": "teve banimento desfeito %(repeats)s vezes",
"were unbanned": "tiveram banimento desfeito",
"were kicked %(repeats)s times": "foram expulsas(os) %(repeats)s vezes",
"was kicked %(repeats)s times": "foi expulsa(o) %(repeats)s vezes",
"were kicked": "foram expulsas(os)",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)salteraram seu nome %(repeats)s vezes",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)salterou seu nome %(repeats)s vezes",
"%(severalUsers)schanged their name": "%(severalUsers)salteraram seus nomes",
"%(oneUser)schanged their name": "%(oneUser)salterou seu nome",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)salteraram sua imagem pública %(repeats)s vezes",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)salterou sua imagem pública %(repeats)s vezes",
"%(severalUsers)schanged their avatar": "%(severalUsers)salteraram sua imagem pública",
"Ban": "Banir",
"A registered account is required for this action": "Uma conta registrada é necessária para esta ação",
"Access Token:": "Token de acesso:",
"Always show message timestamps": "Sempre mostrar as datas das mensagens",
"Authentication": "Autenticação",
"An error has occurred.": "Ocorreu um erro.",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Alterar a senha irá atualmente apagar todas as chaves de criptografia ponta-a-ponta em todos os dispositivos, fazendo com que o histórico da conversa fique ilegível, a não ser que você exporte antes as chaves de sala e então as reimporte depois. No futuro, isso vai melhorar.",
"Email": "Email",
"Email address": "Endereço de email",
"Error decrypting attachment": "Erro ao descriptografar o anexo",
"Interface Language": "Idioma da interface",
"Invalid file%(extra)s": "Arquivo inválido %(extra)s",
"Logged in as:": "Logado como:",
"matrix-react-sdk version:": "versão do matrix-react-sdk:",
"Mute": "Silenciar",
"olm version:": "versão do olm:",
"Operation failed": "A operação falhou",
"Registration required": "Registro obrigatório",
"Remove %(threePid)s?": "Remover %(threePid)s?",
"Report it": "Reportar",
"riot-web version:": "versão do riot-web:",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Mostrar os horários em formato de 12h (p.ex: 2:30pm)",
"Unmute": "Tirar do mudo",
"Warning!": "Atenção!",
"You need to enter a user name.": "Você precisa inserir um nome de usuária(o).",
"Please select the destination room for this message": "Por favor, escolha a sala para onde quer encaminhar esta mensagem",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s apagou o nome da sala.",
"Analytics": "Análise",
"Opt out of analytics": "Sair da ferramenta de análise",
"Options": "Opções",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot coleta informações anônimas de uso para nos permitir melhorar o sistema.",
"Passphrases must match": "As senhas têm que ser iguais",
"Passphrase must not be empty": "A senha não pode estar vazia",
"Export room keys": "Exportar chaves de sala",
"Enter passphrase": "Entre com a senha",
"Confirm passphrase": "Confirme a senha",
"Import room keys": "Importar chaves de sala",
"File to import": "Arquivo para importar",
"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.": "Este processo permite que você exporte as chaves para mensagens que você recebeu em salas criptografadas para um arquivo local. Você poderá então importar o arquivo para outro cliente Matrix no futuro, de modo que este cliente também poderá descriptografar suas mensagens.",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "O arquivo exportado será protegido com uma senha. Você deverá inserir a senha aqui para poder descriptografar o arquivo futuramente.",
"You must join the room to see its files": "Você precisa ingressar na sala para ver seus arquivos",
"Reject all %(invitedRooms)s invites": "Rejeitar todos os %(invitedRooms)s convites",
"Start new chat": "Iniciar nova conversa",
"Guest users can't invite users. Please register.": "Visitantes não podem convidar usuárias(os) registradas(os). Favor registrar.",
"Failed to invite": "Falha ao enviar o convite",
"Failed to invite user": "Falha ao convidar a(o) usuária(o)",
"Failed to invite the following users to the %(roomName)s room:": "Falha ao convidar as(os) seguintes usuárias(os) para a sala %(roomName)s:",
"Confirm Removal": "Confirmar a remoção",
"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.": "Você tem certeza que quer apagar este evento? Note que se você apaga o nome de uma sala ou uma mudança de tópico, esta ação não poderá ser desfeita.",
"Unknown error": "Erro desconhecido",
"Incorrect password": "Senha incorreta",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Isso tornará a sua conta permanentemente inusável. Você não será capaz de registrar novamente o mesmo ID de usuário.",
"This action is irreversible.": "Esta ação é irreversível.",
"To continue, please enter your password.": "Para continuar, por favor insira a sua senha.",
"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:": "Para verificar que este dispositivo é confiável, por favor entre em contato com a(o) sua(seu) dona(o) usando outros meios, como por exemplo pessoalmente ou por uma chamada telefônica, e pergunte a esta pessoa se a chave que ela está vendo nas suas configurações de usuário para este dispositivo é igual a esta:",
"Device name": "Nome do dispositivo",
"Device key": "Chave do dispositivo",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Se a chave for a mesma, clique no botão \"verificar\" abaixo. Se não for a mesma, então alguma outra pessoa está interceptando este dispositivo e você provavelmente vai querer clicar no botão \"colocar na lista negra\".",
"In future this verification process will be more sophisticated.": "No futuro, este processo de verificação será mais sofisticado.",
"Verify device": "Verificar o dispositivo",
"I verify that the keys match": "Eu confirmo que as chaves são iguais",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Encontramos um erro tentando restaurar sua sessão anterior. Se você continuar, terá que fazer login novamente, e o histórico da conversa criptografada se tornará ilegível.",
"Unable to restore session": "Não foi possível restaurar a sessão",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Se você já usou antes uma versão mais recente do Riot, a sua sessão pode ser incompatível com esta versão. Feche esta janela e tente abrir com a versão mais recente.",
"Continue anyway": "Continuar de qualquer maneira",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "O seu nome público é como você aparecerá para as outras pessoas quando conversar nas salas. Qual nome público você deseja ter?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Você está atualmente bloqueando dispositivos não verificados. Para enviar mensagens para estes dispositivos, você necessita antes verificá-los.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Nós recomendamos que você passe pelo processo de verificação para cada dispositivo para confirmar que eles pertencem às pessoas que efetivamente são suas donas, mas você pode reenviar a mensagem sem verificar isso, se assim o desejar.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contém dispositivos que você não viu antes.",
"Unknown devices": "Dispositivos desconhecidos",
"Unknown Address": "Endereço desconhecido",
"Unblacklist": "Tirar da lista de bloqueados",
"Blacklist": "Colocar na lista de bloqueados",
"Unverify": "Des-verificar",
"Verify...": "Verificar...",
"ex. @bob:example.com": "p.ex: @joao:exemplo.com",
"Add User": "Adicionar usuária(o)",
"This Home Server would like to make sure you are not a robot": "Este Servidor de Base gostaria de confirmar que você não é um robô",
"Sign in with CAS": "Assinar com CAS",
"Custom Server Options": "Opções para Servidor Personalizado",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Você pode usar as opções de servidor personalizado para entrar em outros servidores Matrix ao especificar uma URL de um Servidor de Base diferente.",
"This allows you to use this app with an existing Matrix account on a different home server.": "Isso permite que você use este aplicativo com uma conta Matrix existente em um servidor de base diferente.",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Você também pode definir um servidor de identidades personalizado, mas isso vai impedir a sua interação com outros usuários a partir do endereço de email.",
"Dismiss": "Descartar",
"Please check your email to continue registration.": "Por favor, verifique o seu e-mail para continuar o processo de registro.",
"Token incorrect": "Token incorreto",
"A text message has been sent to": "Uma mensagem de texto foi enviada para",
"Please enter the code it contains:": "Por favor, entre com o código que está na mensagem:",
"powered by Matrix": "rodando a partir do Matrix",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Se não especificar um endereço de e-mail, você não poderá redefinir sua senha. Tem certeza?",
"You are registering with %(SelectedTeamName)s": "Você está se registrando com %(SelectedTeamName)s",
"Default server": "Servidor padrão",
"Custom server": "Servidor personalizado",
"Home server URL": "URL do servidor de base",
"Identity server URL": "URL do servidor de identidades",
"What does this mean?": "O que isso significa?",
"Error decrypting audio": "Erro ao descriptografar o áudio",
"Error decrypting image": "Erro ao descriptografar a imagem",
"Image '%(Body)s' cannot be displayed.": "A imagem '%(Body)s' não pode ser exibida.",
"This image cannot be displayed.": "Esta imagem não pode ser exibida.",
"Error decrypting video": "Erro ao descriptografar o vídeo",
"Add an Integration": "Adicionar uma integração",
"Removed or unknown message type": "Mensagem removida ou de tipo desconhecido",
"Disable URL previews by default for participants in this room": "Desabilitar as pré-visualizações de links por padrão para participantes desta sala",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "As pré-visualizações estão %(globalDisableUrlPreview)s por padrão para integrantes desta sala.",
"URL Previews": "Pré-visualização de links",
"Enable URL previews for this room (affects only you)": "Habilitar pré-visualizações de links para esta sala (afeta somente a você)",
"Drop file here to upload": "Arraste um arquivo aqui para enviar",
" (unsupported)": " (não suportado)",
"Ongoing conference call%(supportedText)s. %(joinText)s": "Conferência%(supportedText)s em andamento. %(joinText)s",
"Online": "Online",
"Idle": "Ocioso",
"Offline": "Offline",
"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.": "O arquivo exportado irá permitir a qualquer pessoa que o acesse a descriptografar qualquer uma das mensagens criptografadas que você veja, portanto seja bastante cuidadosa(o) em manter este arquivo seguro. Para deixar este arquivo mais protegido, recomendamos que você insira uma senha abaixo, que será usada para criptografar o arquivo. Só será possível importar os dados usando exatamente a mesma senha.",
"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.": "Este processo faz com que você possa importar as chaves de criptografia que tinha previamente exportado de outro cliente Matrix. Você poderá então descriptografar todas as mensagens que o outro cliente pôde criptografar.",
"You are about to be taken to a third-party site so you can authenticate your account for use with {integrationsUrl}. Do you wish to continue?": "",
"Start automatically after system login": "Iniciar automaticamente ao iniciar o sistema",
"Desktop specific": "Específico para o app de computadores desktop",
"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?": "Você será levado agora a um site de terceiros para poder autenticar a sua conta para uso com o serviço %(integrationsUrl)s. Você quer continuar?",
"Disable URL previews for this room (affects only you)": "Desabilitar as pré-visualizações de sites para esta sala (afeta apenas a você)",
"Device already verified!": "Dispositivo já verificado!",
"disabled": "desabilitado",
"enabled": "habilitado",
"Export": "Exportar",
"Failed to register as guest:": "Falha ao se registrar como visitante:",
"Guest access is disabled on this Home Server.": "O acesso para visitantes está desabilitado neste Servidor de Base.",
"Import": "Importar",
"Incorrect username and/or password.": "Nome de usuária(o) e/ou senha incorreto.",
"Invited": "Convidada(o)",
"Results from DuckDuckGo": "Resultados de DuckDuckGo",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "A chave de assinatura que você forneceu é a mesma que a chave de assinatura que você recebeu do dispositivo %(deviceId)s de %(userId)s . O dispositivo foi portanto marcado como verificado.",
"This Home Server does not support login using email address.": "Este Servidor de Base não permite login usando endereço de e-mail.",
"There was a problem logging in.": "Houve um problema ao fazer login.",
"Unknown (user, device) pair:": "Par usuária(o)-dispositivo desconhecido:",
"Unrecognised command:": "Comando não reconhecido:",
"Unrecognised room alias:": "Apelido de sala não reconhecido:",
"Use compact timeline layout": "Usar o layout de linha do tempo compacta",
"Verified key": "Chave verificada",
"WARNING: Device already verified, but keys do NOT MATCH!": "ATENÇÃO: O dispositivo já foi verificado, mas as chaves NÃO SÃO IGUAIS!",
"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!": "ATENÇÃO: VERIFICAÇÃO DE CHAVE FALHOU! A chave de assinatura para a(o) usuária(o) %(userId)s e dispositivo %(deviceId)s é \"%(fprint)s\", que não é igual à chave fornecida \"%(fingerprint)s\". Isso pode significar que suas comunicações estão sendo interceptadas!",
"Set a Display Name": "Definir seu nome público",
"for %(amount)ss": "por %(amount)ss",
"for %(amount)sm": "por %(amount)sm",
"for %(amount)sh": "por %(amount)sh",
"for %(amount)sd": "por %(amount)sd",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a imagem da sala.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName alterou a imagem da sala para <img/>",
"Missing Media Permissions, click here to request.": "Faltam permissões para uso de mídia no seu computador. Clique aqui para solicitá-las.",
"No Microphones detected": "Não foi detectado nenhum microfone",
"No Webcams detected": "Não foi detectada nenhuma Webcam",
"No media permissions": "Não há permissões de uso de vídeo/áudio no seu navegador",
"You may need to manually permit Riot to access your microphone/webcam": "Você talvez precise autorizar manualmente que o Riot acesse seu microfone e webcam",
"Default Device": "Dispositivo padrão",
"Microphone": "Microfone",
"Camera": "Câmera de vídeo",
"Add a topic": "Adicionar um tópico",
"VoIP": "VoIP",
"Anyone": "Qualquer pessoa",
"Are you sure you want to leave the room '%(roomName)s'?": "Você tem certeza que deseja sair da sala '%(roomName)s'?",
"Custom level": "Nível personalizado",
"(default: %(userName)s)": "(padrão: %(userName)s)",
"Device ID:": "ID do dispositivo:",
"device id: ": "id do dispositivo: ",
"Device key:": "Chave do dispositivo:",
"Email address (optional)": "Endereço de e-mail (opcional)",
"List this room in %(domain)s's room directory?": "Deseja listar esta sala na lista pública de salas de %(domain)s?",
"Mobile phone number (optional)": "Número de telefone celular (opcional)",
"Password:": "Senha:",
"Register": "Registre-se",
"Remote addresses for this room:": "Endereços remotos para esta sala:",
"Save": "Salvar",
"Setting a user name will create a fresh account": "Definir um nome de usuária(o) vai criar uma conta nova",
"Tagged as: ": "Marcado como: ",
"You have <a>disabled</a> URL previews by default.": "Você <a>desabilitou</a> pré-visualizações de links por padrão.",
"You have <a>enabled</a> URL previews by default.": "Você <a>habilitou</a> pré-visualizações de links por padrão.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Você inseriu um contato inválido. Tente usar o ID Matrix ou endereço de e-mail da pessoa que está buscando."
}

880
src/i18n/strings/pt_BR.json Normal file
View File

@@ -0,0 +1,880 @@
{
"accept": "aceitar",
"accepted an invitation": "aceitou um convite",
"accepted the invitation for": "aceitou o convite para",
"Account": "Conta",
"Add email address": "Adicionar endereço de email",
"Add phone number": "Adicionar número de telefone",
"Admin": "Administrador/a",
"Advanced": "Avançado",
"Algorithm": "Algoritmo",
"all room members, from the point they are invited.": "todos os membros da sala, a partir de quando foram convidados",
"all room members, from the point they joined.": "todos os membros da sala, a partir de quando entraram",
"all room members": "todas as pessoas da sala",
"an address": "um endereço",
"and": "e",
"An email has been sent to": "Um email foi enviado para",
"New passwords don't match": "As novas senhas não conferem",
"A new password must be entered.": "Uma nova senha precisa ser informada.",
"answered the call.": "respondeu à chamada.",
"Anyone who knows the room's link, apart from guests": "Qualquer pessoa que tenha o link da sala, exceto visitantes",
"Anyone who knows the room's link, including guests": "Qualquer pessoa que tenha o link da sala, incluindo visitantes",
"Are you sure you want to leave the room?": "Você tem certeza que deseja sair da sala?",
"Are you sure you want to reject the invitation?": "Você tem certeza que deseja rejeitar este convite?",
"Are you sure you want to upload the following files?": "Você tem certeza que deseja enviar os seguintes arquivos?",
"banned": "baniu",
"Banned users": "Usuárias/os banidas/os",
"Bans user with given id": "Banir usuários com o identificador informado",
"Blacklisted": "Bloqueado",
"Bug Report": "Repotar problemas de funcionamento",
"Bulk Options": "Opcões de Batelada",
"Can't load user settings": "Não é possível carregar configurações de usuário",
"changed avatar": "mudou sua imagem de perfil (avatar)",
"changed name": "mudou seu nome",
"changed their display name from": "mudou seu nome para",
"changed their profile picture": "alterou sua foto de perfil",
"changed the power level of": "mudou o nível de permissões de",
"changed the room name to": "mudou o nome da sala para",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s mudou o tópico para \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "As mudanças sobre quem pode ler o histórico da sala só serão aplicadas às mensagens futuras nesta sala",
"Changes your display nickname": "Troca o seu apelido",
"Claimed Ed25519 fingerprint key": "Chave reivindicada da Impressão Digital Ed25519",
"Clear Cache and Reload": "Limpar Memória Cache e Recarregar",
"Clear Cache": "Limpar Memória Cache",
"Click here": "Clique aqui",
"Click here to fix": "Clique aqui para resolver isso",
"Commands": "Comandos",
"Confirm password": "Confirme a nova senha",
"Confirm your new password": "Confirme a nova senha",
"Continue": "Continuar",
"Could not connect to the integration server": "Não foi possível conectar ao servidor de integrações",
"Create an account": "Criar uma conta",
"Create a new account": "Criar uma conta",
"Create Room": "Criar Sala",
"Cryptography": "Criptografia",
"Current password": "Senha atual",
"Curve25519 identity key": "Chave de Indetificação Curve25519",
"Deactivate Account": "Desativar conta",
"Deactivate my account": "Desativar minha conta",
"decline": "rejeitar",
"Decryption error": "Erro de descriptografia",
"Default": "Padrão",
"demote": "reduzir prioridade",
"Deops user with given id": "Retirar função de moderador do usuário com o identificador informado",
"Device ID": "Identificador do dispositivo",
"Devices will not yet be able to decrypt history from before they joined the room": "Os dispositivos não serão ainda capazes de descriptografar o histórico anterior à sua entrada na sala",
"Direct Chat": "Conversa pessoal",
"Disable inline URL previews by default": "Desabilitar visualizações prévias por padrão",
"Display name": "Nome",
"Displays action": "Visualizar atividades",
"Ed25519 fingerprint": "Impressão Digital Ed25519",
"Email Address": "endereço de email",
"Email, name or matrix ID": "Email, nome ou ID matrix",
"Emoji": "Emoji",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Mensagens criptografadas não serão visíveis em clientes que ainda não implementaram criptografia",
"Encrypted room": "Sala criptografada",
"Encryption is enabled in this room": "Criptografia está habilitada nesta sala",
"Encryption is not enabled in this room": "Criptografia não está habilitada nesta sala",
"ended the call.": "chamada encerrada.",
"End-to-end encryption information": "Informação criptografada ponta-a-ponta",
"End-to-end encryption is in beta and may not be reliable": "A criptografia ponta a ponta está em estágio beta e não deve ser totalmente confiável",
"Error": "Erro",
"Event information": "Informação do evento",
"Export E2E room keys": "Exportar chaves ponta-a-ponta da sala",
"Failed to change password. Is your password correct?": "Não foi possível modificar a senha. A senha informada está correta?",
"Failed to forget room": "Não foi possível esquecer a sala",
"Failed to leave room": "Falha ao tentar deixar a sala",
"Failed to reject invitation": "Falha ao tentar rejeitar convite",
"Failed to send email: ": "Falha ao tentar enviar email",
"Failed to set avatar.": "Falha ao tentar definir foto do perfil.",
"Failed to unban": "Não foi possível desfazer o banimento",
"Failed to upload file": "Falha ao enviar o arquivo",
"favourite": "favoritar",
"Favourite": "Favorito",
"Favourites": "Favoritos",
"Filter room members": "Filtrar integrantes da sala",
"Forget room": "Esquecer sala",
"Forgot your password?": "Esqueceu sua senha?",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Por segurança, deslogar irá remover qualquer chave de criptografia ponta-a-ponta deste navegador. Caso deseje descriptografar o histórico das suas conversas E2E em sessões Riot futuras, por favor exporte as chaves da sala para sua garantia.",
"For security, this session has been signed out. Please sign in again.": "Por questões de segurança, esta sessão foi encerrada. Por gentileza conecte-se novamente.",
"Found a bug?": "Encontrou um problema de funcionamento do sistema?",
"Guests cannot join this room even if explicitly invited.": "Visitantes não podem entrar nesta sala, mesmo se forem explicitamente convidadas/os.",
"Guests can't set avatars. Please register.": "Convidados não podem definir uma foto do perfil. Por favor, registre-se.",
"Guests can't use labs features. Please register.": "Convidados não podem usar as funcionalidades de laboratório (lab), por gentileza se registre.",
"Guest users can't upload files. Please register to upload.": "Usuários não podem fazer envio de arquivos. Por favor se cadastre para enviar arquivos.",
"had": "teve",
"Hangup": "Desligar",
"Historical": "Histórico",
"Homeserver is": "Servidor padrão é",
"Identity Server is": "O servidor de identificação é",
"I have verified my email address": "Eu verifiquei o meu endereço de email",
"Import E2E room keys": "Importar chave de criptografia ponta-a-ponta (E2E) da sala",
"Invalid Email Address": "Endereço de email inválido",
"invited": "convidou",
"Invite new room members": "Convidar novas pessoas para ingressar na sala",
"Invites": "Convidar",
"Invites user with given id to current room": "Convidar usuários com um dado identificador para esta sala",
"is a": "é um(a)",
"Sign in with": "Quero entrar",
"joined and left": "entrou e saiu",
"joined": "entrou",
"joined the room": "entrou na sala",
"Joins room with given alias": "Entra na sala com o nome informado",
"Kicks user with given id": "Remove usuário com o identificador informado",
"Labs": "Laboratório",
"Leave room": "Sair da sala",
"left and rejoined": "saiu e entrou novamente",
"left": "saiu",
"left the room": "saiu da sala",
"Logged in as": "Logado como",
"Login as guest": "Entrar como visitante",
"Logout": "Sair",
"Low priority": "Baixa prioridade",
"made future room history visible to": "deixou o histórico futuro da sala visível para",
"Manage Integrations": "Gerenciar integrações",
"Members only": "Apenas integrantes da sala",
"Mobile phone number": "Telefone celular",
"Moderator": "Moderador/a",
"my Matrix ID": "com meu ID do Matrix",
"Name": "Nome",
"Never send encrypted messages to unverified devices from this device": "Nunca envie mensagens criptografada para um dispositivo não verificado a partir deste dispositivo",
"Never send encrypted messages to unverified devices in this room from this device": "Nunca envie mensagens criptografadas para dispositivos não verificados nesta sala a partir deste dispositivo",
"New password": "Nova senha",
"New passwords must match each other.": "As novas senhas informadas precisam ser idênticas.",
"none": "nenhum",
"Notifications": "Notificações",
" (not supported by this browser)": "não suportado por este navegador",
"<not supported>": "<não suportado>",
"NOT verified": "NÃO verificado",
"No users have specific privileges in this room": "Nenhum/a usuário/a possui privilégios específicos nesta sala",
"olm version: ": "Versão do olm: ",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Assim que a criptografia é ativada para uma sala, ela não poderá ser desativada novamente (ainda)",
"Once you&#39;ve followed the link it contains, click below": "Quando você tiver clicado no link que está no email, clique o botão abaixo",
"Only people who have been invited": "Apenas pessoas que tenham sido convidadas",
"or": "ou",
"other": "outro",
"others": "outros",
"Password": "Senha",
"Passwords can't be empty": "As senhas não podem estar em branco",
"People": "Pessoas",
"Permissions": "Permissões",
"Phone": "Telefone",
"placed a": "iniciou uma",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Por favor verifique seu email e clique no link enviado. Quando finalizar este processo, clique para continuar.",
"Please Register": "Por favor, cadastre-se",
"Privacy warning": "Alerta sobre privacidade",
"Privileged Users": "Usuárias/os privilegiadas/os",
"Profile": "Perfil",
"Refer a friend to Riot:": "Indicar um amigo para participar",
"rejected": "recusou",
"rejected the invitation.": "rejeitou o convite.",
"Reject invitation": "Rejeitar convite",
"Remove Contact Information?": "Remover informação de contato?",
"removed their display name": "removeu seu nome",
"removed their profile picture": "removeu sua foto de perfil",
"Remove": "Remover",
"requested a VoIP conference": "requisitou uma conferência VoIP",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Atualmente, ao alterar sua senha, você também zera todas as chaves de criptografia ponta-a-ponta em todos os dipositivos, fazendo com que o histórico de conversas da sala não possa mais ser lido, a não ser que você antes exporte suas chaves de sala e as reimporte após a alteração da senha. No futuro, isso será melhorado.",
"restore": "restaurar",
"Return to app": "Retornar ao aplicativo",
"Return to login screen": "Retornar à tela de login",
"Room Colour": "Cores da sala",
"Room name (optional)": "Título da Sala (opcional)",
"Rooms": "Salas",
"Scroll to bottom of page": "Ir para o fim da página",
"Scroll to unread messages": "Rolar para baixo para ver as mensagens não lidas",
"Searches DuckDuckGo for results": "Buscar por resultados no buscador DuckDuckGo",
"Send a message (unencrypted)": "Enviar uma mensagem",
"Send an encrypted message": "Enviar uma mensagem criptografada",
"Sender device information": "Informação do dispositivo emissor",
"Send Invites": "Enviar convites",
"Send Reset Email": "Enviar email para redefinição de senha",
"sent an image": "enviou uma imagem",
"sent an invitation to": "enviou um convite para",
"sent a video": "enviou um vídeo",
"Server may be unavailable or overloaded": "Servidor pode estar indisponível ou sobrecarregado",
"Server may be unavailable, overloaded, or you hit a bug.": "O servidor pode estar indisponível ou sobrecarregado, ou então você encontrou uma falha no sistema.",
"Session ID": "Identificador de sessão",
"set a profile picture": "colocou uma foto de perfil",
"set their display name to": "configurou seu nome para",
"Settings": "Configurações",
"Show panel": "Mostrar painel",
"Signed Out": "Deslogar",
"Sign in": "Entrar",
"Sign out": "Sair",
"since the point in time of selecting this option": "a partir do momento em que você selecionar esta opção",
"since they joined": "desde que entraram na sala",
"since they were invited": "desde que foram convidadas/os",
"Someone": "Alguém",
"Sorry, this homeserver is using a login which is not recognised ": "Desculpe, o servidor padrão está usando um login de acesso que não é válido ",
"Start a chat": "Começar uma conversa",
"Start Chat": "Começar conversa",
"Success": "Sucesso",
"tag as": "etiquetar como",
"tag direct chat": "definir como conversa pessoal",
"The default role for new room members is": "O papel padrão para novas/os integrantes da sala é",
"The email address linked to your account must be entered.": "O endereço de email relacionado a sua conta precisa ser informado.",
"their invitations": "seus convites",
"their invitation": "seu convite",
"These are experimental features that may break in unexpected ways. Use with caution": "Estes são recursos experimentais que podem não funcionar corretamente. Use com cuidado.",
"The visibility of existing history will be unchanged": "A visibilidade do histórico atual não será alterada",
"This doesn't appear to be a valid email address": "Este não aparenta ser um endereço de email válido",
"this invitation?": "este convite?",
"This is a preview of this room. Room interactions have been disabled": "Esta é uma pré visualização desta sala. As interações com a sala estão desabilitadas",
"This room is not accessible by remote Matrix servers": "Esta sala não é acessível para servidores Matrix remotos",
"This room's internal ID is": "O ID interno desta sala é",
"times": "vezes",
"To ban users": "Para banir usuárias/os",
"To configure the room": "Para poder configurar a sala",
"To invite users into the room": "Para convidar usuárias/os para esta sala",
"to join the discussion": "para se juntar à conversa",
"To kick users": "Para poder remover pessoas da sala",
"To link to a room it must have": "Para fazer um link para uma sala, ela deve ter",
"To redact messages": "Para poder apagar mensagens",
"To reset your password, enter the email address linked to your account": "Para redefinir sua senha, entre com o email da sua conta",
"To send events of type": "Para enviar eventos do tipo",
"To send messages": "Para enviar mensagens",
"turned on end-to-end encryption (algorithm": "acionou a encriptação ponta-a-ponta (algoritmo",
"Unable to add email address": "Não foi possível adicionar endereço de email",
"Unable to remove contact information": "Não foi possível remover informação de contato",
"Unable to verify email address.": "Não foi possível verificar o endereço de email.",
"Unban": "Desfazer banimento",
"Unencrypted room": "Sala não criptografada",
"unencrypted": "não criptografado",
"unknown device": "dispositivo desconhecido",
"unknown error code": "código de erro desconhecido",
"unknown": "desconhecido",
"Upload avatar": "Enviar icone de perfil de usuário",
"uploaded a file": "enviou um arquivo",
"Upload Files": "Enviar arquivos",
"Upload file": "Enviar arquivo",
"User ID": "Identificador de Usuário",
"User Interface": "Interface de usuário",
"User name": "Nome de usuária/o",
"Users": "Usuários",
"User": "Usuária/o",
"Verification Pending": "Verificação pendente",
"Verification": "Verificação",
"verified": "verificado",
"Video call": "Chamada de vídeo",
"Voice call": "Chamada de voz",
"VoIP conference finished.": "Conferência VoIP encerrada.",
"VoIP conference started.": "Conferência VoIP iniciada.",
"(warning: cannot be disabled again!)": "(atenção: esta operação não poderá ser desfeita depois!)",
"Warning": "Atenção!",
"was banned": "banida/o",
"was invited": "convidada/o",
"was kicked": "retirada/o da sala",
"was unbanned": "des-banida/o",
"was": "foi",
"were": "foram",
"Who can access this room?": "Quem pode acessar esta sala?",
"Who can read history?": "Quem pode ler o histórico da sala?",
"Who would you like to add to this room?": "Quais pessoas você gostaria de adicionar a esta sala?",
"Who would you like to communicate with?": "Com quem você gostaria de se comunicar?",
"withdrawn": "retirado",
"Would you like to": "Você gostaria de",
"You are trying to access": "Você está tentando acessar a sala",
"You do not have permission to post to this room": "Você não tem permissão de postar nesta sala",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "Você foi desconectada/o de todos os dispositivos e portanto não receberá mais notificações no seu celular ou no computador. Para reativar as notificações, entre novamente em cada um dos dispositivos que costuma usar",
"You have no visible notifications": "Voce não possui notificações visíveis",
"you must be a": "você precisa ser",
"Your password has been reset": "Sua senha foi redefinida",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Sua senha foi alterada com sucesso. Você não receberá notificações em outros dispositivos até que você logue novamente por eles",
"You should not yet trust it to secure data": "Você não deve confiar nela ainda para preservar seus dados",
"Sun": "Dom",
"Mon": "Seg",
"Tue": "Ter",
"Wed": "Qua",
"Thu": "Qui",
"Fri": "Sex",
"Sat": "Sáb",
"Jan": "Jan",
"Feb": "Fev",
"Mar": "Mar",
"Apr": "Abr",
"May": "Mai",
"Jun": "Jun",
"Jul": "Jul",
"Aug": "Ago",
"Sep": "Set",
"Oct": "Out",
"Nov": "Nov",
"Dec": "Dez",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s de %(monthName)s às %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s às %(time)s",
"en": "Inglês",
"pt-br": "Português do Brasil",
"de": "Alemão",
"da": "Dinamarquês",
"ru": "Russo",
"%(targetName)s accepted an invitation.": "%(targetName)s aceitou um convite.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s aceitou o convite para %(displayName)s.",
"all room members, from the point they are invited": "todas/os as/os integrantes da sala, a partir do momento em que foram convidadas/os",
"all room members, from the point they joined": "todas/os as/os integrantes da sala, a partir do momento em que entraram na sala",
"%(names)s and %(lastPerson)s are typing": "%(names)s e %(lastPerson)s estão escrevendo",
"%(names)s and one other are typing": "%(names)s e uma outra pessoa estão escrevendo",
"%(names)s and %(count)s others are typing": "%(names)s e %(count)s outras pessoas estão escrevendo",
"%(senderName)s answered the call.": "%(senderName)s atendeu à chamada.",
"anyone": "qualquer pessoa",
"%(senderName)s banned %(targetName)s.": "%(senderName)s removeu %(targetName)s da sala.",
"Call Timeout": "Tempo esgotado. Chamada encerrada",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s mudou seu nome público de %(oldDisplayName)s para %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s alterou sua imagem de perfil.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s alterou o nível de permissões de %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s alterou o nome da sala para %(roomName)s.",
"click to reveal": "clique para ver",
"Conference call failed.": "Chamada de conferência falhou.",
"Conference calling is in development and may not be reliable.": "Chamadas de conferência estão em desenvolvimento e portanto podem não funcionar.",
"Conference calls are not supported in encrypted rooms": "Chamadas de conferência não são possíveis em salas criptografadas",
"Conference calls are not supported in this client": "Chamadas de conferência não são possíveis neste navegador",
"/ddg is not a command": "/ddg não é um comando",
"Drop here %(toAction)s": "Arraste aqui %(toAction)s",
"Drop here to tag %(section)s": "Arraste aqui para marcar como %(section)s",
"%(senderName)s ended the call.": "%(senderName)s finalizou a chamada.",
"Existing Call": "Chamada em andamento",
"Failed to lookup current room": "Não foi possível buscar na sala atual",
"Failed to send email": "Não foi possível enviar email",
"Failed to send request.": "Não foi possível mandar requisição.",
"Failed to set up conference call": "Não foi possível montar a chamada de conferência",
"Failed to verify email address: make sure you clicked the link in the email": "Não foi possível verificar o endereço de email: verifique se você realmente clicou no link que está no seu email",
"Failure to create room": "Não foi possível criar a sala",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s para %(toPowerLevel)s",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Visitantes não podem criar novas salas. Por favor, registre-se para criar uma sala e iniciar uma conversa.",
"%(senderName)s invited %(targetName)s.": "%(senderName)s convidou %(targetName)s.",
"%(displayName)s is typing": "%(displayName)s está escrevendo",
"%(targetName)s joined the room.": "%(targetName)s entrou na sala.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s removeu %(targetName)s da sala.",
"%(targetName)s left the room.": "%(targetName)s saiu da sala.",
"%(senderName)s made future room history visible to": "%(senderName)s deixou o histórico futuro da sala visível para",
"Missing room_id in request": "Faltou o id da sala na requisição",
"Missing user_id in request": "Faltou o id de usuário na requisição",
"Must be viewing a room": "Tem que estar visualizando uma sala",
"New Composer & Autocomplete": "Nova ferramenta de formatação de mensagens e autocompletar",
"(not supported by this browser)": "(não é compatível com este navegador)",
"olm version": "versão olm",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s fez uma chamada de %(callType)s.",
"Power level must be positive integer.": "O nível de permissões tem que ser um número inteiro e positivo.",
"Press": "Aperte",
"Reason": "Razão",
"%(targetName)s rejected the invitation.": "%(targetName)s recusou o convite.",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removeu o seu nome público (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s removeu sua imagem de perfil.",
"%(senderName)s requested a VoIP conference.": "%(senderName)s está solicitando uma conferência de voz.",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot não tem permissões para enviar notificações a você - por favor, verifique as configurações do seu navegador",
"Riot was not given permission to send notifications - please try again": "Riot não tem permissões para enviar notificações a você - por favor, tente novamente",
"Room %(roomId)s not visible": "A sala %(roomId)s não está visível",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s enviou uma imagem.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s enviou um convite para %(targetDisplayName)s entrar na sala.",
"%(senderName)s set a profile picture.": "%(senderName)s definiu uma imagem de perfil.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s definiu seu nome público para %(displayName)s.",
"tag as %(tagName)s": "marcar como %(tagName)s",
"This email address is already in use": "Este endereço de email já está sendo usado",
"This email address was not found": "Este endereço de email não foi encontrado",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "O arquivo '%(fileName)s' ultrapassa o limite de tamanho que nosso servidor permite enviar",
"The file '%(fileName)s' failed to upload": "Não foi possível enviar o arquivo '%(fileName)s",
"The remote side failed to pick up": "Houve alguma falha que não permitiu a outra pessoa atender à chamada",
"This room is not recognised.": "Esta sala não é reconhecida.",
"These are experimental features that may break in unexpected ways": "Estas são funcionalidades experimentais que podem apresentar falhas",
"This phone number is already in use": "Este número de telefone já está sendo usado",
"to browse the directory": "para navegar na lista pública de salas",
"to demote": "para reduzir prioridade",
"to favourite": "para favoritar",
"to make a room or": "para criar uma sala ou",
"To remove other users' messages": "Para apagar mensagens de outras pessoas",
"to restore": "para restaurar",
"to start a chat with someone": "para iniciar uma conversa com alguém",
"to tag as %(tagName)s": "para marcar como %(tagName)s",
"to tag direct chat": "para marcar a conversa como pessoal",
"To use it, just wait for autocomplete results to load and tab through them.": "Para usar esta funcionalidade, espere o carregamento dos resultados de autocompletar e então escolha entre as opções.",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s ativou criptografia ponta a ponta (algoritmo %(algorithm)s).",
"Unable to restore previous session": "Não foi possível restaurar a sessão anterior",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s desfez o banimento de %(targetName)s.",
"Unable to capture screen": "Não foi possível capturar a imagem da tela",
"Unable to enable Notifications": "Não foi possível ativar as notificações",
"Upload Failed": "O envio falhou",
"Usage": "Uso",
"Use with caution": "Use com cautela",
"VoIP is unsupported": "Chamada de voz não permitida",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s desfez o convite a %(targetName)s.",
"You are already in a call.": "Você já está em uma chamada.",
"You're not in any rooms yet! Press": "Você ainda não está em nenhuma sala! Pressione",
"You are trying to access %(roomName)s.": "Você está tentando acessar a sala %(roomName)s.",
"You cannot place a call with yourself.": "Você não pode iniciar uma chamada.",
"You cannot place VoIP calls in this browser.": "Você não pode fazer chamadas de voz neste navegador.",
"You need to be able to invite users to do that.": "Para fazer isso, você tem que ter permissão para convidar outras pessoas.",
"You need to be logged in.": "Você tem que estar logado.",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "É necessário que você faça login novamente para poder gerar as chaves de criptografia ponta-a-ponta para este dispositivo e então enviar sua chave pública para o servidor. Pedimos desculpas pela inconveniência, é preciso fazer isso apenas única uma vez.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "O seu endereço de email não parece estar associado a uma conta de usuária/o Matrix neste servidor.",
"Set a display name:": "Defina um nome público para você:",
"Upload an avatar:": "Envie uma imagem de perfil para identificar você:",
"This server does not support authentication with a phone number.": "Este servidor não permite a autenticação através de números de telefone.",
"Missing password.": "Faltou a senha.",
"Passwords don't match.": "As senhas não conferem.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "A senha é muito curta (o mínimo é de %(MIN_PASSWORD_LENGTH)s caracteres).",
"This doesn't look like a valid email address.": "Este endereço de email não parece ser válido.",
"This doesn't look like a valid phone number.": "Este número de telefone não parece ser válido.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "Nomes de usuária/o podem conter apenas letras, números, pontos, hífens e linha inferior (_).",
"An unknown error occurred.": "Um erro desconhecido ocorreu.",
"I already have an account": "Eu já tenho uma conta",
"An error occurred: %(error_string)s": "Um erro ocorreu: %(error_string)s",
"Topic": "Tópico",
"Make this room private": "Tornar esta sala privada",
"Share message history with new users": "Compartilhar histórico de mensagens com novas/os usuárias/os",
"Encrypt room": "Criptografar esta sala",
"There are no visible files in this room": "Não há arquivos públicos nesta sala",
"Error changing language": "Erro ao mudar de idioma",
"Riot was unable to find the correct Data for the selected Language.": "Não foi possível encontrar os dados para o idioma selecionado.",
"Connectivity to the server has been lost.": "A conexão com o servidor foi perdida. Verifique sua conexão de internet.",
"Sent messages will be stored until your connection has returned.": "Imagens enviadas ficarão armazenadas até que sua conexão seja reestabelecida.",
"Auto-complete": "Autocompletar",
"Resend all": "Reenviar todas as mensagens",
"cancel all": "cancelar todas",
"now. You can also select individual messages to resend or cancel.": "agora. Você também pode escolher mensagens individuais e definir se vai reenviar ou cancelar o envio.",
"Active call": "Chamada ativa",
"af": "Afrikaans",
"ar-ae": "Árabe (U.A.E.)",
"ar-bh": "Árabe (Bahrain)",
"ar-dz": "Árabe (Algéria)",
"Sunday": "Domingo",
"Monday": "Segunda",
"ar-eg": "Árabe (Egito)",
"ar-tn": "Árabe (Tunisia)",
"be": "Bielorusso",
"bg": "Búlgaro",
"ca": "Catalão",
"cs": "Tcheco",
"el": "Grego",
"en-au": "Inglês (Austrália)",
"en-ca": "Inglês (Canadá)",
"en-gb": "Inglês (Reino Unido)",
"en-ie": "Inglês (Irlanda)",
"en-nz": "Inglês (Nova Zelândia)",
"en-us": "Inglês (Estados Unidos)",
"es-ar": "Espanhol (Argentina)",
"es-py": "Espanhol (Paraguai)",
"es": "Espanhol (Espanha)",
"et": "Estônia",
"fa": "Farsi",
"fi": "Finlandês",
"fr-be": "Francês (Bélgica)",
"fr-ca": "Francês (Canadá)",
"fr-ch": "Francês (Suíça)",
"fr": "Francês",
"ga": "Irlandês",
"he": "Hebreu",
"hi": "Hindu",
"hr": "Croácia",
"hu": "Hungria",
"id": "Indonésio",
"is": "Islandês",
"it": "Italiano",
"ja": "Japonês",
"ji": "Ídiche",
"lt": "Lituânia",
"lv": "Letão",
"ms": "Malaio",
"mt": "Maltês",
"nl-be": "Holandês (Bélgica)",
"nl": "Holandês",
"no": "Norueguês",
"pl": "Polonês",
"pt": "Português (Portugal)",
"rm": "Romanche",
"ro": "Romeno",
"sk": "Eslovaco",
"sl": "Esloveno",
"sq": "Albanês",
"sr": "Sérvio",
"sv": "Suécia",
"th": "Tailandês",
"tn": "Tsuana",
"tr": "Turquia",
"ts": "Tsonga",
"uk": "Ucraniano",
"ur": "Urdu",
"vi": "Vietnamita",
"xh": "Xhosa",
"zu": "Zulu",
"Failed to forget room %(errCode)s": "Falhou ao esquecer a sala %(errCode)s",
"Failed to join the room": "Falhou ao entrar na sala",
"Tuesday": "Terça",
"Wednesday": "Quarta",
"Thursday": "Quinta",
"Friday": "Sexta",
"Saturday": "Sábado",
"ar-iq": "Árabe (Iraque)",
"ar-jo": "Árabe (Jordânia)",
"ar-kw": "Árabe (Kuwait)",
"ar-lb": "Árabe (Líbano)",
"ar-ly": "Árabe (Líbia)",
"ar-ma": "Árabe (Marrocos)",
"ar-om": "Árabe (Omã)",
"ar-qa": "Árabe (Catar)",
"ar-sa": "Árabe (Arábia Saudita)",
"ar-sy": "Árabe (Síria)",
"ar-ye": "Árabe (Iémen)",
"de-at": "Alemão (Austria)",
"de-ch": "Alemão (Suíça)",
"de-li": "Alemão (Liechtenstein)",
"de-lu": "Alemão (Luxemburgo)",
"en-bz": "Inglês (Belize)",
"en-jm": "Inglês (Jamaica)",
"en-tt": "English (Trindade)",
"en-za": "English (África do Sul)",
"es-bo": "Espanhol (Bolívia)",
"es-cl": "Espanhol (Chile)",
"es-co": "Espanhol (Colômbia)",
"es-cr": "Espanhol (Costa Rica)",
"es-do": "Espanhol (República Dominicana)",
"es-ec": "Espanhol (Equador)",
"es-gt": "Espanhol (Guatemala)",
"es-hn": "Espanhol (Honduras)",
"es-mx": "Espanhol (México)",
"es-ni": "Espanhol (Nicarágua)",
"es-pa": "Espanhol (Panamá)",
"%(oneUser)schanged their avatar": "%(oneUser)salterou sua imagem pública",
"es-pe": "Espanhol (Peru)",
"es-pr": "Espanhol (Porto Rico)",
"es-sv": "Espanhol (El Salvador)",
"es-uy": "Espanhol (Uruguai)",
"es-ve": "Espanhol (Venezuela)",
"eu": "Basco (Basco)",
"fr-lu": "Francês (Luxemburgo)",
"gd": "Galês (Escócia)",
"it-ch": "Italiano (Suíça)",
"ko": "Coreano",
"mk": "Macedônio (República da Macedônia)",
"ro-mo": "Romano (Moldávia)",
"ru-mo": "Russo (Moldávia)",
"sb": "Sorábio",
"sv-fi": "Sueco (Finlândia)",
"zh-cn": "Chinês (República Popular da China)",
"zh-hk": "Chinês (Hong Kong SAR)",
"zh-sg": "Chinês (Singapura)",
"zh-tw": "Chinês (Taiwan)",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Uma mensagem de texto foi enviada para +%(msisdn)s. Gentileza entrar com o código de verificação que contém",
"%(items)s and %(remaining)s others": "%(items)s e %(remaining)s outros",
"%(items)s and one other": "%(items)s e um outro",
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
"and %(overflowCount)s others...": "e %(overflowCount)s outros...",
"and one other...": "e um outro...",
"Are you sure?": "Você tem certeza?",
"Attachment": "Anexo",
"Autoplay GIFs and videos": "Reproduzir automaticamente GIFs e videos",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s de %(monthName)s de %(fullYear)s às %(time)s",
"fo": "Feroês",
"sx": "Sutu",
"sz": "Sami (Lappish)",
"ve": "Venda",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Não consigo conectar ao servidor padrão - favor checar sua conexão à internet e verificar se o certificado SSL do seu <a>servidor padrão</a> é confiável.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Não consigo conectar ao servidor padrão através de HTTP quando uma URL HTTPS está na barra de endereços do seu navegador. Use HTTPS ou então <a>habilite scripts não seguros no seu navegador</a>.",
"Change Password": "Alterar senha",
"changing room on a RoomView is not supported": "mudar a sala em uma 'RoomView' não é permitido",
"Click to mute audio": "Clique para colocar o áudio no mudo",
"Click to mute video": "Clique para desabilitar imagens de vídeo",
"Click to unmute video": "Clique para voltar a mostrar imagens de vídeo",
"Click to unmute audio": "Clique para retirar áudio do mudo",
"Command error": "Erro de comando",
"Decrypt %(text)s": "Descriptografar %(text)s",
"Delete": "Apagar",
"Devices": "Dispositivos",
"Direct chats": "Conversas pessoais",
"Disinvite": "Desconvidar",
"Don't send typing notifications": "Não enviar notificação de estar digitando",
"Download %(text)s": "Baixar %(text)s",
"Enable encryption": "Habilitar criptografia",
"Enter Code": "Entre com o código",
"Failed to ban user": "Não foi possível banir o/a usuário/a",
"Failed to change power level": "Não foi possível mudar o nível de permissões",
"Failed to delete device": "Não foi possível remover o dispositivo",
"Failed to join room": "Não foi possível ingressar na sala",
"Failed to kick": "Não foi possível remover usuária/o",
"Failed to load timeline position": "Não foi possível carregar a posição na linha do tempo",
"Failed to mute user": "Não foi possível remover notificações da/do usuária/o",
"Failed to reject invite": "Não foi possível rejeitar o convite",
"Failed to save settings": "Não foi possível salvar as configurações",
"Failed to set display name": "Houve falha ao definir o nome público",
"Failed to toggle moderator status": "Houve falha ao alterar o status de moderador/a",
"Fill screen": "Tela cheia",
"Hide read receipts": "Ocultar recebimentos de leitura",
"Hide Text Formatting Toolbar": "Ocultar a barra de formatação de texto",
"Incorrect verification code": "Código de verificação incorreto",
"Invalid alias format": "Formato de alias é inválido",
"Invalid address format": "Formato de endereço é inválido",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' não é um formato válido para um endereço",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' não é um formato válido para um alias",
"Join Room": "Ingressar na sala",
"Jump to first unread message.": "Ir diretamente para a primeira das mensagens não lidas.",
"Kick": "Remover",
"Level": "Nível",
"Local addresses for this room:": "Endereço local desta sala:",
"Markdown is disabled": "A formatação 'Markdown' está desabilitada",
"Markdown is enabled": "A formatação 'Markdown' está habilitada",
"Message not sent due to unknown devices being present": "A mensagem não foi enviada por causa da presença de dispositivos desconhecidos",
"Never send encrypted messages to unverified devices in this room": "Nunca envie mensagens criptografadas para dispositivos não verificados nesta sala",
"New address (e.g. #foo:%(localDomain)s)": "Novo endereço (p.ex: #algo:%(localDomain)s)",
"not set": "não definido",
"not specified": "não especificado",
"No devices with registered encryption keys": "Não há dispositivos com chaves de criptografia registradas",
"No more results": "Não há mais resultados",
"No results": "Sem resultados",
"OK": "Ok",
"Revoke Moderator": "Retirar status de moderador",
"Search": "Buscar",
"Search failed": "Busca falhou",
"Server error": "Erro no servidor",
"Server may be unavailable, overloaded, or search timed out :(": "O servidor pode estar indisponível, sobrecarregado, ou a busca ultrapassou o tempo limite :(",
"Server may be unavailable, overloaded, or the file too big": "O servidor pode estar indisponível, sobrecarregado, ou o arquivo é muito grande",
"Server unavailable, overloaded, or something else went wrong.": "O servidor pode estar indisponível, sobrecarregado, ou alguma outra coisa não funcionou.",
"Some of your messages have not been sent.": "Algumas das suas mensagens não foram enviadas.",
"Submit": "Enviar",
"The main address for this room is": "O endereço principal desta sala é",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Esta ação não pode ser realizada por um/a usuário/a visitante. Por favor, registre-se para poder fazer isso.",
"%(actionVerb)s this person?": "%(actionVerb)s esta pessoa?",
"This room has no local addresses": "Esta sala não tem endereços locais",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas parece que você não tem permissões para ver a mensagem em questão.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas não o encontrei.",
"Turn Markdown off": "Desabilitar a formatação 'Markdown'",
"Turn Markdown on": "Habilitar a marcação 'Markdown'",
"Unable to load device list": "Não foi possível carregar a lista de dispositivos",
"Unknown command": "Comando desconhecido",
"Unknown room %(roomId)s": "A sala %(roomId)s é desconhecida",
"You have been invited to join this room by %(inviterName)s": "Você foi convidada/o por %(inviterName)s a ingressar nesta sala",
"You seem to be in a call, are you sure you want to quit?": "Parece que você está em uma chamada. Tem certeza que quer sair?",
"You seem to be uploading files, are you sure you want to quit?": "Parece que você está enviando arquivos. Tem certeza que quer sair?",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Você não poderá desfazer esta mudança, pois estará dando a este(a) usuário(a) o mesmo nível de permissões que você.",
"Make Moderator": "Tornar moderador(a)",
"Room": "Sala",
"(~%(searchCount)s results)": "(±%(searchCount)s resultados)",
"Cancel": "Cancelar",
"bold": "negrito",
"italic": "itálico",
"strike": "tachado",
"underline": "sublinhado",
"code": "código de programação",
"quote": "citação",
"bullet": "marcador de lista",
"numbullet": "marcador de numeração",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)singressaram %(repeats)s vezes",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)singressou %(repeats)s vezes",
"%(severalUsers)sjoined": "%(severalUsers)singressaram",
"%(oneUser)sjoined": "%(oneUser)singressou",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)ssaíram %(repeats)s vezes",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)ssaiu %(repeats)s vezes",
"%(severalUsers)sleft": "%(severalUsers)ssaíram",
"%(oneUser)sleft": "%(oneUser)ssaiu",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)singressaram e saíram %(repeats)s vezes",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)singressou e saiu %(repeats)s vezes",
"%(severalUsers)sjoined and left": "%(severalUsers)singressaram e saíram",
"%(oneUser)sjoined and left": "%(oneUser)singressou e saiu",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)ssaíram e entraram novamente %(repeats)s vezes",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)ssaiu e entrou novamente %(repeats)s vezes",
"%(severalUsers)sleft and rejoined": "%(severalUsers)ssaíram e entraram novamente",
"%(oneUser)sleft and rejoined": "%(oneUser)ssaiu e entrou novamente",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejeitaram seus convites %(repeats)s vezes",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejeitou seu convite %(repeats)s vezes",
"%(severalUsers)srejected their invitations": "%(severalUsers)srejeitaram seus convites",
"%(oneUser)srejected their invitation": "%(oneUser)srejeitou seu convite",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)stiveram seus convites desfeitos %(repeats)s vezes",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)steve seu convite desfeito %(repeats)s vezes",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)stiveram seus convites desfeitos",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)steve seu convite desfeito",
"were invited %(repeats)s times": "foram convidadas(os) %(repeats)s vezes",
"was invited %(repeats)s times": "foi convidada(o) %(repeats)s vezes",
"were invited": "foram convidadas(os)",
"were banned %(repeats)s times": "foram banidas(os) %(repeats)s vezes",
"was banned %(repeats)s times": "foi banida(o) %(repeats)s vezes",
"were banned": "foram banidas(os)",
"were unbanned %(repeats)s times": "tiveram banimento desfeito %(repeats)s vezes",
"was unbanned %(repeats)s times": "teve banimento desfeito %(repeats)s vezes",
"were unbanned": "tiveram banimento desfeito",
"were kicked %(repeats)s times": "foram expulsas(os) %(repeats)s vezes",
"was kicked %(repeats)s times": "foi expulsa(o) %(repeats)s vezes",
"were kicked": "foram expulsas(os)",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)salteraram seu nome %(repeats)s vezes",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)salterou seu nome %(repeats)s vezes",
"%(severalUsers)schanged their name": "%(severalUsers)salteraram seus nomes",
"%(oneUser)schanged their name": "%(oneUser)salterou seu nome",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)salteraram sua imagem pública %(repeats)s vezes",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)salterou sua imagem pública %(repeats)s vezes",
"%(severalUsers)schanged their avatar": "%(severalUsers)salteraram sua imagem pública",
"Ban": "Banir",
"A registered account is required for this action": "Uma conta registrada é necessária para esta ação",
"Access Token:": "Token de acesso:",
"Always show message timestamps": "Sempre mostrar as datas das mensagens",
"Authentication": "Autenticação",
"An error has occurred.": "Ocorreu um erro.",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Alterar a senha irá atualmente apagar todas as chaves de criptografia ponta-a-ponta em todos os dispositivos, fazendo com que o histórico da conversa fique ilegível, a não ser que você exporte antes as chaves de sala e então as reimporte depois. No futuro, isso vai melhorar.",
"Email": "Email",
"Email address": "Endereço de email",
"Error decrypting attachment": "Erro ao descriptografar o anexo",
"Interface Language": "Idioma da interface",
"Invalid file%(extra)s": "Arquivo inválido %(extra)s",
"Logged in as:": "Logado como:",
"matrix-react-sdk version:": "versão do matrix-react-sdk:",
"Mute": "Mudo",
"olm version:": "versão do olm:",
"Operation failed": "A operação falhou",
"Registration required": "Registro obrigatório",
"Remove %(threePid)s?": "Remover %(threePid)s?",
"Report it": "Reportar",
"riot-web version:": "versão do riot-web:",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Mostrar os horários em formato de 12h (p.ex: 2:30pm)",
"Unmute": "Tirar do mudo",
"Warning!": "Atenção!",
"You need to enter a user name.": "Você precisa inserir um nome de usuária(o).",
"Please select the destination room for this message": "Por favor, escolha a sala para onde quer encaminhar esta mensagem",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s apagou o nome da sala.",
"Analytics": "Análise",
"Opt out of analytics": "Sair da ferramenta de análise",
"Options": "Opções",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot coleta informações anônimas de uso para nos permitir melhorar o sistema.",
"Passphrases must match": "As senhas têm que ser iguais",
"Passphrase must not be empty": "A senha não pode estar vazia",
"Export room keys": "Exportar chaves de sala",
"Enter passphrase": "Entre com a senha",
"Confirm passphrase": "Confirme a senha",
"Import room keys": "Importar chaves de sala",
"File to import": "Arquivo para importar",
"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.": "Este processo permite que você exporte as chaves para mensagens que você recebeu em salas criptografadas para um arquivo local. Você poderá então importar o arquivo para outro cliente Matrix no futuro, de modo que este cliente também poderá descriptografar suas mensagens.",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "O arquivo exportado será protegido com uma senha. Você deverá inserir a senha aqui para poder descriptografar o arquivo futuramente.",
"You must join the room to see its files": "Você precisa ingressar na sala para ver seus arquivos",
"Reject all %(invitedRooms)s invites": "Rejeitar todos os %(invitedRooms)s convites",
"Start new chat": "Iniciar nova conversa",
"Guest users can't invite users. Please register.": "Visitantes não podem convidar usuárias(os) registradas(os). Favor registrar.",
"Failed to invite": "Falha ao enviar o convite",
"Failed to invite user": "Falha ao convidar a(o) usuária(o)",
"Failed to invite the following users to the %(roomName)s room:": "Falha ao convidar as(os) seguintes usuárias(os) para a sala %(roomName)s:",
"Confirm Removal": "Confirmar a remoção",
"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.": "Você tem certeza que quer apagar este evento? Note que se você apaga o nome de uma sala ou uma mudança de tópico, esta ação não poderá ser desfeita.",
"Unknown error": "Erro desconhecido",
"Incorrect password": "Senha incorreta",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Isso tornará a sua conta permanentemente inusável. Você não será capaz de registrar novamente o mesmo ID de usuário.",
"This action is irreversible.": "Esta ação é irreversível.",
"To continue, please enter your password.": "Para continuar, por favor insira a sua senha.",
"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:": "Para verificar que este dispositivo é confiável, por favor entre em contato com a(o) sua(seu) dona(o) usando outros meios, como por exemplo pessoalmente ou por uma chamada telefônica, e pergunte a esta pessoa se a chave que ela está vendo nas suas configurações de usuário para este dispositivo é igual a esta:",
"Device name": "Nome do dispositivo",
"Device key": "Chave do dispositivo",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Se a chave for a mesma, clique no botão \"verificar\" abaixo. Se não for a mesma, então alguma outra pessoa está interceptando este dispositivo e você provavelmente vai querer clicar no botão \"colocar na lista negra\".",
"In future this verification process will be more sophisticated.": "No futuro, este processo de verificação será mais sofisticado.",
"Verify device": "Verificar o dispositivo",
"I verify that the keys match": "Eu confirmo que as chaves são iguais",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Encontramos um erro tentando restaurar sua sessão anterior. Se você continuar, terá que fazer login novamente, e o histórico da conversa criptografada se tornará ilegível.",
"Unable to restore session": "Não foi possível restaurar a sessão",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Se você já usou antes uma versão mais recente do Riot, a sua sessão pode ser incompatível com esta versão. Feche esta janela e tente abrir com a versão mais recente.",
"Continue anyway": "Continuar de qualquer maneira",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "O seu nome público é como você aparecerá para as outras pessoas quando conversar nas salas. Qual nome público você deseja ter?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Você está atualmente bloqueando dispositivos não verificados. Para enviar mensagens para estes dispositivos, você necessita antes verificá-los.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Nós recomendamos que você passe pelo processo de verificação para cada dispositivo para confirmar que eles pertencem às pessoas que efetivamente são suas donas, mas você pode reenviar a mensagem sem verificar isso, se assim o desejar.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contém dispositivos que você não viu antes.",
"Unknown devices": "Dispositivos desconhecidos",
"Unknown Address": "Endereço desconhecido",
"Unblacklist": "Tirar da lista de bloqueados",
"Blacklist": "Colocar na lista de bloqueados",
"Unverify": "Des-verificar",
"Verify...": "Verificar...",
"ex. @bob:example.com": "p.ex: @joao:exemplo.com",
"Add User": "Adicionar usuária(o)",
"This Home Server would like to make sure you are not a robot": "Este Servidor de Base gostaria de confirmar que você não é um robô",
"Sign in with CAS": "Assinar com CAS",
"Custom Server Options": "Opções para Servidor Personalizado",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Você pode usar as opções de servidor personalizado para entrar em outros servidores Matrix ao especificar uma URL de um Servidor de Base diferente.",
"This allows you to use this app with an existing Matrix account on a different home server.": "Isso permite que você use este aplicativo com uma conta Matrix existente em um servidor de base diferente.",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Você também pode definir um servidor de identidades personalizado, mas isso vai impedir a sua interação com outros usuários a partir do endereço de email.",
"Dismiss": "Descartar",
"Please check your email to continue registration.": "Por favor, verifique o seu e-mail para continuar o processo de registro.",
"Token incorrect": "Token incorreto",
"A text message has been sent to": "Uma mensagem de texto foi enviada para",
"Please enter the code it contains:": "Por favor, entre com o código que está na mensagem:",
"powered by Matrix": "rodando a partir do Matrix",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Se não especificar um endereço de e-mail, você não poderá redefinir sua senha. Tem certeza?",
"You are registering with %(SelectedTeamName)s": "Você está se registrando com %(SelectedTeamName)s",
"Default server": "Servidor padrão",
"Custom server": "Servidor personalizado",
"Home server URL": "URL do servidor de base",
"Identity server URL": "URL do servidor de identidades",
"What does this mean?": "O que isso significa?",
"Error decrypting audio": "Erro ao descriptografar o áudio",
"Error decrypting image": "Erro ao descriptografar a imagem",
"Image '%(Body)s' cannot be displayed.": "A imagem '%(Body)s' não pode ser exibida.",
"This image cannot be displayed.": "Esta imagem não pode ser exibida.",
"Error decrypting video": "Erro ao descriptografar o vídeo",
"Add an Integration": "Adicionar uma integração",
"Removed or unknown message type": "Mensagem removida ou de tipo desconhecido",
"Disable URL previews by default for participants in this room": "Desabilitar as pré-visualizações de links por padrão para participantes desta sala",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "As pré-visualizações estão %(globalDisableUrlPreview)s por padrão para integrantes desta sala.",
"URL Previews": "Pré-visualização de links",
"Enable URL previews for this room (affects only you)": "Habilitar pré-visualizações de links para esta sala (afeta somente a você)",
"Drop file here to upload": "Arraste um arquivo aqui para enviar",
" (unsupported)": " (não suportado)",
"Ongoing conference call%(supportedText)s. %(joinText)s": "Conferência%(supportedText)s em andamento. %(joinText)s",
"Online": "Online",
"Idle": "Ocioso",
"Offline": "Offline",
"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.": "O arquivo exportado irá permitir a qualquer pessoa que o acesse a descriptografar qualquer uma das mensagens criptografadas que você veja, portanto seja bastante cuidadosa(o) em manter este arquivo seguro. Para deixar este arquivo mais protegido, recomendamos que você insira uma senha abaixo, que será usada para criptografar o arquivo. Só será possível importar os dados usando exatamente a mesma senha.",
"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.": "Este processo faz com que você possa importar as chaves de criptografia que tinha previamente exportado de outro cliente Matrix. Você poderá então descriptografar todas as mensagens que o outro cliente pôde criptografar.",
"You are about to be taken to a third-party site so you can authenticate your account for use with {integrationsUrl}. Do you wish to continue?": "",
"Start automatically after system login": "Iniciar automaticamente ao iniciar o sistema",
"Desktop specific": "Específico para o app de computadores desktop",
"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?": "Você será levado agora a um site de terceiros para poder autenticar a sua conta para uso com o serviço %(integrationsUrl)s. Você quer continuar?",
"Disable URL previews for this room (affects only you)": "Desabilitar as pré-visualizações de sites para esta sala (afeta apenas a você)",
"Device already verified!": "Dispositivo já verificado!",
"disabled": "desabilitado",
"enabled": "habilitado",
"Export": "Exportar",
"Failed to register as guest:": "Falha ao se registrar como visitante:",
"Guest access is disabled on this Home Server.": "O acesso para visitantes está desabilitado neste Servidor de Base.",
"Import": "Importar",
"Incorrect username and/or password.": "Nome de usuária(o) e/ou senha incorreto.",
"Invited": "Convidada(o)",
"Results from DuckDuckGo": "Resultados de DuckDuckGo",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "A chave de assinatura que você forneceu é a mesma que a chave de assinatura que você recebeu do dispositivo %(deviceId)s de %(userId)s . O dispositivo foi portanto marcado como verificado.",
"This Home Server does not support login using email address.": "Este Servidor de Base não permite login usando endereço de e-mail.",
"There was a problem logging in.": "Houve um problema ao fazer login.",
"Unknown (user, device) pair:": "Par usuária(o)-dispositivo desconhecido:",
"Unrecognised command:": "Comando não reconhecido:",
"Unrecognised room alias:": "Apelido de sala não reconhecido:",
"Use compact timeline layout": "Usar o layout de linha do tempo compacta",
"Verified key": "Chave verificada",
"WARNING: Device already verified, but keys do NOT MATCH!": "ATENÇÃO: O dispositivo já foi verificado, mas as chaves NÃO SÃO IGUAIS!",
"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!": "ATENÇÃO: VERIFICAÇÃO DE CHAVE FALHOU! A chave de assinatura para a(o) usuária(o) %(userId)s e dispositivo %(deviceId)s é \"%(fprint)s\", que não é igual à chave fornecida \"%(fingerprint)s\". Isso pode significar que suas comunicações estão sendo interceptadas!",
"Set a Display Name": "Definir seu nome público",
"for %(amount)ss": "por %(amount)ss",
"for %(amount)sm": "por %(amount)sm",
"for %(amount)sh": "por %(amount)sh",
"for %(amount)sd": "por %(amount)sd",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a imagem da sala.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName alterou a imagem da sala para <img/>",
"Missing Media Permissions, click here to request.": "Faltam permissões para uso de mídia no seu computador. Clique aqui para solicitá-las.",
"No Microphones detected": "Não foi detectado nenhum microfone",
"No Webcams detected": "Não foi detectada nenhuma Webcam",
"No media permissions": "Não há permissões de uso de vídeo/áudio no seu navegador",
"You may need to manually permit Riot to access your microphone/webcam": "Você talvez precise autorizar manualmente que o Riot acesse seu microfone e webcam",
"Default Device": "Dispositivo padrão",
"Microphone": "Microfone",
"Camera": "Câmera de vídeo",
"Add a topic": "Adicionar um tópico",
"VoIP": "VoIP",
"Anyone": "Qualquer pessoa",
"Are you sure you want to leave the room '%(roomName)s'?": "Você tem certeza que deseja sair da sala '%(roomName)s'?",
"Custom level": "Nível personalizado",
"(default: %(userName)s)": "(padrão: %(userName)s)",
"Device ID:": "ID do dispositivo:",
"device id: ": "id do dispositivo: ",
"Device key:": "Chave do dispositivo:",
"Email address (optional)": "Endereço de e-mail (opcional)",
"List this room in %(domain)s's room directory?": "Deseja listar esta sala na lista pública de salas de %(domain)s?",
"Mobile phone number (optional)": "Número de telefone celular (opcional)",
"Password:": "Senha:",
"Register": "Registre-se",
"Remote addresses for this room:": "Endereços remotos para esta sala:",
"Save": "Salvar",
"Setting a user name will create a fresh account": "Definir um nome de usuária(o) vai criar uma conta nova",
"Tagged as: ": "Marcado como: ",
"You have <a>disabled</a> URL previews by default.": "Você <a>desabilitou</a> pré-visualizações de links por padrão.",
"You have <a>enabled</a> URL previews by default.": "Você <a>habilitou</a> pré-visualizações de links por padrão.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Você inseriu um contato inválido. Tente usar o ID Matrix ou endereço de e-mail da pessoa que está buscando.",
"Hide removed messages": "Ocultar mensagens removidas"
}

898
src/i18n/strings/ru.json Normal file
View File

@@ -0,0 +1,898 @@
{
"accept": "принимать",
"accepted an invitation": "принял приглашение",
"accepted the invitation for": "принял приглашение на",
"Account": "Аккаунт",
"Add email address": "Добавить email адрес",
"Add phone number": "Добавить телефонный номер",
"Admin": "Admin",
"Advanced": "Дополнительно",
"Algorithm": "Алгоритм",
"all room members": "все участники комнаты",
"all room members, from the point they are invited": "все участники комнаты, с момента приглашения",
"all room members, from the point they joined": "все участники комнаты, с момента вступления",
"an address": "адрес",
"and": "и",
"An email has been sent to": "Email был отправлен",
"A new password must be entered.": "Введите новый пароль.",
"answered the call.": "принятый звонок.",
"anyone": "кто угодно",
"Anyone who knows the room's link, apart from guests": "Любой, кто знает ссылку на комнату, кроме гостей",
"Anyone who knows the room's link, including guests": "Любой, кто знает ссылку комнаты, включая гостей",
"Are you sure you want to reject the invitation?": "Вы уверены что вы хотите отклонить приглашение?",
"Are you sure you want to upload the following files?": "Вы уверены что вы хотите закачать следующий файл?",
"banned": "banned",
"Banned users": "Запрещенный пользователь",
"Bans user with given id": "Запретить пользователя с определенным id",
"Blacklisted": "В черный список",
"Bug Report": "Отчет ошибок",
"Bulk Options": "Объемные параметры",
"Can't load user settings": "Не может загрузить настройки пользователя",
"changed avatar": "изменен аватар",
"changed name": "измененное имя",
"changed their display name from": "changed their display name from",
"changed their profile picture": "changed their profile picture",
"changed the power level of": "changed the power level of",
"changed the room name to": "changed the room name to",
"changed the topic to": "changed the topic to",
"Changes to who can read history will only apply to future messages in this room": "Изменения того, кто может прочитать историю, будут только относиться к будущим сообщениям в этой комнате",
"Changes your display nickname": "Изменяет Ваш псевдоним",
"Claimed Ed25519 fingerprint key": "Требуемый Ed25519 ключ цифрового отпечатка",
"Clear Cache and Reload": "Очистить кэш и перезагрузить",
"Clear Cache": "Очистить кэш",
"Click here": "Нажать здесь",
"Click here to fix": "Нажать здесь для фиксации",
"Commands": "Команды",
"Confirm your new password": "Подтвердите ваш новый пароль",
"Continue": "Продолжить",
"Could not connect to the integration server": "Не может подключится к серверу интеграции",
"Create an account": "Создайте учётную запись",
"Create Room": "Создайте Комнату",
"Cryptography": "Шифрование",
"Curve25519 identity key": "Curve25519 идентификационный ключ",
"Deactivate Account": "Деактивировать Учётную запись",
"Deactivate my account": "Деактивировать мою учётную запись",
"decline": "отказаться",
"Decryption error": "Ошибка дешифрования",
"Default": "Default",
"demote": "понижать",
"Deops user with given id": "Deops пользователь с данным id",
"Device ID": "Устройство ID",
"Devices will not yet be able to decrypt history from before they joined the room": "Устройство еще не будет в состоянии дешифровать историю, до присоединения к комнате",
"Direct Chat": "Персональное сообщение",
"Disable inline URL previews by default": "Отключить встроенные предварительные просмотры URL по умолчанию",
"Display name": "Отображаемое имя",
"Displays action": "Отображение действий",
"Ed25519 fingerprint": "Ed25519 fingerprint",
"Email Address": "Email адрес",
"Email, name or matrix ID": "Email, имя или matrix ID",
"Emoji": "Смайлы",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Зашифрованные сообщения не будут видимы в клиентах, которые еще не подключили шифрование",
"Encrypted room": "Зашифрованная комната",
"ended the call.": "ended the call.",
"End-to-end encryption information": "Информация сквозного шифрования (e2e)",
"End-to-end encryption is in beta and may not be reliable": "Сквозное шифрование (e2e) в бета-версии и не может быть надежным",
"Error": "Ошибка",
"Event information": "Event information",
"Export E2E room keys": "Экспорт E2E ключей комнаты",
"Failed to change password. Is your password correct?": "Не удалось изменить пароль. Ваш пароль правильный?",
"Failed to forget room": "Не удалось забыть комнату",
"Failed to leave room": "Не удалось выйти из комнаты",
"Failed to reject invitation": "Не удалось отклонить приглашение",
"Failed to send email": "Не удалось отослать email",
"Failed to unban": "Не удалось отменить запрет",
"Failed to upload file": "Не удалось закачать файл",
"Favourite": "Избранное",
"favourite": "фаворит",
"Favourites": "Избранное",
"Filter room members": "Фильтр участников комнаты",
"Forget room": "Забыть комнату",
"Forgot your password?": "Вы забыли пароль?",
"For security, this session has been signed out. Please sign in again.": "Для обеспечения безопасности эта сессия была завершена. Войдите в систему еще раз.",
"Found a bug?": "Нашли ошибку?",
"had": "имеет",
"Hangup": "Отключение",
"Historical": "История",
"Homeserver is": "Домашний сервер является",
"Identity Server is": "Регистрационный сервер",
"I have verified my email address": "Я проверил мой адрес электронной почты",
"Import E2E room keys": "Импортировать E2E ключ комнаты",
"Invalid Email Address": "Недействительный адрес электронной почты",
"invited": "invited",
"Invite new room members": "Пригласить новых учасников в комнату",
"Invites": "Приглашать",
"Invites user with given id to current room": "Пригласить пользователя с данным id в текущую комнату",
"is a": "является",
"Sign in with": "Я хочу регистрироваться с",
"joined and left": "присоединенный и оставленный",
"joined": "присоединенный",
"joined the room": "joined the room",
"Joins room with given alias": "Присоединяется к комнате с данным псевдонимом",
"Kicks user with given id": "Кик пользователя с заданным id",
"Labs": "Лаборатория",
"Leave room": "Уйти из комнаты",
"left and rejoined": "Покинуть и переподключится",
"left": "покинуть",
"left the room": "left the room",
"Logged in as": "Зарегистрированный как",
"Login as guest": "Вход в систему как гость",
"Logout": "Выход из системы",
"Low priority": "Низкий приоритет",
"made future room history visible to": "made future room history visible to",
"Manage Integrations": "Управление интеграциями",
"Members only": "Только участники",
"Mobile phone number": "Номер мобильного телефона",
"Moderator": "Ведущий",
"my Matrix ID": "мой Matrix ID",
"Name": "Имя",
"Never send encrypted messages to unverified devices from this device": "Никогда не отправлять зашифрованные сообщения на неверифицированные устроства с этого устройства",
"Never send encrypted messages to unverified devices in this room from this device": "Никогда не отправляйте зашифрованные сообщения в непроверенные устройства в этой комнате из этого устройства",
"New password": "Новый пароль",
"New passwords must match each other.": "Новые пароли должны соответствовать друг другу.",
"none": "никто",
"Notifications": "Уведомления",
" (not supported by this browser)": " (not supported by this browser)",
"<not supported>": "<не поддерживаемый>",
"NOT verified": "НЕ проверенный",
"No users have specific privileges in this room": "Ни у каких пользователей нет специальных полномочий в этой комнате",
"olm version": "olm версия",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Как только шифрование включено для комнаты, оно не может быть выключено снова (на данный момент)",
"or": "или",
"other": "другой",
"others": "другие",
"Password": "Пароль",
"People": "Люди",
"Permissions": "Разрешение",
"Phone": "Телефон",
"placed a": "placed a",
"Please Register": "Пожалуйста, зарегистрируйтесь",
"rejected the invitation.": "rejected the invitation.",
"removed their display name": "removed their display name",
"removed their profile picture": "removed their profile picture",
"Remove": "Удалить",
"requested a VoIP conference": "requested a VoIP conference",
"Return to login screen": "Return to login screen",
"Send Reset Email": "Send Reset Email",
"sent an image": "отправил изображение",
"sent an invitation to": "sent an invitation to",
"set a profile picture": "set a profile picture",
"set their display name to": "set their display name to",
"Settings": "Настройки",
"Start a chat": "Start a chat",
"Start Chat": "Start Chat",
"tag as": "tag as",
"These are experimental features that may break in unexpected ways. Use with caution": "These are experimental features that may break in unexpected ways. Use with caution",
"To send events of type": "Для отправки типа событий",
"To send messages": "Отправить сообщения",
"turned on end-to-end encryption (algorithm": "turned on end-to-end encryption (algorithm",
"Unable to add email address": "Невозможно добавить email адрес",
"Unable to remove contact information": "Невозможно удалить контактную информацию",
"Unable to verify email address.": "Невозможно проверить адрес электронной почты.",
"Unban": "Отменить запрет",
"Unencrypted room": "Незашифрованная комната",
"unencrypted": "незашифрованно",
"unknown device": "неизвесное устройство",
"unknown error code": "неизвестная ошибка",
"unknown": "неизвестно",
"Upload avatar": "Загрузить аватар",
"uploaded a file": "загруженный файл",
"Upload Files": "Загрузка файлов",
"Upload file": "Загрузка файла",
"User ID": "ID пользователя",
"User Interface": "Пользовательский интерфейс",
"User name": "Имя пользователя",
"Users": "Пользователи",
"User": "Пользователь",
"Verification Pending": "Ожидание проверки",
"Verification": "Проверка",
"verified": "проверенный",
"Video call": "Видио вызов",
"Voice call": "Голосовой вызов",
"VoIP conference finished.": "VoIP конференция закончилась.",
"VoIP conference started.": "VoIP Конференция стартовала.",
"(warning: cannot be disabled again!)": "(предупреждение: не может быть отключено!)",
"Warning!": "Предупреждение!",
"was banned": "запрещен",
"was invited": "приглашенный",
"was kicked": "выброшен",
"was unbanned": "незапрещенный",
"was": "был",
"were": "быть",
"Who can access this room?": "Кто может получить доступ к этой комнате?",
"Who can read history?": "Кто может читать историю?",
"Who would you like to add to this room?": "Кого бы вы хотели пригласить в эту комнату?",
"Who would you like to communicate with?": "С кем хотели бы Вы связываться?",
"withdrawn": "уходить",
"Would you like to": "Хотели бы Вы",
"You do not have permission to post to this room": "У Вас нет разрешения писать в эту комнату",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "Вы вышли из всех устройств и не будет больше получать push-уведомления. Чтобы повторно включить уведомления, войдите в систему еще раз для каждого устройства",
"You have no visible notifications": "У Вас нет видимых уведомлений",
"you must be a": "Вы должны быть",
"Your password has been reset": "Ваш пароль был изменен",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Ваш пароль был успешно изменен. Вы не будете получать Push-уведомления о других устройствах, пока не войдете обратно на них",
"You should not yet trust it to secure data": "Вы еще не должны доверять этому защиту данных",
"en": "Английский",
"pt-br": "Португальский Бразилия",
"de": "Немецкий",
"da": "Датский",
"ru": "Русский",
"%(targetName)s accepted an invitation.": "%(targetName)s принял приглашение.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s принял приглашение от %(displayName)s.",
"Resend all": "Переслать снова всем",
"cancel all": "отменить всем",
"Active call": "Активный звонок",
"%(names)s and %(lastPerson)s are typing": "%(names)s и %(lastPerson)s печатает",
"%(names)s and one other are typing": "%(names)s и другой печатают",
"%(names)s and %(count)s others are typing": "%(names)s и %(count)s другие печатают",
"%(senderName)s answered the call.": "%(senderName)s ответил на звонок.",
"%(senderName)s banned %(targetName)s.": "%(senderName)s запрещенный %(targetName)s.",
"Call Timeout": "Время ожидания вызова",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s их имя измененное с %(oldDisplayName)s на %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s изменил фото профиля.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s уровень мощности изменен на %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s имя комнаты измененно на %(roomName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s измененная тема на %(topic)s.",
"Conference call failed.": "Конференц-вызов прервался.",
"Conference calling is in development and may not be reliable.": "Конференц-вызов находится в процессе и может не быть надежным.",
"Conference calls are not supported in encrypted rooms": "Конференц-вызовы не поддерживаются в зашифрованных комнатах",
"Conference calls are not supported in this client": "Конференц-вызовы не поддерживаются в этом клиенте",
"/ddg is not a command": "/ddg не команда",
"Drop here %(toAction)s": "Вставить сюда: %(toAction)s",
"Drop here to tag %(section)s": "Вставить здесь для тега %(section)s",
"%(senderName)s ended the call.": "%(senderName)s прекратил звонок.",
"Existing Call": "Существующий вызов",
"Failed to lookup current room": "Не удалось выполнить поиск текущий комнаты",
"Failed to send request.": "Не удалось выслать запрос.",
"Failed to set up conference call": "Не удалось установить конференц-вызов",
"Failed to verify email address: make sure you clicked the link in the email": "Не удалось подтвердить email-адрес: убедитесь что вы щелкнули по ссылке электронной почты",
"Failure to create room": "Не удалось создать комнату",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s из %(fromPowerLevel)s до %(toPowerLevel)s",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Гостевые пользователи не могут создавать новые комнаты. Зарегистрируйтесь для создания комнаты и чата.",
"click to reveal": "нажать для открытия",
"%(senderName)s invited %(targetName)s.": "%(senderName)s приглашает %(targetName)s.",
"%(displayName)s is typing": "%(displayName)s вводит текст",
"%(targetName)s joined the room.": "%(targetName)s вошёл в комнату.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s выкинул %(targetName)s.",
"%(targetName)s left the room.": "%(targetName)s покинул комнату.",
"%(senderName)s made future room history visible to": "%(senderName)s сделал видимой для всех будущую историю комнаты",
"Missing room_id in request": "Отсутствует room_id в запросе",
"Missing user_id in request": "Отсутствует user_id в запросе",
"Must be viewing a room": "Комната должна быть посищена",
"New Composer & Autocomplete": "Новый едитор & Автозаполнение",
"(not supported by this browser)": "(не поддерживаемый этим браузером)",
"af": "Африкаанс",
"ar-ae": "Арабский (О.А.Е)",
"ar-bh": "Арабский (Бахрейн)",
"ar-dz": "Арабский (Алжыр)",
"ar-eg": "Арабский (Египет)",
"ar-iq": "Арабский (Ирак)",
"ar-jo": "Арабский (Иордания)",
"ar-kw": "Арабский (Кувейт)",
"ar-lb": "Арабский (Ливан)",
"ar-ly": "Арабский (Ливия)",
"ar-ma": "Арабский (Марокко)",
"ar-om": "Арабский (Оман)",
"ar-qa": "Арабский (Катар)",
"ar-sa": "Арабский (Саудовская Аравия)",
"ar-sy": "Арабский (Сирия)",
"ar-tn": "Арабский (Тунис)",
"ar-ye": "Арабский (Йемен)",
"be": "Беларуский",
"bg": "Болгарский",
"ca": "Каталанский",
"cs": "Чешский",
"de-at": "Немецкий (Австрия)",
"de-ch": "Немецкий (Швейцария)",
"de-li": "Немецкий (Лихтенштейн)",
"de-lu": "Немецкий (Люксембург)",
"el": "Гречиский",
"en-au": "Английский (Австралия)",
"en-bz": "Английский (Белиз)",
"en-ca": "Английский (Канада)",
"en-gb": "Английский (UK)",
"en-ie": "Английский (Ирландия)",
"en-jm": "Английский (Ямайка)",
"en-nz": "Английский (Новая Зеландия)",
"en-tt": "Английский (Тринидад)",
"en-us": "Английский (US)",
"en-za": "Английский (Южная Африка)",
"es-ar": "Испанский (Аргентина)",
"es-bo": "Испанский (Боливия)",
"es-cl": "Испанский (Чили)",
"es-co": "Испанский (Колумбия)",
"es-cr": "Испанский (Коста Рика)",
"es-do": "Испанский (Дом. Республика)",
"es-ec": "Испанский (Еквадор)",
"es-gt": "Испанский (Гватемала)",
"es-hn": "Испанский (Гондурас)",
"es-mx": "Испанский (Мексика)",
"es-ni": "Испанский (Никарагуа)",
"es-pa": "Испанский (Панама)",
"et": "Эстонский",
"fi": "Финский",
"fr": "Французкий",
"hr": "Хорватский",
"it": "Итальянский",
"ja": "Японский",
"pl": "Польский",
"pt": "Португальский",
"ru-mo": "Русский (Молдавская Республика)",
"ro": "Румынский",
"uk": "Украинский",
"now. You can also select individual messages to resend or cancel.": "теперь. Вы можете также выбрать отдельные сообщения, чтобы снова послать или отменить.",
"Auto-complete": "Автозаполнение",
"Error changing language": "Ошибка изменения языка",
"Riot was unable to find the correct Data for the selected Language.": "Riot был неспособен найти правельные данные для выбранного языка.",
"Connectivity to the server has been lost.": "Связь с сервером была потеряна.",
"Sent messages will be stored until your connection has returned.": "Отправленные сообщения будут храниться, пока Ваше соединение не возобновиться.",
"There are no visible files in this room": "В этой комнате нет никаких видимых файлов",
"This doesn't look like a valid phone number.": "Это не похоже на допустимый телефонный номер.",
"Missing password.": "Пароль отсутствует.",
"Set a display name:": "Настроить отображаемое имя:",
"Passwords don't match.": "Пароли не совпадают.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Пароль слишком короткий (min %(MIN_PASSWORD_LENGTH)s).",
"This doesn't look like a valid email address.": "Это не похоже на допустимый email адрес.",
"This server does not support authentication with a phone number.": "Этот сервер не поддерживает аутентификацию с телефонным номером.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "Имена пользователей могут только содержать буквы, числа, точки, дефисы и подчеркивания.",
"An unknown error occurred.": "Произошла неизвестная ошибка.",
"I already have an account": "У меня уже есть учетная запись",
"An error occurred: %(error_string)s": "Произошла ошибка: %(error_string)s",
"Topic": "Тема",
"Make this room private": "Сделать эту комнату частной",
"Share message history with new users": "Поделись историей сообщений с новыми учасниками",
"Encrypt room": "Зашифровать комнату",
"es-pe": "Испанский (Перу)",
"hu": "Венгерский",
"nl": "Датский",
"no": "Норвежский",
"sv": "Шведский",
"th": "Тайландский",
"vi": "Ветнамский",
"Monday": "Понедельник",
"Tuesday": "Вторник",
"Wednesday": "Среда",
"Thursday": "Четверг",
"Friday": "Пятница",
"Saturday": "Суббота",
"Sunday": "Воскресенье",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Upload an avatar:": "Загрузите аватар:",
"You need to be logged in.": "Вы должны быть зарегистрированы.",
"You need to be able to invite users to do that.": "Вам необходимо пригласить пользователей чтобы сделать это.",
"You cannot place VoIP calls in this browser.": "Вы не можете сделать вызовы VoIP с этим браузером.",
"You are already in a call.": "Вы уже находитесь в разговоре.",
"You're not in any rooms yet! Press": "Вы еще не находитесь ни в каких комнатах! Нажать",
"You are trying to access %(roomName)s.": "Вы пытаетесь получить доступ %(roomName)s.",
"You cannot place a call with yourself.": "Вы не можете позвонить самим себе.",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s анулировал %(targetName)s's преглашение.",
"Sep": "Сен.",
"Jan": "Янв.",
"Feb": "Фев.",
"Mar": "Мар.",
"Apr": "Апр.",
"May": "Май",
"Jun": "Июн.",
"Jul": "Июл.",
"Aug": "Авг.",
"Oct": "Окт.",
"Nov": "Ноя.",
"Dec": "Дек.",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(time)s",
"Mon": "Пн",
"Sun": "Вс",
"Tue": "Вт",
"Wed": "Ср",
"Thu": "Чт",
"Fri": "Пя",
"Sat": "Сб",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "Вам необходимо снова войти в генерировать сквозное шифрование (е2е) ключей для этого устройства и предоставить публичный ключ Вашему домашнему серверу. Это после выключения; приносим извинения за причиненные неудобства.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Ваш адрес электронной почты, кажется, не связан с Matrix ID на этом Homeserver.",
"to start a chat with someone": "Начать чат с кем-то",
"to tag direct chat": "отметить прямой чат",
"To use it, just wait for autocomplete results to load and tab through them.": "Для его использования просто подождите загрузки результатов автозаполнения и нажимайте Tab для навигации.",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s включил сквозное шифрование (algorithm %(algorithm)s).",
"Unable to restore previous session": "Невозможно востановить предыдущий сеанс",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s запрет отменен %(targetName)s.",
"Unable to capture screen": "Невозможно записать снимок экрана",
"Unable to enable Notifications": "Невозможно включить уведомления",
"Upload Failed": "Неудавшаяся загрузка",
"Usage": "Использование",
"Use with caution": "Использовать осторожно",
"VoIP is unsupported": "VoIP не поддерживается",
"es-pr": "Испанский (Пуэрто-Рико)",
"es-py": "Испанский язык (Парагвай)",
"es": "Испанский (Испания)",
"es-sv": "Испанский (Сальвадор)",
"es-uy": "Испанский (Уругвай)",
"es-ve": "Испанский (Венесуэла)",
"fa": "Фарси",
"fo": "Фарезский",
"fr-be": "Французский (Бельгия)",
"fr-ca": "Французский (Канада)",
"fr-ch": "Французский (Швейцария)",
"ga": "Ирландский",
"he": "Иврит",
"hi": "Хинди",
"id": "Индонезийский",
"is": "Исландский",
"ji": "Идиш",
"lt": "Литовкий",
"lv": "Латвийский",
"ms": "Малайцы",
"mt": "Мальтийский",
"nl-be": "Голландский (Бельгия)",
"rm": "Ретороманский",
"sb": "Вендский",
"sk": "Словацкий",
"sl": "Словенский",
"sq": "Албанский",
"sr": "Сербский",
"sv-fi": "Шведский (Финляндия)",
"sz": "Саами (лопарский)",
"tn": "Тсвана",
"tr": "Турецкий",
"ts": "Тсонга",
"ur": "Урду",
"ve": "Венда",
"xh": "Коса",
"zh-cn": "Китайский (PRC)",
"zh-sg": "Китайский (Сингапур)",
"zh-tw": "Китайский (Тайвань)",
"zu": "Зулусский",
"eu": "Баскский",
"fr-lu": "Французский (Люксембург)",
"gd": "Гэльский (Шотландия)",
"it-ch": "Итальянский (Швейцария)",
"ko": "Корейский",
"mk": "Македонский (FYROM)",
"ro-mo": "Румынский (Республика Молдова)",
"sx": "Суту",
"zh-hk": "Китайский (Гонконг)",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "На +%(msisdn)s было отправлено текстовое сообщение. Пожалуйста, введите проверочный код из него",
"and %(overflowCount)s others...": "и %(overflowCount)s других...",
"Are you sure?": "Вы уверены?",
"Autoplay GIFs and videos": "Проигрывать GIF и видео автоматически",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Невозможно соединиться с домашним сервером - проверьте своё соединение и убедитесь, что <a>SSL-сертификат вашего домашнего сервера</a> включён в доверяемые.",
"changing room on a RoomView is not supported": "изменение комнаты в RoomView не поддерживается",
"Click to mute audio": "Выключить звук",
"Click to mute video": "Выключить звук у видео",
"Click to unmute video": "Включить звук у видео",
"Click to unmute audio": "Включить звук",
"Decrypt %(text)s": "Расшифровать %(text)s",
"Delete": "Удалить",
"Devices": "Устройства",
"Direct chats": "Личные чаты",
"Disinvite": "Отозвать приглашение",
"Don't send typing notifications": "Не оповещать, когда я печатаю",
"Download %(text)s": "Загрузить %(text)s",
"Enable encryption": "Включить шифрование",
"Enter Code": "Ввести код",
"Failed to ban user": "Не удалось забанить пользователя",
"Failed to change power level": "Не удалось изменить уровень привилегий",
"Failed to delete device": "Не удалось удалить устройство",
"Failed to forget room %(errCode)s": "Не удалось забыть комнату %(errCode)s",
"Failed to join room": "Не удалось присоединиться к комнате",
"Failed to join the room": "Не удалось войти в комнату",
"A registered account is required for this action": "Необходима зарегистрированная учетная запись для этого действия",
"Access Token:": "Токен:",
"Always show message timestamps": "Всегда показывать время сообщения",
"Authentication": "Авторизация",
"olm version:": "версия olm:",
"%(items)s and %(remaining)s others": "%(items)s и другие %(remaining)s",
"%(items)s and one other": "%(items)s и ещё один",
"%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s",
"and one other...": "и ещё один...",
"An error has occurred.": "Произошла ошибка.",
"Attachment": "Вложение",
"Ban": "Запретить",
"Change Password": "Сменить пароль",
"Command error": "Ошибка команды",
"Confirm password": "Подтвердить пароль",
"Current password": "Текущий пароль",
"Email": "Электронная почта",
"Failed to kick": "Не удалось выгнать",
"Failed to load timeline position": "Не удалось загрузить позицию таймлайна",
"Failed to mute user": "Не удалось заглушить",
"Failed to reject invite": "Не удалось отклонить приглашение",
"Failed to save settings": "Не удалось сохранить настройки",
"Failed to set display name": "Не удалось установить отображаемое имя",
"Failed to toggle moderator status": "Не удалось изменить статус модератора",
"Fill screen": "Заполнить экран",
"Guest users can't upload files. Please register to upload.": "Гости не могут посылать файлы. Пожалуйста, зарегистрируйтесь для отправки.",
"Hide read receipts": "Скрыть отметки о прочтении",
"Hide Text Formatting Toolbar": "Скрыть панель форматирования текста",
"Incorrect verification code": "Неверный код подтверждения",
"Interface Language": "Язык интерфейса",
"Invalid alias format": "Неверный формат привязки",
"Invalid address format": "Неверный формат адреса",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' неверный формат для адреса",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' неверный формат для привязки",
"Join Room": "Войти в комнату",
"Kick": "Выгнать",
"Level": "Уровень",
"Local addresses for this room:": "Местный адрес этой комнаты:",
"Markdown is disabled": "Markdown отключен",
"Markdown is enabled": "Markdown включен",
"matrix-react-sdk version:": "версия matrix-react-sdk:",
"Never send encrypted messages to unverified devices in this room": "Никогда не отправлять зашифрованные сообщения непроверенным устройствам в этой комнате",
"New address (e.g. #foo:%(localDomain)s)": "Новый адрес (например, #foo:%(localDomain)s)",
"New passwords don't match": "Пароли не совпадают",
"not set": "не установлено",
"not specified": "не указано",
"No devices with registered encryption keys": "Нет устройств с зарегистрированными ключами шифрования",
"No more results": "Нет больше результатов",
"No results": "Нет результатов",
"OK": "ОК",
"Only people who have been invited": "Только приглашённые люди",
"Passwords can't be empty": "Пароли не могут быть пустыми",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s выполнил %(callType)s вызов.",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Пожалуйста, проверьте вашу электронную почту и нажмите в ней ссылку. По завершении нажмите продолжить.",
"Power level must be positive integer.": "Уровень силы должен быть положительным числом.",
"Press": "Нажать",
"Profile": "Профиль",
"Reason": "Причина",
"Registration required": "Требуется регистрация",
"rejected": "отклонено",
"%(targetName)s rejected the invitation.": "%(targetName)s отклонил приглашение.",
"Reject invitation": "Отклонить приглашение",
"Remove Contact Information?": "Удалить контактную информацию?",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s убрал своё отображаемое имя (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s убрал своё изображение.",
"%(senderName)s requested a VoIP conference.": "%(senderName)s запросил голосовую конференц-связь.",
"Report it": "Сообщить об этом",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "На данный момент сброс пароля сбросит все ключи шифрования на всех устройствах, зашифрованная история общения будет нечитаема, если вы сперва не скачаете ваши ключи от комнаты и не загрузите их после. В будущем это будет улучшено.",
"restore": "восстановить",
"Return to app": "Вернуться в приложение",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot не имеет права для отправки вам уведомлений, пожалуйста, проверьте настройки вашего браузера",
"Riot was not given permission to send notifications - please try again": "Riot не получил разрешение для отправки уведомлений, пожалуйста, попробуйте снова",
"riot-web version:": "версия riot-web:",
"Room %(roomId)s not visible": "Комната %(roomId)s невидима",
"Room Colour": "Цвет комнаты",
"Room name (optional)": "Имя комнаты (необязательно)",
"Rooms": "Комнаты",
"Scroll to bottom of page": "Прокрутить вниз страницы",
"Scroll to unread messages": "Прокрутить к непрочитанным сообщениям",
"Search": "Поиск",
"Search failed": "Поиск не удался",
"Send a message (unencrypted)": "Отправить сообщение (не зашифровано)",
"Send an encrypted message": "Отправить зашифрованное сообщение",
"Sender device information": "Информация об устройстве отправителя",
"Send Invites": "Отправить приглашения",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s отправил изображение.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s отправил приглашение для %(targetDisplayName)s войти в комнату.",
"sent a video": "отправил видео",
"Show panel": "Показать панель",
"Sign in": "Войти",
"Sign out": "Выйти",
"since the point in time of selecting this option": "с момента выбора этой настройки",
"since they joined": "с момента входа",
"since they were invited": "с момента приглашения",
"Some of your messages have not been sent.": "Некоторые из ваших сообщений не были отправлены.",
"Someone": "Кто-то",
"Submit": "Отправить",
"Success": "Успех",
"tag as %(tagName)s": "отметить как %(tagName)s",
"tag direct chat": "отметить прямое общение",
"The default role for new room members is": "Роль по умолчанию для новых участников комнаты",
"The main address for this room is": "Основной адрес для этой комнаты",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Это действие не может быть выполнено гостем. Пожалуйста, зарегистрируйтесь для этого.",
"This email address is already in use": "Этот адрес электронной почты уже используется",
"This email address was not found": "Этот адрес электронной почты не найден",
"The email address linked to your account must be entered.": "Необходимо ввести адрес электронной почты, связанный с вашей учётной записью.",
"The file '%(fileName)s' failed to upload": "Не удалось загрузить файл '%(fileName)s'",
"The remote side failed to pick up": "Удалённая сторона не смогла ответить",
"This room has no local addresses": "Эта комната не имеет местного адреса",
"This room is not recognised.": "Эта комната не опознана.",
"These are experimental features that may break in unexpected ways": "Это экспериментальные функции, которые могут неожиданным образом вызывать ошибки",
"This doesn't appear to be a valid email address": "Не похоже, что это правильный адрес электронной почты",
"This is a preview of this room. Room interactions have been disabled": "Это просмотр данной комнаты. Взаимодействия с ней были отключены",
"This phone number is already in use": "Этот телефонный номер уже используется",
"This room's internal ID is": "Внутренний ID этой комнаты",
"times": "раз",
"to demote": "для понижения",
"to favourite": "для избранного",
"to restore": "восстановить",
"Turn Markdown off": "Выключить Markdown",
"Turn Markdown on": "Включить Markdown",
"Unknown command": "Неизвестная команда",
"Unknown room %(roomId)s": "Неизвестная комната %(roomId)s",
"You have been invited to join this room by %(inviterName)s": "Вы были приглашены войти в эту комнату от %(inviterName)s",
"You seem to be uploading files, are you sure you want to quit?": "Похоже вы передаёте файлы, вы уверены, что хотите выйти?",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s",
"Make Moderator": "Сделать модератором",
"Room": "Комната",
"Cancel": "Отмена",
"bold": "жирный",
"italic": "наклонный",
"strike": "перечёркнутый",
"underline": "подчёркнутый",
"code": "код",
"quote": "цитата",
"bullet": "пункт",
"numbullet": "нумерация",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sвошли %(repeats)s раз",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sвошёл %(repeats)s раз",
"%(severalUsers)sjoined": "%(severalUsers)sвошли",
"%(oneUser)sjoined": "%(oneUser)sвошёл",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sушли %(repeats)s раз",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sушли %(repeats)s раз",
"%(severalUsers)sleft": "%(severalUsers)sушли",
"%(oneUser)sleft": "%(oneUser)sушёл",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sвошли и ушли %(repeats)s раз",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sвошёл и ушёл %(repeats)s раз",
"%(severalUsers)sjoined and left": "%(severalUsers)sвошли и ушли",
"%(oneUser)sjoined and left": "%(oneUser)sвошёл и ушёл",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sушли и перезашли %(repeats)s раз",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)s ушёл и перезашёл %(repeats)s раз",
"%(severalUsers)sleft and rejoined": "%(severalUsers)sушли и перезашли",
"%(oneUser)sleft and rejoined": "%(oneUser)sушёл и перезашёл",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)sотклонили приглашения %(repeats)s раз",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)sотклонил приглашения %(repeats)s раз",
"%(severalUsers)srejected their invitations": "%(severalUsers)sотклонили приглашения",
"%(oneUser)srejected their invitation": "%(oneUser)sотклонил приглашение",
"were invited %(repeats)s times": "были приглашёны %(repeats)s раз",
"was invited %(repeats)s times": "был приглашён %(repeats)s раз",
"were invited": "были приглашёны",
"were banned %(repeats)s times": "были запрещёны %(repeats)s раз",
"was banned %(repeats)s times": "был запрещён %(repeats)s раз",
"were banned": "были запрещёны",
"were unbanned %(repeats)s times": "были разрешены %(repeats)s раз",
"was unbanned %(repeats)s times": "был разрешён %(repeats)s раз",
"were unbanned": "были разрешены",
"were kicked %(repeats)s times": "были выгнаны %(repeats)s раз",
"was kicked %(repeats)s times": "был выгнан %(repeats)s раз",
"were kicked": "были выгнаны",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)sизменили имена %(repeats)s раз",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)sизменил имя %(repeats)s раз",
"%(severalUsers)schanged their name": "%(severalUsers)sизменили имя",
"%(oneUser)schanged their name": "%(oneUser)sизменил имя",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)sизменили своё изображение %(repeats)s раз",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)sизменил своё изображение %(repeats)s раз",
"%(severalUsers)schanged their avatar": "%(severalUsers)sизменили своё изображение",
"%(oneUser)schanged their avatar": "%(oneUser)sизменил своё изображение",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Не возможно подключиться к серверу через HTTP, когда в строке браузера HTTPS. Используйте HTTPS или <a>включив небезопасные скрипты</a>.",
"Dismiss": "Отказ",
"Custom Server Options": "Расширенные настройки сервера",
"Mute": "Беззвучный",
"Operation failed": "Действие не удалось",
"powered by Matrix": "управляемый с Matrix",
"Add a topic": "Добавить тему",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Времея отображать в 12 часовом формате (напр. 2:30pm)",
"Use compact timeline layout": "Компактное отображение",
"Hide removed messages": "Скрыть удаленное сообщение",
"No Microphones detected": "Микрофоны не обнаружены",
"Unknown devices": "Незнакомое устройство",
"Camera": "Камера",
"Microphone": "Микрофон",
"Desktop specific": "Специфический десктоп",
"Start automatically after system login": "Автостарт после входа в систему",
"Analytics": "Аналитика",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot собирет анонимные данные, чтобы улутшыть эту програму.",
"Opt out of analytics": "Подтвердить отказ передачи аналитических данных",
"Logged in as:": "Зарегестрирован как:",
"Default Device": "Стандартное устройство",
"No Webcams detected": "Веб-камера не обнаружена",
"VoIP": "VoIP",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Для обеспечения безопасности выход из системы удалит все ключи шифроования из этого браузера. Если вы хотите иметь возможность расшифровать переписку в будущем - вы должны экспортирвать ключи вручную.",
"Guest access is disabled on this Home Server.": "Гостевой доступ отключен на этом сервере.",
"Guests can't set avatars. Please register.": "Гости не могут устанавливать аватар. Пожалуйста, зарегистрируйтесь.",
"Guests can't use labs features. Please register.": "Гости не могут использовать экспериментальные возможности. Пожалуйста, зарегистрируйтесь.",
"Guests cannot join this room even if explicitly invited.": "Гости не могут заходить в эту комнату если не были приглашены.",
"Missing Media Permissions, click here to request.": "Отсутствуют разрешения, нажмите для запроса.",
"No media permissions": "Нет медиа разрешений",
"You may need to manually permit Riot to access your microphone/webcam": "Вам необходимо предоставить Riot доступ к микрофону или веб-камере вручную",
"Anyone": "Все",
"Are you sure you want to leave the room '%(roomName)s'?": "Вы уверены, что хотите покинуть '%(roomName)s'?",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s удалил имя комнаты.",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Смена пароля также сбросит все ключи шифрования на всех устройствах, сделав зашифрованную историю недоступной, если только вы сначала не экспортируете ключи шифрования и не импортируете их потом. В будущем это будет исправлено.",
"Custom level": "Пользовательский уровень",
"(default: %(userName)s)": "(по-умолчанию: %(userName)s)",
"Device already verified!": "Устройство уже верифицировано!",
"Device ID:": "ID устройства:",
"device id: ": "id устройства: ",
"Device key:": "Ключ устройства:",
"disabled": "отключено",
"Disable markdown formatting": "Отключить форматирование Markdown",
"Email address": "Адрес email",
"Email address (optional)": "Адрем email (не обязательно)",
"enabled": "включено",
"Error decrypting attachment": "Ошибка расшифровки файла",
"Export": "Экспорт",
"Failed to register as guest:": "Ошибка регистрации как гостя:",
"Failed to set avatar.": "Не удалось установить аватар.",
"Import": "Импорт",
"Incorrect username and/or password.": "Неверное имя пользователя и/или пароль.",
"Invalid file%(extra)s": "Неправильный файл%(extra)s",
"Invited": "Приглашен",
"Jump to first unread message.": "Перейти к первому непрочитанному сообщению.",
"List this room in %(domain)s's room directory?": "Показывать эту комнату в списке комнат %(domain)s?",
"Message not sent due to unknown devices being present": "Сообщение не было отправлено из-за присутствия неизвестного устройства",
"Mobile phone number (optional)": "Номер мобильного телефона (не обязательно)",
"Once you&#39;ve followed the link it contains, click below": "Как только вы пройдете по ссылке, нажмите на кнопку ниже",
"Password:": "Пароль:",
"Privacy warning": "Предупреждение приватности",
"Privileged Users": "Привилегированные пользователи",
"Revoke Moderator": "Снять модераторские права",
"Refer a friend to Riot:": "Расскажите другу о Riot:",
"Register": "Регистрация",
"Remote addresses for this room:": "Удаленные адреса для этой комнаты:",
"Remove %(threePid)s?": "Удалить %(threePid)s?",
"Results from DuckDuckGo": "Результаты от DuckDuckGo",
"Save": "Сохранить",
"Searches DuckDuckGo for results": "Ищет результаты через DuckDuckGo",
"Server error": "Ошибка сервера",
"Server may be unavailable or overloaded": "Сервер может быть недоступен или перегружен",
"Server may be unavailable, overloaded, or search timed out :(": "Сервер может быть недоступен, перегружен или поиск прекращен по тайм-ауту :(",
"Server may be unavailable, overloaded, or the file too big": "Сервер может быть недоступен, перегружен или размер файла слишком большой",
"Server may be unavailable, overloaded, or you hit a bug.": "Сервер может быть недоступен, перегружен или вы нашли баг.",
"Server unavailable, overloaded, or something else went wrong.": "Сервер может быть недоступен, перегружен или произошло что-то страшное.",
"Session ID": "ID сессии",
"%(senderName)s set a profile picture.": "%(senderName)s установил картинку профиля.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s установил отображаемое имя %(displayName)s.",
"Setting a user name will create a fresh account": "Установка имени пользователя создаст новую учетную запись",
"Signed Out": "Вышли",
"Sorry, this homeserver is using a login which is not recognised ": "Извините, этот Home Server использует логин, который не удалось распознать ",
"Tagged as: ": "Теги: ",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Ключ, предоставленный вами, совпадает с ключем, полученным от устройства %(userId)s с ID %(deviceId)s. Устройство помечено как верифицированное.",
"%(actionVerb)s this person?": "%(actionVerb)s этого пользователя?",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "Файл '%(fileName)s' превышает ограничение размера загрузок на этом Home Server'е",
"This Home Server does not support login using email address.": "Этот Home Server не поддерживает вход по адресу email.",
"There was a problem logging in.": "Возникла проблема входа в учетную запись.",
"The visibility of existing history will be unchanged": "Видимость текущей истории не будет изменена",
"this invitation?": "это приглашение?",
"This room is not accessible by remote Matrix servers": "Это комната закрыта для других серверов Matrix",
"To ban users": "Забанить пользователей",
"to browse the directory": "просматривать директорию",
"To configure the room": "Конфигурировать комнату",
"To invite users into the room": "Приглашать пользователей в комнату",
"to join the discussion": "присоединиться к дискуссии",
"To kick users": "Выгонять пользователей",
"To link to a room it must have": "Для создания ссылки на комнату она должна иметь",
"to make a room or": "создать комнату или",
"To remove other users' messages": "Удалять сообщения других пользователей",
"To reset your password, enter the email address linked to your account": "Чтобы сбросить ваш пароль введите адрес email, который используется аккаунтом",
"to tag as %(tagName)s": "отметить как %(tagName)s",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Вы попытались загрузить указанное сообщение в комнате, однако у вас нету разрешений для его просмотра.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Вы попытались загрузить указанное сообщение в комнате, однако сервер не смог его найти.",
"Unable to load device list": "Невозможно загрузить список устройств",
"Unknown (user, device) pair:": "Неизвестная пара пользователь-устройство:",
"Unmute": "Разглушить",
"Unrecognised command:": "Неизвестная команда:",
"Unrecognised room alias:": "Неизвестный псевдоним комнаты:",
"Verified key": "Верифицированный ключ",
"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!": "ВНИМАНИЕ: ОШИБКА ВЕРИФИКАЦИИ КЛЮЧЕЙ! Ключ для подписки устройства %(deviceId)s пользователя %(userId)s: \"%(fprint)s\", однако он не совпадает с предоставленным ключем \"%(fingerprint)s\". Это может означать перехват вашего канала коммуникации!",
"You have <a>disabled</a> URL previews by default.": "Предпросмотр ссылок <a>отключен</a> по-умолчанию.",
"You have <a>enabled</a> URL previews by default.": "Предпросмотр ссылок <a>включен</a> по-умолчанию.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Вы ввели неправильный адрес. Попробуйте использовать Matrix ID или адрес email.",
"You need to enter a user name.": "Необходимо ввести имя пользователя.",
"You seem to be in a call, are you sure you want to quit?": "Вы учавствуете в звонке, вы уверены, что хотите выйти?",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Вы не сможете отменить это действие так как даете пользователю такой же уровень доступа как и у вас.",
"Set a Display Name": "Установить отображаемое имя",
"(~%(searchCount)s results)": "(~%(searchCount)s результатов)",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)s отозвали свои приглашения %(repeats)s раз",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)s отозвал свои приглашения %(repeats)s раз",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)s отозвали свои приглашения",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)s отозвал свое приглашение",
"Please select the destination room for this message": "Выберите комнату назначения для этого сообщения",
"Options": "Настройки",
"Passphrases must match": "Пароли должны совпадать",
"Passphrase must not be empty": "Пароль не должен быть пустым",
"Export room keys": "Экспортировать ключи",
"Enter passphrase": "Введите пароль",
"Confirm passphrase": "Подтвердите пароль",
"Import room keys": "Импортировать ключи",
"File to import": "Файл для импорта",
"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.": "Экспортированный файл защищен паролем. Вы должны ввести этот пароль для расшифровки.",
"You must join the room to see its files": "Вы должны зайти в комнату для просмотра файлов",
"Reject all %(invitedRooms)s invites": "Отклонить все %(invitedRooms)s приглашения",
"Start new chat": "Начать новый чат",
"Guest users can't invite users. Please register.": "Гости не могут приглашать пользователей. Пожалуйста, зарегистрируйтесь.",
"Failed to invite": "Ошибка приглашения",
"Failed to invite user": "Ошибка приглашения пользователя",
"Failed to invite the following users to the %(roomName)s room:": "Ошибка приглашения следующих пользователей в %(roomName)s:",
"Confirm Removal": "Подтвердите удаление",
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Вы уверены, что хотите удалить этот эвент? Обратите внимание, что если это смена имени комнаты или топика, то удаление отменит это изменение.",
"Unknown error": "Неизвестная ошибка",
"Incorrect password": "Неправильный пароль",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Это сделает вашу учетную запись нерабочей. Вы не сможете зарегистрироваться снова с тем же ID.",
"This action is irreversible.": "Это действие необратимо.",
"To continue, please enter your password.": "Для продолжения введите ваш пароль.",
"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": "Имя устройства",
"Device key": "Ключ устройства",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Если совпадают, то нажмите кнопку верификации ниже. Если нет, то кто-то перехватил это устройство или ключ и вы, скорее всего, захотите внести его в черный список.",
"In future this verification process will be more sophisticated.": "В будущем процесс верификации будет усложнен.",
"Verify device": "Верифицировать устройство",
"I verify that the keys match": "Я верифицирую - ключи совпадают",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Обнаружена ошибка при восстановлении вашей предыдущей сессии. Вам необходимо зайти снова, шифрованные сообщения будут нечитаемы.",
"Unable to restore session": "Невозможно восстановить сессию",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Если вы использовали более новую версию Riot, то ваша сессия может быть несовместима с текущей. Закройте это окно и вернитесь к использованию более новой версии.",
"Continue anyway": "Все равно продолжить",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Отображаемое имя - это то, как вы отображаетесь в чате. Какое имя вы хотите?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Пока что вы вносите неверифицированные устройства в черный список автоматически. Для отправки сообщений на эти устройства вам необходимо их верифицировать.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Рекомендуется сначала верифицировать устройства для подтверждения их владения правильным пользователем, но вы можете отправить сообщение без верификации, если хотите.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" содержит неизвестные прежде устройства.",
"Unknown Address": "Неизвестный адрес",
"Unblacklist": "Удалить из черного списка",
"Blacklist": "Добавить в черный список",
"Unverify": "Убрать верификацию",
"Verify...": "Верифицировать...",
"ex. @bob:example.com": "например @bob:example.com",
"Add User": "Добавить пользователя",
"This Home Server would like to make sure you are not a robot": "Этот Home Server хочет удостовериться что вы не робот",
"Sign in with CAS": "Войти с помощью CAS",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Вы можете использовать пользовательские настройки сервера для использования другого Home Server'а при помощи указания его URL.",
"This allows you to use this app with an existing Matrix account on a different home server.": "Это позволяет использовать это приложение с существующей учетной записью на другом Home Server'е.",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Вы также можете указать пользовательский сервер идентификации, но это обычно ломает возможность общаться с пользователями с помощью адреса email.",
"Please check your email to continue registration.": "Проверьте свою почту для продолжения регистрации.",
"Token incorrect": "Неправильный токен",
"A text message has been sent to": "Текстовое сообщение было отправлено",
"Please enter the code it contains:": "Введите содержащийся код:",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Если вы не укажете адрес email, то вы не сможете сбросить свой пароль в будущем. Вы уверены?",
"You are registering with %(SelectedTeamName)s": "Вы регистрируетесь на %(SelectedTeamName)s",
"Default server": "Сервер по-умолчанию",
"Custom server": "Пользовательский сервер",
"Home server URL": "URL Home Server'а",
"Identity server URL": "URL сервера идентификации",
"What does this mean?": "Что это значит?",
"Error decrypting audio": "Ошибка расшифровки аудио",
"Error decrypting image": "Ошибка расшифровки изображения",
"Image '%(Body)s' cannot be displayed.": "Изображение '%(Body)s' не может быть отображено.",
"This image cannot be displayed.": "Это изображение не может быть отображено.",
"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": "Удаленный или неизвестный тип сообщения",
"Disable URL previews by default for participants in this room": "Отключить предпросмотр URL для участников этой комнаты по-умолчанию",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "Предпросмотр URL %(globalDisableUrlPreview)s по-умолчанию для участников этой комнаты.",
"URL Previews": "Предпросмотр URL",
"Enable URL previews for this room (affects only you)": "Включить предпросмотр URL в этой комнате (только для вас)",
"Drop file here to upload": "Перетащите файл сюда для загрузки",
" (unsupported)": " (не поддерживается)",
"Ongoing conference call%(supportedText)s. %(joinText)s": "Идет конференц-звонок%(supportedText)s. %(joinText)s",
"for %(amount)ss": "для %(amount)s",
"for %(amount)sm": "для %(amount)s",
"for %(amount)sh": "для %(amount)s",
"for %(amount)sd": "для %(amount)s",
"Online": "В сети",
"Idle": "Отошел",
"Offline": "Не в сети",
"Disable URL previews for this room (affects only you)": "Отключить предпросмотр URL в этой комнате (только для вас)",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName сменил аватар комнаты на <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s удалил аватар комнаты.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s сменил аватар для %(roomName)s",
"Create new room": "Создать комнату",
"Room directory": "Каталог комнат",
"Start chat": "Начать чат",
"Welcome page": "Домашняя страница",
"Add": "Добавить",
"%(count)s new messages.one": "%(count)s новое сообщение",
"%(count)s new messages.other": "%(count)s новых сообщений",
"Error: Problem communicating with the given homeserver.": "Ошибка: проблема коммуникаций с указанным Home Server'ом.",
"Failed to fetch avatar URL": "Ошибка получения аватара",
"The phone number entered looks invalid": "Введенный номер телефона выглядит неправильным",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Ошибка загрузки истории комнаты: недостаточно прав.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Ошибка загрузки истории комнаты: запрошенный элемент не найден.",
"Uploading %(filename)s and %(count)s others.zero": "Загрузка %(filename)s",
"Uploading %(filename)s and %(count)s others.one": "Загрузка %(filename)s и %(count)s другой файл",
"Uploading %(filename)s and %(count)s others.other": "Загрузка %(filename)s и %(count)s других файлов",
"Username invalid: %(errMessage)s": "Неверное имя пользователя: %(errMessage)s",
"Searching known users": "Искать известных пользователей",
"You must <a>register</a> to use this functionality": "Вы должны <a>зарегистрироваться</a> для использования этой функции",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Отослать снова</a> или <a>отменить отправку</a>. Вы также можете выбрать на отправку или отмену отдельные сообщения.",
"New Password": "Новый пароль",
"Start chatting": "Начать общение",
"Start Chatting": "Начать общение",
"Click on the button below to start chatting!": "Нажмите на кнопку ниже для того, чтобы начать общение!",
"Create a new chat or reuse an existing one": "Создать новый чат или использовать уже существующий",
"You already have existing direct chats with this user:": "У вас уже есть существующие приватные чаты с этим пользователем:",
"Username available": "Имя пользователя доступно",
"Username not available": "Имя пользователя недоступно",
"Something went wrong!": "Что-то пошло не так!",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "Это будет ваше имя пользователя на <span></span>, или вы можете выбрать <a>другой сервер</a>.",
"If you already have a Matrix account you can <a>log in</a> instead.": "Если вы уже имеете учетную запись Matrix, то вы можете <a>войти</a>."
}

295
src/i18n/strings/sv.json Normal file
View File

@@ -0,0 +1,295 @@
{
"af": "Afrikaans",
"ar-ae": "Arabiska (U.A.E.)",
"ar-bh": "Arabiska (Bahrain)",
"ar-dz": "Arabiska (Algeriet)",
"ar-eg": "Arabiska (Egypten)",
"ar-iq": "Arabiska (Irak)",
"ar-jo": "Arabiska (Jordanien)",
"ar-kw": "Arabiska (Kuwait)",
"ar-lb": "Arabiska (Libanon)",
"ar-ly": "Arabiska (Libyen)",
"ar-ma": "Arabiska (Marocko)",
"ar-om": "Arabiska (Oman)",
"ar-qa": "Arabiska (Qatar)",
"ar-sa": "Arabiska (Saudiarabien)",
"ar-sy": "Arabiska (Syrien)",
"ar-tn": "Arabiska (Tunisien)",
"ar-ye": "Arabiska (Yemen)",
"be": "Vitryska",
"bg": "Bulgariska",
"ca": "Katalanska",
"cs": "Tjeckiska",
"da": "Danska",
"de-at": "Tyska (Österrike)",
"de-ch": "Tyska (Schweiz)",
"de": "Tyska",
"de-li": "Tyska (Liechtenstein)",
"de-lu": "Tyska (Luxembourg)",
"el": "Grekiska",
"en-au": "Engelska (Australien)",
"en-bz": "Engelska (Belize)",
"en-ca": "Engelska (Kanada)",
"en": "Engelska",
"en-gb": "Engelska (Förenta kungariket)",
"en-ie": "Engelska (Irland)",
"en-jm": "Engelska (Jamaica)",
"en-nz": "Engelska (Nya Zeeland)",
"en-tt": "Engelska (Trinidad)",
"en-us": "Engelska (Förenta staterna)",
"en-za": "Engelska (Sydafrika)",
"es-ar": "Spanska (Argentina)",
"es-bo": "Spanska (Bolivia)",
"es-cl": "Spanska (Chile)",
"es-co": "Spanska (Colombia)",
"es-cr": "Spanska (Costa Rica)",
"es-do": "Spanska (Dominikanska republiken)",
"es-ec": "Spanska (Ecuador)",
"es-gt": "Spanska (Guatemala)",
"es-hn": "Spanska (Honduras)",
"es-mx": "Spanska (Mexico)",
"es-ni": "Spanska (Nicaragua)",
"es-pa": "Spanska (Panama)",
"es-pe": "Spanska (Peru)",
"es-pr": "Spanska (Puerto Rico)",
"es-py": "Spanska (Paraguay)",
"es": "Spanska (Spanien)",
"es-sv": "Spanska (El Salvador)",
"es-uy": "Spanska (Uruguay)",
"es-ve": "Spanska (Venezuela)",
"et": "Estniska",
"eu": "Baskiska",
"fa": "Persiska",
"fi": "Finska",
"fo": "Färöiska",
"fr-be": "Franska (Belgien)",
"fr-ca": "Franska (Kanada)",
"fr-ch": "Franska (Schweiz)",
"fr": "Franska",
"fr-lu": "Franska (Luxembourg)",
"ga": "Irländska",
"gd": "Gaeliska (Skottland)",
"he": "Hebreiska",
"hi": "Hindi",
"hr": "Kroatiska",
"hu": "Ungerska",
"id": "Indonesiska",
"is": "Isländska",
"it-ch": "Italienska (Schweiz)",
"it": "Italienska",
"ja": "Japanska",
"ji": "Jiddisch",
"ko": "Koreanska",
"lt": "Litauiska",
"lv": "Lettiska",
"mk": "Makedonska (FYROM)",
"ms": "Malaysiska",
"mt": "Maltesiska",
"nl-be": "Nederländska (Belgien)",
"nl": "Nederländska",
"no": "Norska",
"pl": "Polska",
"pt-br": "Brasiliansk portugisiska",
"pt": "Portugisiska",
"rm": "Rätoromanska",
"ro-mo": "Rumänska (Republiken Moldavien)",
"ro": "Rumänska",
"ru-mo": "Ryska (Republiken Moldavien)",
"ru": "Ryska",
"sb": "Sorbiska",
"sk": "Slovakiska",
"sl": "Slovenska",
"sq": "Albanska",
"sr": "Serbiska",
"sv-fi": "Svenska (Finland)",
"sv": "Svenska",
"sx": "Sutu",
"sz": "Samiska",
"th": "Thailändska",
"tn": "Tswana",
"tr": "Turkiska",
"ts": "Tsonga",
"uk": "Ukrainska",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamesiska",
"xh": "Xhosa",
"zh-cn": "Kinesiska (Folkrepubliken Kina)",
"zh-hk": "Kinesiska (Hongkong SAR)",
"zh-sg": "Kinesiska (Singapore)",
"zh-tw": "Kinesiska (Taiwan)",
"zu": "Zulu",
"A registered account is required for this action": "Ett registrerat konto behövs för den här handlingen",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Ett SMS har skickats till +%(msisdn)s. Vänligen ange verifieringskoden ur meddelandet",
"accept": "acceptera",
"%(targetName)s accepted an invitation.": "%(targetName)s accepterade en inbjudan.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepterade inbjudan för %(displayName)s.",
"Account": "Konto",
"Add a topic": "Lägg till ett ämne",
"Add email address": "Lägg till en epostadress",
"Add phone number": "Lägg till ett telefonnummer",
"Admin": "Administratör",
"VoIP": "VoIP",
"Missing Media Permissions, click here to request.": "Saknar mediebehörigheter, klicka för att begära.",
"No Microphones detected": "Ingen mikrofon hittades",
"No Webcams detected": "Ingen kamera hittades",
"No media permissions": "Inga mediebehörigheter",
"You may need to manually permit Riot to access your microphone/webcam": "Du måste manuellt tillåta Riot att komma åt din mikrofon/kamera",
"Default Device": "Standardenhet",
"Microphone": "Mikrofon",
"Camera": "Kamera",
"Advanced": "Avancerad",
"Algorithm": "Algoritm",
"Always show message timestamps": "Visa alltid tidsstämpel för meddelanden",
"Hide removed messages": "Göm raderade meddelanden",
"Authentication": "Autentisering",
"all room members": "alla rumsmedlemmar",
"all room members, from the point they are invited": "alla rumsmedlemmar fr.o.m att de bjöds in",
"all room members, from the point they joined": "alla rumsmedlemmar fr.o.m. att de gick med som medlem",
"an address": "en address",
"and": "och",
"%(items)s and %(remaining)s others": "%(items)s och %(remaining)s andra",
"%(items)s and one other": "%(items)s och en annan",
"%(items)s and %(lastItem)s": "%(items)s och %(lastItem)s",
"and %(overflowCount)s others...": "och %(overflowCount)s andra...",
"and one other...": "och en annan...",
"%(names)s and %(lastPerson)s are typing": "%(names)s och %(lastPerson)s skriver",
"%(names)s and one other are typing": "%(names)s och en annan skriver",
"%(names)s and %(count)s others are typing": "%(names)s och %(count)s andra skriver",
"An email has been sent to": "Ett epostmeddelande har sänts till",
"A new password must be entered.": "Ett nytt lösenord måste anges.",
"%(senderName)s answered the call.": "%(senderName)s svarade på samtalet.",
"Anyone who knows the room's link, including guests": "Alla som har rummets adress, inklusive gäster",
"anyone": "vem som helst",
"Anyone": "Vem som helst",
"Anyone who knows the room's link, apart from guests": "Alla som har rummets adress, förutom gäster",
"An error has occurred.": "Ett fel har inträffat.",
"Are you sure?": "Är du säker?",
"Are you sure you want to leave the room '%(roomName)s'?": "Vill du lämna rummet '%(roomName)s'?",
"Are you sure you want to upload the following files?": "Vill du ladda upp följande filer?",
"Autoplay GIFs and videos": "Spela automatiskt upp GIFar och videor",
"Are you sure you want to reject the invitation?": "Vill du avvisa inbjudan?",
"Bulk Options": "Volymhandlingar",
"Bug Report": "Buggrapport",
"Blacklisted": "Svartlistad",
"%(senderName)s banned %(targetName)s.": "%(senderName)s bannade %(targetName)s.",
"Banned users": "Bannade användare",
"Bans user with given id": "Bannar användaren med givet ID",
"Ban": "Banna",
"Attachment": "Bilaga",
"Call Timeout": "Samtalstimeout",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Det gick inte att ansluta till hemservern - kontrollera anslutningen och att <a>hemserverns TLS-certifikat</a> är pålitat.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Det går inte att ansluta till en hemserver via HTTP då adressen i webbläsaren är HTTPS. Använd HTTPS, eller <a>sätt på osäkra skript</a>.",
"Can't load user settings": "Det gick inte att ladda användarinställningar",
"Change Password": "Byt lösenord",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s bytte namn från %(oldDisplayName)s till %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s bytte sin profilbild.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s bytte rummets namn till %(roomName)s.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s tog bort rummets namn.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s bytte rummets ämne till \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Ändringar till vem som kan läsa meddelandehistorik tillämpas endast till framtida meddelanden i det här rummet",
"Changes your display nickname": "Byter ditt synliga namn",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Om du byter lösenord kommer för tillfället alla krypteringsnycklar på alla enheter att nollställas, vilket gör all krypterad meddelandehistorik omöjligt att läsa, om du inte först exporterar rumsnycklarna och sedan importerar dem efteråt. I framtiden kommer det här att förbättras.",
"Claimed Ed25519 fingerprint key": "Påstådd Ed25519-fingeravtrycksnyckel",
"Clear Cache and Reload": "Töm cache och ladda om",
"Clear Cache": "Töm cache",
"Click here": "Klicka här",
"Click here to fix": "Klicka här för att fixa",
"Click to mute audio": "Klicka för att dämpa ljud",
"Click to mute video": "Klicka för att stänga av video",
"click to reveal": "klicka för att avslöja",
"Click to unmute video": "Klicka för att sätta på video",
"Click to unmute audio": "Klicka för att sätta på ljud",
"Command error": "Kommandofel",
"Commands": "Kommandon",
"Conference call failed.": "Konferenssamtal misslyckades.",
"Conference calling is in development and may not be reliable.": "Konferenssamtal är under utveckling och är inte nödvändigtvis pålitliga.",
"Conference calls are not supported in encrypted rooms": "Konferenssamtal stöds inte i krypterade rum",
"Conference calls are not supported in this client": "Konferenssamtal stöds inte i den här klienten",
"Confirm password": "Bekräfta lösenord",
"Confirm your new password": "Bekräfta ditt nya lösenord",
"Continue": "Fortsätt",
"Could not connect to the integration server": "Det gick inte att ansluta till integrationsservern",
"Create an account": "Skapa ett konto",
"Create Room": "Skapa ett rum",
"Cryptography": "Kryptografi",
"Current password": "Nuvarande lösenord",
"Curve25519 identity key": "Curve25519 -identitetsnyckel",
"Custom level": "Egen nivå",
"/ddg is not a command": "/ddg är inte ett kommando",
"Deactivate Account": "Deaktivera konto",
"Deactivate my account": "Deaktivera mitt konto",
"decline": "avböj",
"Decrypt %(text)s": "Dekryptera %(text)s",
"Decryption error": "Dekrypteringsfel",
"(default: %(userName)s)": "(standard: %(userName)s)",
"Delete": "Radera",
"demote": "degradera",
"Deops user with given id": "Degraderar användaren med givet id",
"Default": "Standard",
"Device already verified!": "Enheten är redan verifierad!",
"Device ID": "Enhets-ID",
"Device ID:": "Enhets-ID:",
"device id: ": "enhets-id: ",
"Device key:": "Enhetsnyckel:",
"Devices": "Enheter",
"Devices will not yet be able to decrypt history from before they joined the room": "Enheter kan inte ännu dekryptera meddelandehistorik från före de gick med i rummet",
"Direct Chat": "Direkt-chatt",
"Direct chats": "Direkta chattar",
"disabled": "avstängd",
"Disable inline URL previews by default": "Stäng av inline-URL-förhandsvisningar som standard",
"Disinvite": "Häv inbjudan",
"Display name": "Namn",
"Displays action": "Visar handling",
"Don't send typing notifications": "Sänd inte \"skriver\"-status",
"Download %(text)s": "Ladda ner %(text)s",
"Drop here %(toAction)s": "Dra hit för att %(toAction)s",
"Drop here to tag %(section)s": "Dra hit för att tagga %(section)s",
"Ed25519 fingerprint": "Ed25519-fingeravtryck",
"Email": "Epost",
"Email address": "Epostadress",
"Email address (optional)": "Epostadress (valfri)",
"Email, name or matrix ID": "Epostadress, namn, eller Matrix-ID",
"Emoji": "Emoji",
"Enable encryption": "Sätt på kryptering",
"enabled": "aktiverat",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Krypterade meddelanden syns inte på klienter som inte ännu stöder kryptering",
"Encrypted room": "Krypterat rum",
"%(senderName)s ended the call.": "%(senderName)s avslutade samtalet.",
"End-to-end encryption information": "Krypteringsinformation",
"End-to-end encryption is in beta and may not be reliable": "Kryptering är i beta och är inte nödvändigtvis pålitligt",
"Enter Code": "Skriv in kod",
"Error": "Fel",
"Error decrypting attachment": "Det gick inte att dekryptera bilagan",
"Event information": "Händelseinformation",
"Existing Call": "Existerande samtal",
"Export": "Exportera",
"Export E2E room keys": "Exportera krypteringsrumsnycklar",
"Failed to ban user": "Det gick inte att banna användaren",
"Failed to change password. Is your password correct?": "Det gick inte att byta lösenord. Är lösenordet rätt?",
"Failed to change power level": "Det gick inte att ändra maktnivå",
"Failed to delete device": "Det gick inte att radera enhet",
"Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s",
"Failed to join room": "Det gick inte att gå med i rummet",
"Failed to join the room": "Det gick inte att gå med i rummet",
"Failed to kick": "Det gick inte att kicka",
"Failed to leave room": "Det gick inte att lämna rummet",
"Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen",
"Failed to lookup current room": "Det gick inte att hämta det nuvarande rummet",
"Failed to mute user": "Det gick inte att dämpa användaren",
"Failed to register as guest:": "Det gick inte att registrera som gästanvändare:",
"Failed to reject invite": "Det gick inte att avböja inbjudan",
"Failed to reject invitation": "Det gick inte att avböja inbjudan",
"Failed to save settings": "Det gick inte att spara inställningarna",
"Failed to send email": "Det gick inte att skicka epost",
"Failed to send request.": "Det gick inte att sända begäran.",
"Failed to set avatar.": "Det gick inte att sätta profilbilden.",
"Failed to set display name": "Det gick inte att sätta namnet",
"Failed to set up conference call": "Det gick inte att starta konferenssamtalet",
"Failed to toggle moderator status": "Det gick inte att växla moderator-status",
"Failed to unban": "Det gick inte att avbanna",
"Failed to upload file": "Det gick inte att ladda upp filen",
"Failed to verify email address: make sure you clicked the link in the email": "Det gick inte att bekräfta epostadressen, klicka på länken i epostmeddelandet",
"Favourite": "Favorit",
"favourite": "favorit"
}

3
src/i18n/strings/te.json Normal file
View File

@@ -0,0 +1,3 @@
{
"was invited": "తనని ఆహ్వానించారు"
}

482
src/i18n/strings/th.json Normal file
View File

@@ -0,0 +1,482 @@
{
"de": "เยอร์มัน",
"en-us": "อังกฤษ (สหรัฐอเมริกา)",
"en": "อังกฤษ",
"en-ca": "อังกฤษ(แคนาดา)",
"ja": "ญี่ปุ่น",
"fr": "ฝรั่งเศส",
"ko": "เกาหลี",
"th": "ไทย",
"vi": "เวียดนาม",
"accept": "ยอมรับ",
"Account": "บัญชี",
"Add phone number": "เพิ่มหมายเลขโทรศัพท์",
"Microphone": "ไมโครโฟน",
"No Microphones detected": "ไม่พบไมโครโฟน",
"Camera": "กล้อง",
"Advanced": "ขึ้นสูง",
"and": "และ",
"Ban": "แบน",
"Bug Report": "รายงานจุดบกพร่อง",
"Change Password": "เปลี่ยนรหัสผ่าน",
"Create Room": "สรัางห้อง",
"Delete": "ลบ",
"Default": "ค่าเริ่มต้น",
"(default: %(userName)s)": "(ค่าเริ่มต้น: %(userName)s)",
"Default Device": "อุปกรณ์เริ่มต้น",
"%(senderName)s banned %(targetName)s.": "%(senderName)s แบน %(targetName)s แล้ว",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s เปลี่ยนหัวข้อเป็น \"%(topic)s\"",
"Decrypt %(text)s": "ถอดรหัส %(text)s",
"Device ID": "ID อุปกรณ์",
"Device ID:": "ID อุปกรณ์:",
"device id: ": "id อุปกรณ์: ",
"Devices": "อุปกรณ์",
"Direct Chat": "แชทโดยตรง",
"Download %(text)s": "ดาวน์โหลด %(text)s",
"Emoji": "อีโมจิ",
"Enable encryption": "เปิดใช้งานการเข้ารหัส",
"enabled": "เปิดใช้งานแล้ว",
"Error": "ข้อผิดพลาด",
"Found a bug?": "พบจุดบกพร่อง?",
"is a": "เป็น",
"%(displayName)s is typing": "%(displayName)s กำลังพิมพ์",
"Kick": "เตะ",
"Low priority": "ความสำคัญต่ำ",
"matrix-react-sdk version:": "เวอร์ชัน matrix-react-sdk:",
"Members only": "สมาชิกเท่านั้น",
"Mobile phone number": "หมายเลขโทรศัพท์",
"Mobile phone number (optional)": "หมายเลขโทรศัพท์ (เลือกหรือไม่ก็ได้)",
"Name": "ชื่อ",
"OK": "ตกลง",
"Password": "รหัสผ่าน",
"Password:": "รหัสผ่าน:",
"Please Register": "กรุณาลงทะเบียน",
"Profile": "โปรไฟล์",
"Reason": "เหตุผล",
"Register": "ลงทะเบียน",
"Results from DuckDuckGo": "ผลจาก DuckDuckGo",
"Return to app": "กลับไปยังแอป",
"riot-web version:": "เวอร์ชัน riot-web:",
"Cancel": "ยกเลิก",
"Dismiss": "ไม่สนใจ",
"Mute": "เงียบ",
"Notifications": "การแจ้งเตือน",
"Operation failed": "การดำเนินการล้มเหลว",
"powered by Matrix": "ใช้เทคโนโลยี Matrix",
"Search": "ค้นหา",
"Settings": "การตั้งค่า",
"unknown error code": "รหัสข้อผิดพลาดที่ไม่รู้จัก",
"Sunday": "วันอาทิตย์",
"Monday": "วันจันทร์",
"Tuesday": "วันอังคาร",
"Wednesday": "วันพุธ",
"Thursday": "วันพฤหัสบดี",
"Friday": "วันศุกร์",
"Saturday": "วันเสาร์",
"olm version:": "เวอร์ชัน olm:",
"Report it": "รายงานเลย",
"Remove": "ลบ",
"Custom Server Options": "กำหนดเซิร์ฟเวอร์เอง",
"Failed to join the room": "การเข้าร่วมห้องล้มเหลว",
"Drop here %(toAction)s": "ปล่อยที่นี่ %(toAction)s",
"Favourite": "รายการโปรด",
"Failed to forget room %(errCode)s": "การลืมห้องล้มเหลว %(errCode)s",
"%(targetName)s accepted an invitation.": "%(targetName)s ตอบรับคำเชิญแล้ว",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s ตอบรับคำเชิญสำหรับ %(displayName)s แล้ว",
"Add a topic": "เพิ่มหัวข้อ",
"Add email address": "เพิ่มที่อยู่อีเมล",
"Admin": "ผู้ดูแล",
"No Webcams detected": "ไม่พบกล้องเว็บแคม",
"No media permissions": "ไม่มีสิทธิ์เข้าถึงสื่อ",
"You may need to manually permit Riot to access your microphone/webcam": "คุณอาจต้องให้สิทธิ์ Riot เข้าถึงไมค์โครโฟนไมค์โครโฟน/กล้องเว็บแคม ด้วยตัวเอง",
"Algorithm": "อัลกอริทึม",
"Hide removed messages": "ซ่อนข้อความที่ถูกลบแล้ว",
"Authentication": "การยืนยันตัวตน",
"all room members": "สมาชิกทั้งหมด",
"all room members, from the point they are invited": "สมาชิกทั้งหมด นับตั้งแต่เมื่อได้รับคำเชิญ",
"all room members, from the point they joined": "สมาชิกทั้งหมด นับตั้งแต่เมื่อเข้าร่วมห้อง",
"an address": "ที่อยู่",
"%(items)s and %(remaining)s others": "%(items)s และอีก %(remaining)s ผู้ใช้",
"%(items)s and one other": "%(items)s และอีกหนึ่งผู้ใช้",
"%(items)s and %(lastItem)s": "%(items)s และ %(lastItem)s",
"and %(overflowCount)s others...": "และอีก %(overflowCount)s ผู้ใช้...",
"and one other...": "และอีกหนึ่งผู้ใช้...",
"%(names)s and %(lastPerson)s are typing": "%(names)s และ %(lastPerson)s กำลังพิมพ์",
"%(names)s and one other are typing": "%(names)s และอีกหนึ่งคนกำลังพิมพ์",
"%(names)s and %(count)s others are typing": "%(names)s และอีก %(count)s คนกำลังพิมพ์",
"%(senderName)s answered the call.": "%(senderName)s รับสายแล้ว",
"anyone": "ทุกคน",
"An error has occurred.": "เกิดข้อผิดพลาด",
"Anyone": "ทุกคน",
"Anyone who knows the room's link, apart from guests": "ทุกคนที่มีลิงก์ ยกเว้นแขก",
"Anyone who knows the room's link, including guests": "ทุกคนที่มีลิงก์ รวมถึงแขก",
"Are you sure?": "คุณแน่ใจหรือไม่?",
"Are you sure you want to leave the room '%(roomName)s'?": "คุณแน่ใจหรือว่าต้องการจะออกจากห้อง '%(roomName)s'?",
"Are you sure you want to reject the invitation?": "คุณแน่ใจหรือว่าต้องการจะปฏิเสธคำเชิญ?",
"Are you sure you want to upload the following files?": "คุณแน่ใจหรือว่าต้องการอัปโหลดไฟล์ต่อไปนี้?",
"Attachment": "ไฟล์แนบ",
"Autoplay GIFs and videos": "เล่น GIF และวิดิโออัตโนมัติ",
"Banned users": "ผู้ใช้ที่ถูกแบน",
"Bans user with given id": "ผู้ใช้และ id ที่ถูกแบน",
"Blacklisted": "ขึ้นบัญชีดำ",
"Can't load user settings": "ไม่สามารถโหลดการตั้งค่าผู้ใช้ได้",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s ได้เปลี่ยนชื่อที่แสดงจาก %(oldDisplayName)s ไปเป็น %(displayName)s",
"%(senderName)s changed their profile picture.": "%(senderName)s เปลี่ยนรูปโปรไฟล์ของเขา",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s เปลี่ยนชื่อห้องไปเป็น %(roomName)s",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s ลบชื่อห้อง",
"Changes your display nickname": "เปลี่ยนชื่อเล่นที่แสดงของคุณ",
"changing room on a RoomView is not supported": "ไม่รองรับการเปลี่ยนห้องใน RoomView",
"Clear Cache and Reload": "ล้างแคชแล้วโหลดใหม่",
"Clear Cache": "ล้างแคช",
"Click here": "คลิกที่นี่",
"Click here to fix": "คลิกที่นี่เพื่อแก้ไข",
"Click to mute audio": "คลิกที่นี่เพื่อปิดเสียง",
"Click to mute video": "คลิกที่นี่เพื่อปิดกล้อง",
"click to reveal": "คลิกเพื่อแสดง",
"Click to unmute video": "คลิกเพื่อเปิดกล้อง",
"Click to unmute audio": "คลิกเพื่อเปิดเสียง",
"Command error": "คำสั่งผิดพลาด",
"Commands": "คำสั่ง",
"Conference call failed.": "การโทรประชุมล้มเหลว",
"Conference calling is in development and may not be reliable.": "การโทรประชุมอยู่ระหว่างการพัฒนาและอาจพึ่งพาไม่ได้",
"Conference calls are not supported in encrypted rooms": "การโทรประชุมไม่รองรับห้องที่ถูกเข้ารหัส",
"Conference calls are not supported in this client": "ไคลเอนต์นี้ไม่รองรับการโทรประชุม",
"Confirm password": "ยืนยันรหัสผ่าน",
"Confirm your new password": "ยืนยันรหัสผ่านใหม่",
"Continue": "ดำเนินการต่อ",
"Create an account": "สร้างบัญชี",
"Cryptography": "วิทยาการเข้ารหัส",
"Current password": "รหัสผ่านปัจจุบัน",
"/ddg is not a command": "/ddg ไม่ใช่คำสั่ง",
"Deactivate Account": "ปิดการใช้งานบัญชี",
"Deactivate my account": "ปิดการใช้งานบัญชีของฉัน",
"decline": "ปฏิเสธ",
"Decryption error": "การถอดรหัสผิดพลาด",
"demote": "ลดขั้น",
"Device already verified!": "ยืนยันอุปกรณ์แล้ว!",
"Device key:": "Key อุปกรณ์:",
"Devices will not yet be able to decrypt history from before they joined the room": "อุปกรณ์จะยังไม่สามารถถอดรหัสประวัติแชทก่อนจะเข้าร่วมห้องได้",
"Direct chats": "แชทตรง",
"disabled": "ถูกปิดใช้งาน",
"Disinvite": "ถอนคำเชิญ",
"Display name": "ชื่อที่แสดง",
"Don't send typing notifications": "ไม่แจ้งว่ากำลังพิมพ์",
"Drop here to tag %(section)s": "ปล่อยที่นี่เพื่อแท็กว่า %(section)s",
"Ed25519 fingerprint": "ลายนิ้วมือ Ed25519",
"Email": "อีเมล",
"Email address": "ที่อยู่อีเมล",
"Email address (optional)": "ที่อยู่อีเมล (เลือกหรือไม่ก็ได้)",
"Email, name or matrix ID": "อีเมล ชื่อ หรือ ID matrix",
"Encrypted room": "ห้องที่ถูกเข้ารหัส",
"%(senderName)s ended the call.": "%(senderName)s จบการโทร",
"Enter Code": "กรอกรหัส",
"Error decrypting attachment": "การถอดรหัสไฟล์แนบผิดพลาด",
"Export": "ส่งออก",
"Failed to ban user": "การแบนผู้ใช้ล้มเหลว",
"Failed to change password. Is your password correct?": "การเปลี่ยนรหัสผ่านล้มเหลว รหัสผ่านของคุณถูกต้องหรือไม่?",
"Failed to delete device": "การลบอุปกรณ์ล้มเหลว",
"Failed to join room": "การเข้าร่วมห้องล้มเหลว",
"Failed to kick": "การเตะล้มเหลว",
"Failed to leave room": "การออกจากห้องล้มเหลว",
"Failed to lookup current room": "การหาห้องปัจจุบันล้มเหลว",
"Failed to register as guest:": "การลงทะเบียนในฐานะแขกล้มเหลว:",
"Failed to reject invite": "การปฏิเสธคำเชิญล้มเหลว",
"Failed to reject invitation": "การปฏิเสธคำเชิญล้มเหลว",
"Failed to save settings": "การบันทึกการตั้งค่าล้มเหลว",
"Failed to send email": "การส่งอีเมลล้มเหลว",
"Failed to send request.": "การส่งคำขอล้มเหลว",
"Failed to set display name": "การตั้งชื่อที่แสดงล้มเหลว",
"Failed to toggle moderator status": "การสลับสถานะผู้ช่วยดูแลล้มเหลว",
"Failed to unban": "การถอนแบนล้มเหลว",
"Failed to upload file": "การอัปโหลดไฟล์ล้มเหลว",
"Failed to verify email address: make sure you clicked the link in the email": "การยืนยันอีเมลล้มเหลว: กรุณาตรวจสอบว่าคุณคลิกลิงก์ในอีเมลแล้ว",
"Failure to create room": "การสร้างห้องล้มเหลว",
"favourite": "รายการโปรด",
"Favourites": "รายการโปรด",
"Filter room members": "กรองสมาชิกห้อง",
"Forget room": "ลืมห้อง",
"Forgot your password?": "ลิมรหัสผ่าน?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s จาก %(fromPowerLevel)s ไปเป็น %(toPowerLevel)s",
"had": "เคยมี",
"Hangup": "วางสาย",
"Historical": "ประวัติแชทเก่า",
"Homeserver is": "เซิร์ฟเวอร์บ้านคือ",
"Identity Server is": "เซิร์ฟเวอร์ระบุตัวตนคือ",
"I have verified my email address": "ฉันยืนยันที่อยู่อีเมลแล้ว",
"Import": "นำเข้า",
"Incorrect username and/or password.": "ชื่อผู้ใช้และ/หรือรหัสผ่านไม่ถูกต้อง",
"Incorrect verification code": "รหัสยืนยันไม่ถูกต้อง",
"Interface Language": "ภาษาอินเตอร์เฟส",
"Invalid alias format": "รูปแบบนามแฝงไม่ถูกต้อง",
"Invalid address format": "รูปแบบที่อยู่ไม่ถูกต้อง",
"Invalid Email Address": "ที่อยู่อีเมลไม่ถูกต้อง",
"Invalid file%(extra)s": "ไฟล์ %(extra)s ไม่ถูกต้อง",
"%(senderName)s invited %(targetName)s.": "%(senderName)s เชิญ %(targetName)s แล้ว",
"Invite new room members": "เชิญสมาชิกใหม่",
"Invited": "เชิญแล้ว",
"Invites": "คำเชิญ",
"Invites user with given id to current room": "เชิญผู้ใช้ พร้อม id ของห้องปัจจุบัน",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' ไม่ใช่รูปแบบที่ถูกต้องสำหรับที่อยู่",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' ไม่ใช่รูปแบบที่ถูกต้องสำหรับนามแฝง",
"Sign in with": "เข้าสู่ระบบด้วย",
"Join Room": "เข้าร่วมห้อง",
"joined and left": "เข้าร่วมแล้วออกจากห้อง",
"joined": "เข้าร่วมแล้ว",
"%(targetName)s joined the room.": "%(targetName)s เข้าร่วมห้องแล้ว",
"Jump to first unread message.": "ข้ามไปยังข้อความแรกที่ยังไม่ได้อ่าน",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s เตะ %(targetName)s แล้ว",
"Labs": "ห้องทดลอง",
"Leave room": "ออกจากห้อง",
"left and rejoined": "ออกแล้วกลับเข้าร่วมอีกครั้ง",
"left": "ออกไปแล้ว",
"%(targetName)s left the room.": "%(targetName)s ออกจากห้องแล้ว",
"List this room in %(domain)s's room directory?": "แสดงห้องนี้ในไดเรกทอรีห้องของ %(domain)s?",
"Logged in as:": "เข้าสู่ระบบในชื่อ:",
"Login as guest": "เข้าสู่ระบบในฐานะแขก",
"Logout": "ออกจากระบบ",
"Markdown is disabled": "ปิดใช้งาน Markdown แล้ว",
"Markdown is enabled": "เปิดใช้งาน Markdown แล้ว",
"Missing user_id in request": "ไม่พบ user_id ในคำขอ",
"Moderator": "ผู้ช่วยดูแล",
"my Matrix ID": "Matrix ID ของฉัน",
"New address (e.g. #foo:%(localDomain)s)": "ที่อยู่ใหม่ (เช่น #foo:%(localDomain)s)",
"New password": "รหัสผ่านใหม่",
"New passwords don't match": "รหัสผ่านใหม่ไม่ตรงกัน",
"New passwords must match each other.": "รหัสผ่านใหม่ทั้งสองช่องต้องตรงกัน",
"none": "ไม่มี",
"not set": "ไม่ได้ตั้ง",
"not specified": "ไม่ได้ระบุ",
"(not supported by this browser)": "(เบราว์เซอร์นี้ไม่รองรับ)",
"<not supported>": "<ไม่รองรับ>",
"NOT verified": "ยังไม่ได้ยืนยัน",
"No more results": "ไม่มีผลลัพธ์อื่น",
"No results": "ไม่มีผลลัพธ์",
"Once you&#39;ve followed the link it contains, click below": "หลังจากคุณเปิดลิงก์ข้างในแล้ว คลิกข้างล่าง",
"Passwords can't be empty": "รหัสผ่านต้องไม่ว่าง",
"People": "บุคคล",
"Permissions": "สิทธิ์",
"Phone": "โทรศัพท์",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s ได้เริ่มการโทรแบบ%(callType)s",
"Please check your email and click on the link it contains. Once this is done, click continue.": "กรุณาเช็คอีเมลและคลิกลิงก์ข้างใน หลังจากนั้น คลิกดำเนินการต่อ",
"Privacy warning": "คำเตือนเกี่ยวกับความเป็นส่วนตัว",
"Privileged Users": "ผู้ใช้ที่มีสิทธิพิเศษ",
"Revoke Moderator": "เพิกถอนผู้ช่วยดูแล",
"Refer a friend to Riot:": "แนะนำเพื่อนให้รู้จัก Riot:",
"Registration required": "ต้องลงทะเบียนก่อน",
"rejected": "ปฏิเสธแล้ว",
"%(targetName)s rejected the invitation.": "%(targetName)s ปฏิเสธคำเชิญแล้ว",
"Reject invitation": "ปฏิเสธคำเชิญ",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s ลบชื่อที่แสดงแล้ว (%(oldDisplayName)s)",
"%(senderName)s removed their profile picture.": "%(senderName)s ลบรูปโปรไฟล์ของเขาแล้ว",
"Remove %(threePid)s?": "ลบ %(threePid)s?",
"restore": "กู้คืน",
"Return to login screen": "กลับไปยังหน้าลงชื่อเข้าใช้",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot ไม่มีสิทธิ์ส่งการแจ้งเตือน - กรุณาตรวจสอบการตั้งค่าเบราว์เซอร์ของคุณ",
"Riot was not given permission to send notifications - please try again": "Riot ไม่ได้รับสิทธิ์ส่งการแจ้งเตือน - กรุณาลองใหม่อีกครั้ง",
"Room Colour": "สีห้อง",
"Room name (optional)": "ชื้อห้อง (เลือกหรือไม่ก็ได้)",
"Rooms": "ห้องสนทนา",
"Save": "บันทึก",
"Scroll to bottom of page": "เลื่อนลงไปล่างสุด",
"Scroll to unread messages": "เลี่ยนไปที่ข้อความที่ยังไม่ได้อ่าน",
"Search failed": "การค้นหาล้มเหลว",
"Searches DuckDuckGo for results": "ค้นหาบน DuckDuckGo",
"Send a message (unencrypted)": "ส่งข้อความ (ไม่เข้ารหัส)",
"Send an encrypted message": "ส่งข้อความที่เข้ารหัสแล้ว",
"Send Invites": "ส่งคำเชิญ",
"sent an image": "ส่งรูป",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s ได้ส่งรูป",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s ได้ส่งคำเชิญให้ %(targetDisplayName)s เข้าร่วมห้อง",
"sent a video": "ส่งวิดิโอ",
"Server error": "เซิร์ฟเวอร์ผิดพลาด",
"Server may be unavailable or overloaded": "เซิร์ฟเวอร์อาจไม่พร้อมใช้งานหรือทำงานหนักเกินไป",
"Server may be unavailable, overloaded, or search timed out :(": "เซิร์ฟเวอร์อาจไม่พร้อมใช้งาน ทำงานหนักเกินไป หรือการค้นหาหมดเวลา :(",
"Server may be unavailable, overloaded, or the file too big": "เซิร์ฟเวอร์อาจไม่พร้อมใช้งาน ทำงานหนักเกินไป หรือไฟล์ใหญ่เกินไป",
"Server may be unavailable, overloaded, or you hit a bug.": "เซิร์ฟเวอร์อาจไม่พร้อมใช้งาน ทำงานหนักเกินไป หรือเจอจุดบกพร่อง",
"Server unavailable, overloaded, or something else went wrong.": "เซิร์ฟเวอร์อาจไม่พร้อมใช้งาน ทำงานหนักเกินไป หรือบางอย่างผิดปกติ",
"%(senderName)s set a profile picture.": "%(senderName)s ตั้งรูปโปรไฟล์",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s ตั้งชื่อที่แสดงเป็น %(displayName)s",
"Setting a user name will create a fresh account": "การตั้งชื่อผู้ใช้จะสร้างบัญชีใหม่",
"Show panel": "แสดงหน้าต่าง",
"Signed Out": "ออกจากระบบแล้ว",
"Sign in": "เข้าสู่ระบบ",
"Sign out": "ออกจากระบบ",
"Someone": "ใครบางคน",
"Always show message timestamps": "แสดงเวลาในแชทเสมอ",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "แสดงเวลาในแชทในรูปแบบ 12 ชั่วโมง (เช่น 2:30pm)",
"Start a chat": "เริ่มแชท",
"Start Chat": "เริ่มแชท",
"Submit": "ส่ง",
"Success": "สำเร็จ",
"tag as %(tagName)s": "แท็กว่า %(tagName)s",
"tag direct chat": "แท็กว่าแชทตรง",
"Tagged as: ": "แท็กไว้ว่า: ",
"The main address for this room is": "ที่อยู่หลักของห้องนี้คือ",
"This email address is already in use": "ที่อยู่อีเมลถูกใช้แล้ว",
"This email address was not found": "ไม่พบที่อยู่อีเมล",
"%(actionVerb)s this person?": "%(actionVerb)s บุคคลนี้?",
"The file '%(fileName)s' failed to upload": "การอัปโหลดไฟล์ '%(fileName)s' ล้มเหลว",
"This Home Server does not support login using email address.": "เซิร์ฟเวอร์บ้านนี้ไม่รองรับการลงชื่อเข้าใช้ด้วยที่อยู่อีเมล",
"There was a problem logging in.": "มีปัญหาในการลงชื่อเข้าใช้",
"this invitation?": "คำเชิญนี้?",
"This is a preview of this room. Room interactions have been disabled": "นี่คือตัวอย่างของห้อง การตอบสนองภายในห้องถูกปิดใช้งาน",
"This phone number is already in use": "หมายเลขโทรศัพท์นี้ถูกใช้งานแล้ว",
"This room's internal ID is": "ID ภายในของห้องนี้คือ",
"times": "ครั้ง",
"%(oneUser)schanged their name": "%(oneUser)sเปลี่ยนชื่อของเขาแล้ว",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)sเปลี่ยนชื่อของพวกเขา %(repeats)s ครั้ง",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)sเปลี่ยนชื่อของเขา %(repeats)s ครั้ง",
"%(severalUsers)schanged their name": "%(severalUsers)sเปลี่ยนชื่อของพวกเขาแล้ว",
"Create new room": "สร้างห้องใหม่",
"Room directory": "ไดเรกทอรีห้อง",
"Start chat": "เริ่มแชท",
"Welcome page": "หน้าต้อนรับ",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "ไม่สามารถเชื่อมต่อไปยังเซิร์ฟเวอร์บ้านผ่านทาง HTTP ได้เนื่องจาก URL ที่อยู่บนเบราว์เซอร์เป็น HTTPS กรุณาใช้ HTTPS หรือ<a>เปิดใช้งานสคริปต์ที่ไม่ปลอดภัย</a>.",
"%(count)s new messages.one": "มี %(count)s ข้อความใหม่",
"%(count)s new messages.other": "มี %(count)s ข้อความใหม่",
"Disable inline URL previews by default": "ตั้งค่าเริ่มต้นให้ไม่แสดงตัวอย่าง URL ในแชท",
"Disable markdown formatting": "ปิดใช้งานการจัดรูปแบบ markdown",
"End-to-end encryption information": "ข้อมูลการเข้ารหัสจากปลายทางถึงปลายทาง",
"End-to-end encryption is in beta and may not be reliable": "การเข้ารหัสจากปลายทางถึงปลายทางยังอยู่ในเบต้า และอาจพึ่งพาไม่ได้",
"Error: Problem communicating with the given homeserver.": "ข้อผิดพลาด: มีปัญหาในการติดต่อกับเซิร์ฟเวอร์บ้านที่กำหนด",
"Export E2E room keys": "ส่งออกกุญแจถอดรหัส E2E",
"Failed to change power level": "การเปลี่ยนระดับอำนาจล้มเหลว",
"Import E2E room keys": "นำเข้ากุญแจถอดรหัส E2E",
"to favourite": "ไปยังรายการโปรด",
"to demote": "เพื่อลดขั้น",
"The default role for new room members is": "บทบาทเริ่มต้นของสมาชิกใหม่คือ",
"The phone number entered looks invalid": "ดูเหมือนว่าหมายเลขโทรศัพท์ที่กรอกรมาไม่ถูกต้อง",
"The email address linked to your account must be entered.": "กรุณากรอกที่อยู่อีเมลที่เชื่อมกับบัญชีของคุณ",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "ไฟล์ '%(fileName)s' มีขนาดใหญ่เกินจำกัดของเซิร์ฟเวอร์บ้าน",
"To send messages": "เพื่อส่งข้อความ",
"to start a chat with someone": "เพื่อเริ่มแชทกับผู้อื่น",
"to tag as %(tagName)s": "เพื่อแท็กว่า %(tagName)s",
"to tag direct chat": "เพื่อแทกว่าแชทตรง",
"Turn Markdown off": "ปิด markdown",
"Turn Markdown on": "เปิด markdown",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s ได้เปิดใช้งานการเข้ารหัสจากปลายทางถึงปลายทาง (อัลกอริทึม%(algorithm)s).",
"Unable to add email address": "ไมาสามารถเพิ่มที่อยู่อีเมล",
"Unable to verify email address.": "ไม่สามารถยืนยันที่อยู่อีเมล",
"Unban": "ปลดแบน",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s ปลดแบน %(targetName)s แล้ว",
"Unable to capture screen": "ไม่สามารถจับภาพหน้าจอ",
"Unable to enable Notifications": "ไม่สามารถเปิดใช้งานการแจ้งเตือน",
"Unable to load device list": "ไม่สามารถโหลดรายชื่ออุปกรณ์",
"Unencrypted room": "ห้องที่ไม่เข้ารหัส",
"unencrypted": "ยังไม่ได้เข้ารหัส",
"Unknown command": "คำสั่งที่ไม่รู้จัก",
"unknown device": "อุปกรณ์ที่ไม่รู้จัก",
"Unknown room %(roomId)s": "ห้องที่ไม่รู้จัก %(roomId)s",
"Unknown (user, device) pair:": "คู่ (ผู้ใช้, อุปกรณ์) ที่ไม่รู้จัก:",
"unknown": "ไม่รู้จัก",
"Unrecognised command:": "คำสั่งที่ไม่รู้จัก:",
"Unrecognised room alias:": "นามแฝงห้องที่ไม่รู้จัก:",
"Uploading %(filename)s and %(count)s others.zero": "กำลังอัปโหลด %(filename)s",
"Uploading %(filename)s and %(count)s others.one": "กำลังอัปโหลด %(filename)s และอีก %(count)s ไฟล์",
"Uploading %(filename)s and %(count)s others.other": "กำลังอัปโหลด %(filename)s และอีก %(count)s ไฟล์",
"uploaded a file": "อัปโหลดไฟล์",
"Upload Failed": "การอัปโหลดล้มเหลว",
"Upload Files": "อัปโหลดไฟล์",
"Upload file": "อัปโหลดไฟล์",
"Usage": "การใช้งาน",
"User ID": "ID ผู้ใช้",
"User Interface": "อินเตอร์เฟสผู้ใช้",
"User name": "ชื่อผู้ใช้",
"User": "ผู้ใช้",
"Warning!": "คำเตือน!",
"Who can access this room?": "ใครสามารถเข้าถึงห้องนี้ได้?",
"Who can read history?": "ใครสามารถอ่านประวัติแชทได้?",
"Who would you like to add to this room?": "คุณต้องการเพิ่มใครเข้าห้องนี้?",
"Who would you like to communicate with?": "คุณต้องการสื่อสารกับใคร?",
"You're not in any rooms yet! Press": "คุณยังไม่ได้อยู่ในห้องใดเลย! กด",
"You are trying to access %(roomName)s.": "คุณกำลังพยายามเข้าสู่ %(roomName)s",
"You have <a>disabled</a> URL previews by default.": "ค่าเริ่มต้นของคุณ<a>ปิดใช้งาน</a>ตัวอย่าง URL เอาไว้",
"You have <a>enabled</a> URL previews by default.": "ค่าเริ่มต้นของคุณ<a>เปิดใช้งาน</a>ตัวอย่าง URL เอาไว้",
"you must be a": "คุณต้องเป็น",
"You must <a>register</a> to use this functionality": "คุณต้อง<a>ลงทะเบียน</a>เพื่อใช้ฟังก์ชันนี้",
"You need to be logged in.": "คุณต้องเข้าสู่ระบบก่อน",
"You need to enter a user name.": "คุณต้องกรอกชื่อผู้ใช้ก่อน",
"Your password has been reset": "รหัสผ่านถูกรีเซ็ตแล้ว",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "การเปลี่ยนรหัสผ่านเสร็จสมบูณณ์ คุณจะไม่ได้รับการแจ้งเตือนบนอุปกรณ์อื่น ๆ จนกว่าคุณจะกลับเข้าสู่ระบบในอุปกรณ์เหล่านั้น",
"Sun": "อา.",
"Mon": "จ.",
"Tue": "อ.",
"Wed": "พ.",
"Thu": "พฤ.",
"Fri": "ศ.",
"Sat": "ส.",
"Jan": "ม.ค.",
"Feb": "ก.พ.",
"Mar": "มี.ค.",
"Apr": "เม.ย.",
"May": "พ.ค.",
"Jun": "มิ.ย.",
"Jul": "ก.ค.",
"Aug": "ส.ค.",
"Sep": "ก.ย.",
"Oct": "ต.ค.",
"Nov": "พ.ย.",
"Dec": "ธ.ค.",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s %(day)s %(monthName)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s %(day)s %(monthName)s %(fullYear)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "ตั้งชื่อที่แสดง:",
"Set a Display Name": "ตั้งชื่อที่แสดง",
"Passwords don't match.": "รหัสผ่านไม่ตรงกัน",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "รหัสผ่านสั้นเกินไป (ขึ้นต่ำ %(MIN_PASSWORD_LENGTH)s ตัวอักษร)",
"An unknown error occurred.": "เกิดข้อผิดพลาดที่ไม่รู้จัก",
"I already have an account": "ฉันมีบัญชีอยู่แล้ว",
"An error occured: %(error_string)s": "เกิดข้อผิดพลาด: %(error_string)s",
"Topic": "หัวข้อ",
"Make Moderator": "เลื่อนขั้นเป็นผู้ช่วยดูแล",
"Make this room private": "ทำให้ห้องนี้เป็นส่วนตัว",
"Share message history with new users": "แบ่งประวัติแชทให้ผู้ใช้ใหม่",
"Encrypt room": "เข้ารหัสห้อง",
"Room": "ห้อง",
"(~%(searchCount)s results)": "(~%(searchCount)s ผลลัพธ์)",
"or": "หรือ",
"bold": "หนา",
"italic": "เอียง",
"strike": "ขีดทับ",
"underline": "ขีดเส้นใต้",
"code": "โค๊ด",
"quote": "อ้างอิง",
"were kicked %(repeats)s times": "ถูกเตะ %(repeats)s ครั้ง",
"was kicked %(repeats)s times": "ถูกเตะ %(repeats)s ครั้ง",
"were kicked": "ถูกเตะ",
"was kicked": "ถูกเตะ",
"New Password": "รหัสผ่านใหม่",
"Options": "ตัวเลือก",
"Export room keys": "ส่งออกกุณแจห้อง",
"Confirm passphrase": "ยืนยันรหัสผ่าน",
"Import room keys": "นำเข้ากุณแจห้อง",
"File to import": "ไฟล์ที่จะนำเข้า",
"Start new chat": "เริ่มแชทใหม่",
"Failed to invite": "การเชิญล้มเหลว",
"Failed to invite user": "การเชิญผู้ใช้ล้มเหลว",
"Failed to invite the following users to the %(roomName)s room:": "การเชิญผู้ใช้เหล่านี้เข้าสู่ห้อง %(roomName)s ล้มเหลว:",
"Confirm Removal": "ยืนยันการลบ",
"Unknown error": "ข้อผิดพลาดที่ไม่รู้จัก",
"Incorrect password": "รหัสผ่านไม่ถูกต้อง",
"Device name": "ชื่ออุปกรณ์",
"Device key": "Key อุปกรณ์",
"Unknown devices": "อุปกรณ์ที่ไม่รู้จัก",
"Unknown Address": "ที่อยู่ที่ไม่รู้จัก",
"Unblacklist": "ถอดบัญชีดำ",
"Blacklist": "ขึ้นบัญชีดำ",
"ex. @bob:example.com": "เช่น @bob:example.com",
"Add User": "เพิ่มผู้ใช้",
"This Home Server would like to make sure you are not a robot": "เซิร์ฟเวอร์บ้านต้องการยืนยันว่าคุณไม่ใช่หุ่นยนต์",
"Sign in with CAS": "เข้าสู่ระบบด้วย CAS",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "คุณสามารถกำหนดเซิร์ฟเวอร์บ้านเองได้โดยใส่ URL ของเซิร์ฟเวอร์นั้น เพื่อเข้าสู่ระบบของเซิร์ฟเวอร์ Matrix อื่น",
"This allows you to use this app with an existing Matrix account on a different home server.": "ทั้งนี่เพื่อให้คุณสามารถใช้ Riot กับบัญชี Matrix ที่มีอยู่แล้วบนเซิร์ฟเวอร์บ้านอื่น ๆ ได้",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "คุณอาจเลือกเซิร์ฟเวอร์ระบุตัวตนเองด้วยก็ได้ แต่คุณจะไม่สามารถเชิญผู้ใช้อื่นด้วยที่อยู่อีเมล หรือรับคำเชิญจากผู้ใช้อื่นทางที่อยู่อีเมลได้",
"Default server": "เซิร์ฟเวอร์เริ่มต้น",
"Custom server": "เซิร์ฟเวอร์ที่กำหนดเอง",
"Home server URL": "URL เซิร์ฟเวอร์บ้าน",
"Identity server URL": "URL เซิร์ฟเวอร์ระบุตัวตน",
"%(severalUsers)sleft %(repeats)s times": "%(targetName)sออกจากห้อง %(repeats)s ครั้ง",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sออกจากห้อง %(repeats)s ครั้ง",
"%(severalUsers)sleft": "%(severalUsers)sออกจากห้องแล้ว",
"%(oneUser)sleft": "%(oneUser)sออกจากห้องแล้ว"
}

View File

@@ -0,0 +1,209 @@
{
"Create an account": "创建新账号",
"Create Room": "创建聊天室",
"Cryptography": "加密",
"Current password": "当前密码",
"/ddg is not a command": "/ddg 不是一个命令",
"Deactivate Account": "销毁账号",
"Deactivate my account": "销毁我的账号",
"decline": "拒绝",
"Decrypt %(text)s": "解密 %(text)s",
"Decryption error": "解密出错",
"Delete": "删除",
"Default": "默认",
"Device ID": "设备识别码",
"Devices": "设备列表",
"Devices will not yet be able to decrypt history from before they joined the room": "新加入聊天室的设备不能解密加入之前的聊天记录",
"Direct Chat": "私聊",
"Direct chats": "私聊",
"Disable inline URL previews by default": "默认禁用自动网址预览",
"Disinvite": "取消邀请",
"Display name": "显示名称",
"Displays action": "显示操作",
"Don't send typing notifications": "不要发送我的打字状态",
"Download %(text)s": "下载 %(text)s",
"Drop here %(toAction)s": "拖拽到这里 %(toAction)s",
"Email": "电子邮箱",
"Email address": "电子邮箱地址",
"Email, name or matrix ID": "电子邮箱姓名或者matrix ID",
"Emoji": "Emoji",
"Enable encryption": "启用加密",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "不支持加密的客户端将看不到加密的消息",
"Encrypted room": "加密聊天室",
"%(senderName)s ended the call.": "%(senderName)s 结束了通话。.",
"End-to-end encryption information": "端到端加密信息",
"End-to-end encryption is in beta and may not be reliable": "端到端加密现为测试版,不一定可靠",
"Enter Code": "输入代码",
"Error": "错误",
"Error decrypting attachment": "解密附件时出错",
"Event information": "事件信息",
"Existing Call": "现有通话",
"Export E2E room keys": "导出聊天室的端到端加密密钥",
"Failed to ban user": "封禁用户失败",
"Failed to change password. Is your password correct?": "修改密码失败。确认原密码输入正确吗?",
"Failed to delete device": "删除设备失败",
"Failed to forget room %(errCode)s": "无法忘记聊天室 %(errCode)s",
"Failed to join room": "无法加入聊天室",
"Failed to join the room": "无法加入此聊天室",
"Failed to kick": "踢人失败",
"Failed to leave room": "无法离开聊天室",
"Failed to load timeline position": "无法加载时间轴位置",
"Failed to lookup current room": "找不到当前聊天室",
"Failed to mute user": "禁言用户失败",
"Failed to reject invite": "拒绝邀请失败",
"Failed to reject invitation": "拒绝邀请失败",
"Failed to save settings": "保存设置失败",
"Failed to send email": "发送邮件失败",
"Failed to send request.": "发送请求失败。",
"Failed to set avatar.": "设置头像失败。.",
"Failed to set display name": "设置昵称失败",
"Failed to set up conference call": "无法启动群组通话",
"Failed to toggle moderator status": "无法切换管理员权限",
"Failed to unban": "解除封禁失败",
"Failed to upload file": "上传文件失败",
"Failed to verify email address: make sure you clicked the link in the email": "邮箱验证失败: 请确保你已点击邮件中的链接",
"Failure to create room": "创建聊天室失败",
"Favourite": "收藏",
"favourite": "收藏",
"Favourites": "收藏夹",
"Fill screen": "满屏显示",
"Filter room members": "过滤聊天室成员",
"Forget room": "忘记聊天室",
"Forgot your password?": "忘记密码?",
"For security, this session has been signed out. Please sign in again.": "出于安全考虑,此会话已被注销。请重新登录。.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "出于安全考虑,用户注销时会清除浏览器里的端到端加密密钥。如果你想要下次登录 Riot 时能解密过去的聊天记录,请导出你的聊天室密钥。",
"Found a bug?": "发现漏洞?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s 从 %(fromPowerLevel)s 变为 %(toPowerLevel)s",
"Guests can't set avatars. Please register.": "游客不能设置头像。请注册。.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "游客不能创建聊天室。请注册以创建聊天室和聊天.",
"Guest users can't upload files. Please register to upload.": "游客不能上传文件。请注册以上传文件",
"Guests can't use labs features. Please register.": "游客不能使用实验性功能。请注册。.",
"Guests cannot join this room even if explicitly invited.": "游客不能加入此聊天室,即使有人主动邀请。.",
"had": "已经",
"Hangup": "挂断",
"Hide read receipts": "隐藏已读回执",
"Hide Text Formatting Toolbar": "隐藏格式工具栏",
"Historical": "历史",
"Homeserver is": "主服务器是",
"Identity Server is": "身份认证服务器是",
"I have verified my email address": "我已经验证了我的邮箱地址",
"Import E2E room keys": "导入聊天室端对端加密密钥",
"Incorrect verification code": "验证码错误",
"Interface Language": "界面语言",
"Invalid alias format": "别名格式错误",
"Invalid address format": "地址格式错误",
"Invalid Email Address": "邮箱地址格式错误",
"Invalid file%(extra)s": "非法文件%(extra)s",
"Report it": "报告",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "重设密码会导致所有设备上的端到端加密密钥被重置,使得加密的聊天记录不可读,除非你事先导出密钥,修改密码后再导入。此问题将来会得到改善。.",
"restore": "恢复",
"Return to app": "返回 App",
"Return to login screen": "返回登录页面",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot 未被允许向你推送消息 - 请检查浏览器设置",
"Riot was not given permission to send notifications - please try again": "Riot 未被允许推送消息通知 - 请重试",
"riot-web version:": "riot-网页版:",
"Room %(roomId)s not visible": "聊天室 %(roomId)s 已隐藏",
"Room Colour": "聊天室颜色",
"Room name (optional)": "聊天室名称 (可选)",
"Rooms": "聊天室",
"Scroll to bottom of page": "滚动到页面底部",
"Scroll to unread messages": "滚动到未读消息",
"Search": "搜索",
"Search failed": "搜索失败",
"Searches DuckDuckGo for results": "搜索 DuckDuckGo",
"Send a message (unencrypted)": "发送消息 (非加密)",
"Send an encrypted message": "发送加密消息",
"Sender device information": "发送者的设备信息",
"Send Invites": "发送邀请",
"Send Reset Email": "发送密码重设邮件",
"sent an image": "发了一张图片",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s 发了一张图片。.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s 向 %(targetDisplayName)s 发了加入聊天室的邀请。.",
"sent a video": "发了一个视频",
"Server error": "服务器错误",
"Server may be unavailable or overloaded": "服务器可能不可用或者超载",
"Server may be unavailable, overloaded, or search timed out :(": "服务器可能不可用、超载,或者搜索超时 :(",
"Server may be unavailable, overloaded, or the file too big": "服务器可能不可用、超载,或者文件过大",
"Server may be unavailable, overloaded, or you hit a bug.": "服务器可能不可用、超载,或者你遇到了一个漏洞.",
"Server unavailable, overloaded, or something else went wrong.": "服务器可能不可用、超载,或者其他东西出错了.",
"Session ID": "会话 ID",
"%(senderName)s set a profile picture.": "%(senderName)s 设置了头像。.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s 将昵称改为了 %(displayName)s。.",
"Settings": "设置",
"Show panel": "显示侧边栏",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "用12小时制显示时间戳 (如:下午 2:30",
"Signed Out": "已退出登录",
"Sign in": "登录",
"Sign out": "注销",
"since the point in time of selecting this option": "从选择此选项起",
"since they joined": "从他们加入时起",
"since they were invited": "从他们被邀请时起",
"Some of your messages have not been sent.": "部分消息发送失败",
"Someone": "某个用户",
"Sorry, this homeserver is using a login which is not recognised ": "很抱歉,无法识别此主服务器使用的登录方式 ",
"Start a chat": "创建聊天",
"Start Chat": "开始聊天",
"Submit": "提交",
"Success": "成功",
"The default role for new room members is": "此聊天室新成员的默认角色是",
"The main address for this room is": "此聊天室的主要地址是",
"This action cannot be performed by a guest user. Please register to be able to do this.": "游客不能进行此操作。请注册",
"This email address is already in use": "此邮箱地址已经被使用",
"This email address was not found": "未找到此邮箱地址",
"%(actionVerb)s this person?": "%(actionVerb)s 这个用户?",
"The email address linked to your account must be entered.": "必须输入和你账号关联的邮箱地址。",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "文件 '%(fileName)s' 超过了此主服务器的上传大小限制",
"The file '%(fileName)s' failed to upload": "文件 '%(fileName)s' 上传失败",
"Disable URL previews for this room (affects only you)": "在这个房间禁止URL预览只影响你",
"af": "南非荷兰语",
"ca": "加泰罗尼亚语",
"cs": "捷克语",
"da": "丹麦语",
"de-at": "德语(奥地利)",
"de-ch": "德语(瑞士)",
"de": "德语",
"de-lu": "德语(卢森堡)",
"el": "希腊语",
"en-au": "英语(澳大利亚)",
"en": "英语",
"zh-cn": "中文(中国)",
"zh-hk": "中文(香港)",
"zh-sg": "中文(新加坡)",
"zh-tw": "中国(台湾)",
"Add email address": "添加邮件地址",
"Add phone number": "添加电话号码",
"Advanced": "高级",
"Algorithm": "算法",
"Always show message timestamps": "总是显示消息时间戳",
"all room members": "所有聊天室成员",
"all room members, from the point they are invited": "所有聊天室成员,从他们被邀请开始",
"all room members, from the point they joined": "所有聊天室成员,从他们加入开始",
"an address": "一个地址",
"and": "和",
"%(names)s and %(lastPerson)s are typing": "%(names)s 和 %(lastPerson)s 正在打字",
"%(names)s and %(count)s others are typing": "%(names)s 和另外 %(count)s 个人正在打字",
"An email has been sent to": "一封邮件已经被发送到",
"A new password must be entered.": "一个新的密码必须被输入。.",
"%(senderName)s answered the call.": "%(senderName)s 接了通话。.",
"An error has occurred.": "一个错误出现了。",
"Attachment": "附件",
"Autoplay GIFs and videos": "自动播放GIF和视频",
"%(senderName)s banned %(targetName)s.": "%(senderName)s 封禁了 %(targetName)s.",
"Ban": "封禁",
"Banned users": "被封禁的用户",
"Click here": "点击这里",
"Click here to fix": "点击这里修复",
"Confirm password": "确认密码",
"Confirm your new password": "确认你的新密码",
"Continue": "继续",
"Ed25519 fingerprint": "Ed25519指纹",
"Invite new room members": "邀请新的聊天室成员",
"Join Room": "加入聊天室",
"joined": "加入了",
"%(targetName)s joined the room.": "%(targetName)s 加入了聊天室。.",
"Jump to first unread message.": "跳到第一条未读消息。",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s 把 %(targetName)s 踢出了聊天室。.",
"Leave room": "离开聊天室",
"Login as guest": "以游客的身份登录",
"New password": "新密码"
}

View File

@@ -0,0 +1,365 @@
{
"An email has been sent to": "一封郵件已經被發送到",
"A new password must be entered.": "一個新的密碼必須被輸入。.",
"anyone": "任何人",
"An error has occurred.": "一個錯誤出現了。",
"Anyone who knows the room's link, apart from guests": "任何知道房間連結的人,但訪客除外",
"Anyone who knows the room's link, including guests": "任何知道房間連結的人,包括訪客",
"Are you sure?": "你確定嗎?",
"Are you sure you want to reject the invitation?": "您確認要謝絕邀請嗎?",
"Are you sure you want to upload the following files?": "您確認要上傳以下文件嗎?",
"Attachment": "附件",
"Autoplay GIFs and videos": "自動播放GIF和視頻",
"%(senderName)s banned %(targetName)s.": "%(senderName)s 封禁了 %(targetName)s.",
"Ban": "封禁",
"Banned users": "被封禁的用戶",
"Blacklisted": "已列入黑名單",
"Bug Report": "臭蟲回報",
"Call Timeout": "通話超時",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "無法連結主伺服器 - 請檢查網路狀況並確保您的 <a>主伺服器 SSL 證書</a> 得到信任",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "當瀏覽器網址列里有 HTTPS URL 時,不能使用 HTTP 連結主伺服器。請採用 HTTPS 或者 <a>允許不安全的腳本</a>",
"Can't load user settings": "無法載入使用者設定",
"Change Password": "變更密碼",
"%(targetName)s left the room.": "%(targetName)s 離開了聊天室。.",
"af": "南非荷蘭語",
"ar-ae": "阿拉伯語 (U.A.E.)",
"ar-bh": "阿拉伯語 (巴林)",
"ar-dz": "阿拉伯語 (阿爾吉利亞)",
"ar-eg": "阿拉伯語 (埃及)",
"ar-iq": "阿拉伯語 (伊拉克)",
"ar-jo": "阿拉伯語 (約旦)",
"ar-kw": "阿拉伯語 (科威特)",
"ar-lb": "阿拉伯語 (黎巴嫩)",
"ar-ly": "阿拉伯語 (利比亞)",
"ar-ma": "阿拉伯語 (摩洛哥)",
"ar-om": "阿拉伯語 (阿曼)",
"ar-qa": "阿拉伯語 (卡達)",
"ar-sa": "阿拉伯語 (沙烏地阿拉伯)",
"ar-sy": "阿拉伯語 (敍利亞)",
"ar-tn": "阿拉伯語 (突尼斯)",
"ar-ye": "阿拉伯語 (葉門)",
"be": "白俄羅斯語",
"bg": "保加利亞",
"ca": "加泰羅尼亞語",
"cs": "捷克語",
"da": "丹麥語",
"de-at": "德語(奧地利)",
"de-ch": "德語(瑞士)",
"de": "德語",
"de-lu": "德語(盧森堡)",
"el": "希臘語",
"en-au": "英語(澳大利亞)",
"en-bz": "英語 (貝里茲)",
"en-ca": "英語 (加拿大)",
"en": "英語",
"en-gb": "英語 (英國)",
"en-ie": "英語 (愛爾蘭)",
"en-jm": "英語 (牙買加)",
"en-nz": "英語 (新西蘭)",
"en-tt": "英語 (千里達)",
"en-us": "英語 (美國)",
"en-za": "英語 (南非)",
"es-ar": "西班牙語 (阿根廷)",
"es-bo": "西班牙語 (波利維亞)",
"es-cl": "西班牙語 (智利)",
"es-co": "西班牙語 (哥倫比亞)",
"es-cr": "西班牙語 (哥斯大黎加)",
"es-do": "西班牙語 (多明尼加共和國)",
"es-ec": "西班牙語 (厄瓜多)",
"es-gt": "西班牙語 (瓜地馬拉)",
"es-hn": "西班牙語 (宏都拉斯)",
"es-mx": "西班牙語 (墨西哥)",
"es-ni": "西班牙語 (尼加拉瓜)",
"es-pa": "西班牙語 (巴拿馬)",
"es-pe": "西班牙語 (祕魯)",
"es-pr": "西班牙語 (波多黎各)",
"es-py": "西班牙語 (巴拉圭)",
"es": "西班牙語 (西班牙)",
"es-sv": "西班牙語 (薩爾瓦多)",
"es-uy": "西班牙語 (烏拉圭)",
"es-ve": "西班牙語 (委內瑞拉)",
"fr-be": "法語 (比利時)",
"fr-ca": "法語 (加拿大)",
"fr-ch": "法語 (瑞士)",
"fr": "法語 (法國)",
"fr-lu": "法語 (慮森堡)",
"zh-cn": "中文(中國)",
"zh-hk": "中文(香港)",
"zh-sg": "中文(新加坡)",
"zh-tw": "中文(台灣)",
"zu": "祖魯語",
"accept": "接受",
"Account": "帳號",
"Access Token:": "取用令牌:",
"Add email address": "添加郵件地址",
"Add phone number": "添加電話號碼",
"Admin": "管理者",
"Advanced": "高級",
"Algorithm": "算法",
"Always show message timestamps": "總是顯示消息時間戳",
"Authentication": "授權",
"all room members": "所有聊天室成員",
"all room members, from the point they are invited": "所有聊天室成員,從他們被邀請開始",
"all room members, from the point they joined": "所有聊天室成員,從他們加入開始",
"an address": "一個地址",
"and": "和",
"%(items)s and %(remaining)s others": "%(items)s 和 %(remaining)s 其它",
"%(items)s and one other": "%(items)s 和其它",
"%(items)s and %(lastItem)s": "%(items)s 和 %(lastItem)s",
"and one other...": "與另一個...",
"%(names)s and %(lastPerson)s are typing": "%(names)s 和 %(lastPerson)s 正在打字",
"%(names)s and %(count)s others are typing": "%(names)s 和另外 %(count)s 個人正在打字",
"%(senderName)s answered the call.": "%(senderName)s 接了通話。.",
"Clear Cache": "清理緩存",
"Click here": "點擊這里",
"Click here to fix": "點擊這里修復",
"Confirm password": "確認密碼",
"Confirm your new password": "確認你的新密碼",
"Continue": "繼續",
"Create an account": "創建新帳號",
"Create Room": "創建聊天室",
"Cryptography": "加密",
"Current password": "當前密碼",
"/ddg is not a command": "/ddg 不是一個命令",
"Deactivate Account": "銷毀賬號",
"Deactivate my account": "銷毀我的帳號",
"decline": "拒絕",
"Decrypt %(text)s": "解密 %(text)s",
"Decryption error": "解密出錯",
"Delete": "刪除",
"Default": "默認",
"Device ID": "設備識別碼",
"Devices": "設備列表",
"Devices will not yet be able to decrypt history from before they joined the room": "新加入聊天室的設備不能解密加入之前的聊天記錄",
"Direct Chat": "私聊",
"Direct chats": "私聊",
"Disable inline URL previews by default": "默認禁用自動網址預覽",
"Disinvite": "取消邀請",
"Display name": "顯示名稱",
"Displays action": "顯示操作",
"Don't send typing notifications": "不要發送我的打字狀態",
"Download %(text)s": "下載 %(text)s",
"Drop here %(toAction)s": "拖拽到這里 %(toAction)s",
"Ed25519 fingerprint": "Ed25519指紋",
"Email": "電子郵箱",
"Email address": "電子郵箱地址",
"Email, name or matrix ID": "電子郵箱姓名或者matrix ID",
"Emoji": "Emoji",
"Enable encryption": "啟用加密",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "不支持加密的客戶端將看不到加密的消息",
"Encrypted room": "加密聊天室",
"%(senderName)s ended the call.": "%(senderName)s 結束了通話。.",
"End-to-end encryption information": "端到端加密信息",
"End-to-end encryption is in beta and may not be reliable": "端到端加密現為測試版,不一定可靠",
"Enter Code": "輸入代碼",
"Error": "錯誤",
"Error decrypting attachment": "解密附件時出錯",
"Event information": "事件信息",
"Existing Call": "現有通話",
"Export E2E room keys": "導出聊天室的端到端加密密鑰",
"Failed to ban user": "封禁用戶失敗",
"Failed to change password. Is your password correct?": "修改密碼失敗。確認原密碼輸入正確嗎?",
"Failed to delete device": "刪除設備失敗",
"Failed to forget room %(errCode)s": "無法忘記聊天室 %(errCode)s",
"Failed to join room": "無法加入聊天室",
"Failed to join the room": "無法加入此聊天室",
"Failed to kick": "踢人失敗",
"Failed to leave room": "無法離開聊天室",
"Failed to load timeline position": "無法加載時間軸位置",
"Failed to lookup current room": "找不到當前聊天室",
"Failed to mute user": "禁言用戶失敗",
"Failed to reject invite": "拒絕邀請失敗",
"Failed to reject invitation": "拒絕邀請失敗",
"Failed to save settings": "保存設置失敗",
"Failed to send email": "發送郵件失敗",
"Failed to send request.": "發送請求失敗。",
"Failed to set avatar.": "設置頭像失敗。.",
"Failed to set display name": "設置暱稱失敗",
"Failed to set up conference call": "無法啟動群組通話",
"Failed to toggle moderator status": "無法切換管理員權限",
"Failed to unban": "解除封禁失敗",
"Failed to upload file": "上傳文件失敗",
"Failed to verify email address: make sure you clicked the link in the email": "郵箱驗證失敗: 請確保你已點擊郵件中的鏈接",
"Failure to create room": "創建聊天室失敗",
"Favourite": "收藏",
"favourite": "收藏",
"Favourites": "收藏夾",
"Fill screen": "全螢幕顯示",
"Filter room members": "過濾聊天室成員",
"Forget room": "忘記聊天室",
"Forgot your password?": "忘記密碼?",
"For security, this session has been signed out. Please sign in again.": "出於安全考慮,此會話已被注銷。請重新登錄。.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "出於安全考慮,用戶注銷時會清除瀏覽器里的端到端加密密鑰。如果你想要下次登錄 Riot 時能解密過去的聊天記錄,請導出你的聊天室密鑰。",
"Found a bug?": "發現漏洞?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s 從 %(fromPowerLevel)s 變為 %(toPowerLevel)s",
"Guests can't set avatars. Please register.": "游客不能設置頭像。請注冊。.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "游客不能創建聊天室。請注冊以創建聊天室和聊天.",
"Guest users can't upload files. Please register to upload.": "游客不能上傳文件。請注冊以上傳文件",
"Guests can't use labs features. Please register.": "游客不能使用實驗性功能。請注冊。.",
"Guests cannot join this room even if explicitly invited.": "游客不能加入此聊天室,即使有人主動邀請。.",
"had": "已經",
"Hangup": "掛斷",
"Hide read receipts": "隱藏已讀回執",
"Hide Text Formatting Toolbar": "隱藏格式工具欄",
"Historical": "曆史",
"Homeserver is": "主服務器是",
"Identity Server is": "身份認證服務器是",
"I have verified my email address": "我已經驗證了我的郵箱地址",
"Import E2E room keys": "導入聊天室端對端加密密鑰",
"Incorrect verification code": "驗證碼錯誤",
"Interface Language": "界面語言",
"Invalid alias format": "別名格式錯誤",
"Invalid address format": "地址格式錯誤",
"Invalid Email Address": "郵箱地址格式錯誤",
"Invalid file%(extra)s": "非法文件%(extra)s",
"Invite new room members": "邀請新的聊天室成員",
"Join Room": "加入聊天室",
"joined": "加入了",
"%(targetName)s joined the room.": "%(targetName)s 加入了聊天室。.",
"Jump to first unread message.": "跳到第一條未讀消息。",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s 把 %(targetName)s 踢出了聊天室。.",
"Leave room": "離開聊天室",
"Login as guest": "以游客的身份登錄",
"New password": "新密碼",
"Report it": "報告",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "重設密碼會導致所有設備上的端到端加密密鑰被重置,使得加密的聊天記錄不可讀,除非你事先導出密鑰,修改密碼后再導入。此問題將來會得到改善。.",
"restore": "恢復",
"Return to app": "返回 App",
"Return to login screen": "返回登錄頁面",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot 未被允許向你推送消息 - 請檢查瀏覽器設置",
"Riot was not given permission to send notifications - please try again": "Riot 未被允許推送消息通知 - 請重試",
"riot-web version:": "riot-網頁版:",
"Room %(roomId)s not visible": "聊天室 %(roomId)s 已隱藏",
"Room Colour": "聊天室顏色",
"Room name (optional)": "聊天室名稱 (可選)",
"Rooms": "聊天室",
"Scroll to bottom of page": "滾動到頁面底部",
"Scroll to unread messages": "滾動到未讀消息",
"Search": "搜索",
"Search failed": "搜索失敗",
"Searches DuckDuckGo for results": "搜索 DuckDuckGo",
"Send a message (unencrypted)": "發送消息 (非加密)",
"Send an encrypted message": "發送加密消息",
"Sender device information": "發送者的設備信息",
"Send Invites": "發送邀請",
"Send Reset Email": "發送密碼重設郵件",
"sent an image": "發了一張圖片",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s 發了一張圖片。.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s 向 %(targetDisplayName)s 發了加入聊天室的邀請。.",
"sent a video": "發了一個視頻",
"Server error": "伺服器錯誤",
"Server may be unavailable or overloaded": "服務器可能不可用或者超載",
"Server may be unavailable, overloaded, or search timed out :(": "服務器可能不可用、超載,或者搜索超時 :(",
"Server may be unavailable, overloaded, or the file too big": "服務器可能不可用、超載,或者文件過大",
"Server may be unavailable, overloaded, or you hit a bug.": "服務器可能不可用、超載,或者你遇到了一個漏洞.",
"Server unavailable, overloaded, or something else went wrong.": "伺服器可能不可用、超載,或者其他東西出錯了.",
"Session ID": "會話 ID",
"%(senderName)s set a profile picture.": "%(senderName)s 設置了頭像。.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s 將暱稱改為了 %(displayName)s。.",
"Settings": "設置",
"Show panel": "顯示側邊欄",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "用12小時制顯示時間戳 (如:下午 2:30",
"Signed Out": "已退出登錄",
"Sign in": "登錄",
"Sign out": "注銷",
"since the point in time of selecting this option": "從選擇此選項起",
"since they joined": "從他們加入時起",
"since they were invited": "從他們被邀請時起",
"Some of your messages have not been sent.": "部分消息發送失敗",
"Someone": "某個用戶",
"Sorry, this homeserver is using a login which is not recognised ": "很抱歉,無法識別此主伺服器使用的登錄方式 ",
"Start a chat": "創建聊天",
"Start Chat": "開始聊天",
"Submit": "提交",
"Success": "成功",
"The default role for new room members is": "此聊天室新成員的默認角色是",
"The main address for this room is": "此聊天室的主要地址是",
"This action cannot be performed by a guest user. Please register to be able to do this.": "訪客不能進行此操作。請注冊",
"This email address is already in use": "此郵箱地址已經被使用",
"This email address was not found": "未找到此郵箱地址",
"%(actionVerb)s this person?": "%(actionVerb)s 這個用戶?",
"The email address linked to your account must be entered.": "必須輸入和你帳號關聯的郵箱地址。",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "文件 '%(fileName)s' 超過了此主伺服器的上傳大小限制",
"The file '%(fileName)s' failed to upload": "文件 '%(fileName)s' 上傳失敗",
"Turn Markdown off": "關閉Markdown 語法",
"Turn Markdown on": "啟用Markdown 語法",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s 啟用端對端加密 (algorithm %(algorithm)s).",
"Unable to add email address": "無法加入電郵地址",
"Unable to capture screen": "無法截取畫面",
"Unable to enable Notifications": "無法啟用通知功能",
"Would you like to": "你要",
"You are already in a call.": "你已在電話通話中",
"You're not in any rooms yet! Press": "你尚未加入任何聊天室!請按",
"You are trying to access %(roomName)s.": "你將進入 %(roomName)聊天室",
"You cannot place a call with yourself.": "你不能打電話給自已",
"You cannot place VoIP calls in this browser.": "在此瀏覽器下無法置入網路電話通話",
"Sun": "星期日",
"Mon": "星期一",
"Tue": "星期二",
"%(severalUsers)sleft": "%(severalUsers)s離開",
"%(oneUser)sleft": "%(oneUser)s離開",
"%(severalUsers)sjoined and left": "%(severalUsers)s加入與離開",
"%(oneUser)sleft and rejoined": "%(oneUser)s離開再重新加入",
"for %(amount)sh": " %(amount)sh",
"for %(amount)sd": " %(amount)sd",
"Online": "在線",
"Idle": "閒置",
"Offline": "下線",
"Disable URL previews for this room (affects only you)": "在這個房間禁止URL預覽只影響你",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName 更改了聊天室的圖像為 <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s 移除了聊天室圖像",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s 更改了聊天室 %(roomName)s 圖像",
"Cancel": "取消",
"Custom Server Options": "自定伺服器選項",
"Dismiss": "無視",
"Mute": "靜音",
"Notifications": "通知",
"Operation failed": "操作失敗",
"powered by Matrix": "由Matrix架設",
"Remove": "移除",
"unknown error code": "未知的錯誤代碼",
"Sunday": "星期日",
"Monday": "星期一",
"Tuesday": "星期二",
"Wednesday": "星期三",
"Thursday": "星期四",
"Friday": "星期五",
"Saturday": "星期六",
"OK": "OK",
"Please Register": "請註冊",
"Add a topic": "新增標題",
"VoIP": "VoIP",
"Default Device": "默認裝置",
"Microphone": "麥克風",
"Camera": "攝影機",
"Anyone": "任何人",
"Bulk Options": "批次選項",
"Command error": "指令出錯",
"Commands": "指令",
"Device ID:": "裝置 ID:",
"device id: ": "裝置 id:",
"Reason": "原因",
"Register": "注冊",
"Registration required": "要求註冊",
"rejected": "拒絕",
"Default server": "默認的伺服器",
"Custom server": "自定的伺服器",
"Home server URL": "自家伺服器網址",
"Identity server URL": "識別伺服器網址",
"What does this mean?": "它代表什麼意思?",
"Error decrypting audio": "解密音檔出錯",
"Error decrypting image": "解密圖片出錯",
"Image '%(Body)s' cannot be displayed.": "圖片 '%(Body)s' 無法顯示",
"This image cannot be displayed.": "這張圖片無法顯示",
"Error decrypting video": "解密影片出錯",
"Add an Integration": "新增整合器",
"Ongoing conference call%(supportedText)s. %(joinText)s": "%(supportedText)s. %(joinText)s 正在進行電話會議",
" (unsupported)": " (不支持)",
"URL Previews": "網址預覽",
"Enable URL previews for this room (affects only you)": "啟用此房間的網址預覽(僅影響您)",
"Drop file here to upload": "把文件放在這裡上傳",
"Disable URL previews by default for participants in this room": "默認情況下,此房間的參與者禁用網址預覽",
"URL previews are %(globalDisableUrlPreview)s by default for participants in this room.": "默認情況下,這個房間的參與者的網址預覽是%(globalDisableUrlPreview)。",
"Removed or unknown message type": "已刪除或未知的信息類型",
"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)。你想繼續嗎?"
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var Skinner = require('./Skinner');
import Skinner from './Skinner';
module.exports.loadSkin = function(skinObject) {
Skinner.load(skinObject);

261
src/languageHandler.js Normal file
View File

@@ -0,0 +1,261 @@
/*
Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import request from 'browser-request';
import counterpart from 'counterpart';
import q from 'q';
import UserSettingsStore from './UserSettingsStore';
const i18nFolder = 'i18n/';
// We use english strings as keys, some of which contain full stops
counterpart.setSeparator('|');
// Fall back to English
counterpart.setFallbackLocale('en');
// The translation function. This is just a simple wrapper to counterpart,
// but exists mostly because we must use the same counterpart instance
// between modules (ie. here (react-sdk) and the app (riot-web), and if we
// just import counterpart and use it directly, we end up using a different
// instance.
export function _t(...args) {
// Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
// The interpolation library that counterpart uses does not support undefined/null
// values and instead will throw an error. This is a problem since everywhere else
// in JS land passing undefined/null will simply stringify instead, and when converting
// valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null
// if there are no existing null guards. To avoid this making the app completely inoperable,
// we'll check all the values for undefined/null and stringify them here.
if (args[1] && typeof args[1] === 'object') {
Object.keys(args[1]).forEach((k) => {
if (args[1][k] === undefined) {
console.warn("_t called with undefined interpolation name: " + k);
args[1][k] = 'undefined';
}
if (args[1][k] === null) {
console.warn("_t called with null interpolation name: " + k);
args[1][k] = 'null';
}
});
}
return counterpart.translate(...args);
}
/*
* Translates stringified JSX into translated JSX. E.g
* _tJsx(
* "click <a href=''>here</a> now",
* /<a href=''>(.*?)<\/a>/,
* (sub) => { return <a href=''>{ sub }</a>; }
* );
*
* @param {string} jsxText The untranslated stringified JSX e.g "click <a href=''>here</a> now".
* This will be translated by passing the string through to _t(...)
*
* @param {RegExp|RegExp[]} patterns A regexp to match against the translated text.
* The captured groups from the regexp will be fed to 'sub'.
* Only the captured groups will be included in the output, the match itself is discarded.
* If multiple RegExps are provided, the function at the same position will be called. The
* match will always be done from left to right, so the 2nd RegExp will be matched against the
* remaining text from the first RegExp.
*
* @param {Function|Function[]} subs A function which will be called
* with multiple args, each arg representing a captured group of the matching regexp.
* This function must return a JSX node.
*
* @return A list of strings/JSX nodes.
*/
export function _tJsx(jsxText, patterns, subs) {
// convert everything to arrays
if (patterns instanceof RegExp) {
patterns = [patterns];
}
if (subs instanceof Function) {
subs = [subs];
}
// sanity checks
if (subs.length !== patterns.length || subs.length < 1) {
throw new Error(`_tJsx: programmer error. expected number of RegExps == number of Functions: ${subs.length} != ${patterns.length}`);
}
for (let i = 0; i < subs.length; i++) {
if (!patterns[i] instanceof RegExp) {
throw new Error(`_tJsx: programmer error. expected RegExp for text: ${jsxText}`);
}
if (!subs[i] instanceof Function) {
throw new Error(`_tJsx: programmer error. expected Function for text: ${jsxText}`);
}
}
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
const tJsxText = _t(jsxText);
let output = [tJsxText];
for (let i = 0; i < patterns.length; i++) {
// convert the last element in 'output' into 3 elements (pre-text, sub function, post-text).
// Rinse and repeat for other patterns (using post-text).
let inputText = output.pop();
let match = inputText.match(patterns[i]);
if (!match) {
throw new Error(`_tJsx: translator error. expected translation to match regexp: ${patterns[i]}`);
}
let capturedGroups = match.slice(1);
// Return the raw translation before the *match* followed by the return value of sub() followed
// by the raw translation after the *match* (not captured group).
output.push(inputText.substr(0, match.index));
output.push(subs[i].apply(null, capturedGroups));
output.push(inputText.substr(match.index + match[0].length));
}
return output;
}
// Allow overriding the text displayed when no translation exists
// Currently only used in unit tests to avoid having to load
// the translations in riot-web
export function setMissingEntryGenerator(f) {
counterpart.setMissingEntryGenerator(f);
}
export function setLanguage(preferredLangs) {
if (!Array.isArray(preferredLangs)) {
preferredLangs = [preferredLangs];
}
let langToUse;
let availLangs;
return getLangsJson().then((result) => {
availLangs = result;
for (let i = 0; i < preferredLangs.length; ++i) {
if (availLangs.hasOwnProperty(preferredLangs[i])) {
langToUse = preferredLangs[i];
break;
}
}
if (!langToUse) {
// Fallback to en_EN if none is found
langToUse = 'en'
console.error("Unable to find an appropriate language");
}
return getLanguage(i18nFolder + availLangs[langToUse].fileName);
}).then((langData) => {
counterpart.registerTranslations(langToUse, langData);
counterpart.setLocale(langToUse);
UserSettingsStore.setLocalSetting('language', langToUse);
console.log("set language to " + langToUse);
// Set 'en' as fallback language:
if (langToUse != "en") {
return getLanguage(i18nFolder + availLangs['en'].fileName);
}
}).then((langData) => {
if (langData) counterpart.registerTranslations('en', langData);
});
};
export function getAllLanguagesFromJson() {
return getLangsJson().then((langsObject) => {
var langs = [];
for (var langKey in langsObject) {
if (langsObject.hasOwnProperty(langKey)) {
langs.push({
'value': langKey,
'label': langsObject[langKey].label
});
}
}
return langs;
});
}
export function getLanguagesFromBrowser() {
if (navigator.languages && navigator.languages.length) return navigator.languages;
if (navigator.language) return [navigator.language];
return [navigator.userLanguage];
}
/**
* Turns a language string, normalises it,
* (see normalizeLanguageKey) into an array of language strings
* with fallback to generic languages
* (eg. 'pt-BR' => ['pt-br', 'pt'])
*
* @param {string} language The input language string
* @return {string[]} List of normalised languages
*/
export function getNormalizedLanguageKeys(language) {
const languageKeys = [];
const normalizedLanguage = this.normalizeLanguageKey(language);
const languageParts = normalizedLanguage.split('-');
if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
languageKeys.push(languageParts[0]);
} else {
languageKeys.push(normalizedLanguage);
if (languageParts.length == 2) {
languageKeys.push(languageParts[0]);
}
}
return languageKeys;
};
/**
* Returns a language string with underscores replaced with
* hyphens, and lowercased.
*/
export function normalizeLanguageKey(language) {
return language.toLowerCase().replace("_","-");
};
export function getCurrentLanguage() {
return counterpart.getLocale();
}
function getLangsJson() {
const deferred = q.defer();
request(
{ method: "GET", url: i18nFolder + 'languages.json' },
(err, response, body) => {
if (err || response.status < 200 || response.status >= 300) {
deferred.reject({err: err, response: response});
return;
}
deferred.resolve(JSON.parse(body));
}
);
return deferred.promise;
}
function getLanguage(langPath) {
const deferred = q.defer();
let response_return = {};
request(
{ method: "GET", url: langPath },
(err, response, body) => {
if (err || response.status < 200 || response.status >= 300) {
deferred.reject({err: err, response: response});
return;
}
deferred.resolve(JSON.parse(body));
}
);
return deferred.promise;
}

View File

@@ -0,0 +1,79 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import dis from '../dispatcher';
import {Store} from 'flux/utils';
const INITIAL_STATE = {
deferred_action: null,
};
/**
* A class for storing application state to do with login/registration. This is a simple
* flux store that listens for actions and updates its state accordingly, informing any
* listeners (views) of state changes.
*/
class LifecycleStore extends Store {
constructor() {
super(dis);
// Initialise state
this._state = INITIAL_STATE;
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this.__emitChange();
}
__onDispatch(payload) {
switch (payload.action) {
case 'do_after_sync_prepared':
this._setState({
deferred_action: payload.deferred_action,
});
break;
case 'cancel_after_sync_prepared':
this._setState({
deferred_action: null,
});
break;
case 'sync_state':
if (payload.state !== 'PREPARED') {
break;
}
if (!this._state.deferred_action) break;
const deferredAction = Object.assign({}, this._state.deferred_action);
this._setState({
deferred_action: null,
});
dis.dispatch(deferredAction);
break;
case 'on_logged_out':
this.reset();
break;
}
}
reset() {
this._state = Object.assign({}, INITIAL_STATE);
}
}
let singletonLifecycleStore = null;
if (!singletonLifecycleStore) {
singletonLifecycleStore = new LifecycleStore();
}
module.exports = singletonLifecycleStore;

205
src/stores/RoomViewStore.js Normal file
View File

@@ -0,0 +1,205 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import dis from '../dispatcher';
import {Store} from 'flux/utils';
import MatrixClientPeg from '../MatrixClientPeg';
import sdk from '../index';
import Modal from '../Modal';
import { _t } from '../languageHandler';
const INITIAL_STATE = {
// Whether we're joining the currently viewed room
joining: false,
// Any error occurred during joining
joinError: null,
// The room ID of the room
roomId: null,
// The room alias of the room (or null if not originally specified in view_room)
roomAlias: null,
// Whether the current room is loading
roomLoading: false,
// Any error that has occurred during loading
roomLoadError: null,
};
/**
* A class for storing application state for RoomView. This is the RoomView's interface
* with a subset of the js-sdk.
* ```
*/
class RoomViewStore extends Store {
constructor() {
super(dis);
// Initialise state
this._state = INITIAL_STATE;
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this.__emitChange();
}
__onDispatch(payload) {
switch (payload.action) {
// view_room:
// - room_alias: '#somealias:matrix.org'
// - room_id: '!roomid123:matrix.org'
case 'view_room':
this._viewRoom(payload);
break;
case 'view_room_error':
this._viewRoomError(payload);
break;
case 'will_join':
this._setState({
joining: true,
});
break;
case 'cancel_join':
this._setState({
joining: false,
});
break;
// join_room:
// - opts: options for joinRoom
case 'join_room':
this._joinRoom(payload);
break;
case 'joined_room':
this._joinedRoom(payload);
break;
case 'join_room_error':
this._joinRoomError(payload);
break;
case 'on_logged_out':
this.reset();
break;
}
}
_viewRoom(payload) {
// Always set the room ID if present
if (payload.room_id) {
this._setState({
roomId: payload.room_id,
roomLoading: false,
roomLoadError: null,
});
} else if (payload.room_alias) {
this._setState({
roomId: null,
roomAlias: payload.room_alias,
roomLoading: true,
roomLoadError: null,
});
MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done(
(result) => {
dis.dispatch({
action: 'view_room',
room_id: result.room_id,
room_alias: payload.room_alias,
});
}, (err) => {
dis.dispatch({
action: 'view_room_error',
room_id: null,
room_alias: payload.room_alias,
err: err,
});
});
}
}
_viewRoomError(payload) {
this._setState({
roomId: payload.room_id,
roomAlias: payload.room_alias,
roomLoading: false,
roomLoadError: payload.err,
});
}
_joinRoom(payload) {
this._setState({
joining: true,
});
MatrixClientPeg.get().joinRoom(this._state.roomId, payload.opts).done(() => {
dis.dispatch({
action: 'joined_room',
});
}, (err) => {
dis.dispatch({
action: 'join_room_error',
err: err,
});
const msg = err.message ? err.message : JSON.stringify(err);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to join room"),
description: msg,
});
});
}
_joinedRoom(payload) {
this._setState({
joining: false,
});
}
_joinRoomError(payload) {
this._setState({
joining: false,
joinError: payload.err,
});
}
reset() {
this._state = Object.assign({}, INITIAL_STATE);
}
getRoomId() {
return this._state.roomId;
}
getRoomAlias() {
return this._state.roomAlias;
}
isRoomLoading() {
return this._state.roomLoading;
}
getRoomLoadError() {
return this._state.roomLoadError;
}
isJoining() {
return this._state.joining;
}
getJoinError() {
return this._state.joinError;
}
}
let singletonRoomViewStore = null;
if (!singletonRoomViewStore) {
singletonRoomViewStore = new RoomViewStore();
}
module.exports = singletonRoomViewStore;

View File

@@ -0,0 +1,88 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import dis from '../dispatcher';
import {Store} from 'flux/utils';
const INITIAL_STATE = {
cachedPassword: localStorage.getItem('mx_pass'),
};
/**
* A class for storing application state to do with the session. This is a simple flux
* store that listens for actions and updates its state accordingly, informing any
* listeners (views) of state changes.
*
* Usage:
* ```
* sessionStore.addListener(() => {
* this.setState({ cachedPassword: sessionStore.getCachedPassword() })
* })
* ```
*/
class SessionStore extends Store {
constructor() {
super(dis);
// Initialise state
this._state = INITIAL_STATE;
}
_update() {
// Persist state to localStorage
if (this._state.cachedPassword) {
localStorage.setItem('mx_pass', this._state.cachedPassword);
} else {
localStorage.removeItem('mx_pass', this._state.cachedPassword);
}
this.__emitChange();
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this._update();
}
__onDispatch(payload) {
switch (payload.action) {
case 'cached_password':
this._setState({
cachedPassword: payload.cachedPassword,
});
break;
case 'password_changed':
this._setState({
cachedPassword: null,
});
break;
case 'on_logged_out':
this._setState({
cachedPassword: null,
});
break;
}
}
getCachedPassword() {
return this._state.cachedPassword;
}
}
let singletonSessionStore = null;
if (!singletonSessionStore) {
singletonSessionStore = new SessionStore();
}
module.exports = singletonSessionStore;

View File

@@ -26,3 +26,14 @@ export function formatCount(count) {
if (count < 100000000) return (count / 1000000).toFixed(0) + "M";
return (count / 1000000000).toFixed(1) + "B"; // 10B is enough for anyone, right? :S
}
/**
* format a key into groups of 4 characters, for easier visual inspection
*
* @param {string} key key to format
*
* @return {string}
*/
export function formatCryptoKey(key) {
return key.match(/.{1,4}/g).join(" ");
}

View File

@@ -32,7 +32,7 @@ const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle;
/**
* Decrypt a megolm key file
*
* @param {ArrayBuffer} file
* @param {ArrayBuffer} data file to decrypt
* @param {String} password
* @return {Promise<String>} promise for decrypted output
*/
@@ -61,12 +61,12 @@ export function decryptMegolmKeyFile(data, password) {
const hmac = body.subarray(-32);
return deriveKeys(salt, iterations, password).then((keys) => {
const [aes_key, hmac_key] = keys;
const [aesKey, hmacKey] = keys;
const toVerify = body.subarray(0, -32);
return subtleCrypto.verify(
{name: 'HMAC'},
hmac_key,
hmacKey,
hmac,
toVerify,
).then((isValid) => {
@@ -80,7 +80,7 @@ export function decryptMegolmKeyFile(data, password) {
counter: iv,
length: 64,
},
aes_key,
aesKey,
ciphertext,
);
});
@@ -102,7 +102,7 @@ export function decryptMegolmKeyFile(data, password) {
*/
export function encryptMegolmKeyFile(data, password, options) {
options = options || {};
const kdf_rounds = options.kdf_rounds || 500000;
const kdfRounds = options.kdf_rounds || 500000;
const salt = new Uint8Array(16);
window.crypto.getRandomValues(salt);
@@ -115,8 +115,8 @@ export function encryptMegolmKeyFile(data, password, options) {
// of a single bit of iv is a price we have to pay.
iv[9] &= 0x7f;
return deriveKeys(salt, kdf_rounds, password).then((keys) => {
const [aes_key, hmac_key] = keys;
return deriveKeys(salt, kdfRounds, password).then((keys) => {
const [aesKey, hmacKey] = keys;
return subtleCrypto.encrypt(
{
@@ -124,7 +124,7 @@ export function encryptMegolmKeyFile(data, password, options) {
counter: iv,
length: 64,
},
aes_key,
aesKey,
new TextEncoder().encode(data),
).then((ciphertext) => {
const cipherArray = new Uint8Array(ciphertext);
@@ -134,17 +134,17 @@ export function encryptMegolmKeyFile(data, password, options) {
resultBuffer[idx++] = 1; // version
resultBuffer.set(salt, idx); idx += salt.length;
resultBuffer.set(iv, idx); idx += iv.length;
resultBuffer[idx++] = kdf_rounds >> 24;
resultBuffer[idx++] = (kdf_rounds >> 16) & 0xff;
resultBuffer[idx++] = (kdf_rounds >> 8) & 0xff;
resultBuffer[idx++] = kdf_rounds & 0xff;
resultBuffer[idx++] = kdfRounds >> 24;
resultBuffer[idx++] = (kdfRounds >> 16) & 0xff;
resultBuffer[idx++] = (kdfRounds >> 8) & 0xff;
resultBuffer[idx++] = kdfRounds & 0xff;
resultBuffer.set(cipherArray, idx); idx += cipherArray.length;
const toSign = resultBuffer.subarray(0, idx);
return subtleCrypto.sign(
{name: 'HMAC'},
hmac_key,
hmacKey,
toSign,
).then((hmac) => {
hmac = new Uint8Array(hmac);
@@ -170,7 +170,7 @@ function deriveKeys(salt, iterations, password) {
new TextEncoder().encode(password),
{name: 'PBKDF2'},
false,
['deriveBits']
['deriveBits'],
).then((key) => {
return subtleCrypto.deriveBits(
{
@@ -180,33 +180,33 @@ function deriveKeys(salt, iterations, password) {
hash: 'SHA-512',
},
key,
512
512,
);
}).then((keybits) => {
const now = new Date();
console.log("E2e import/export: deriveKeys took " + (now - start) + "ms");
const aes_key = keybits.slice(0, 32);
const hmac_key = keybits.slice(32);
const aesKey = keybits.slice(0, 32);
const hmacKey = keybits.slice(32);
const aes_prom = subtleCrypto.importKey(
const aesProm = subtleCrypto.importKey(
'raw',
aes_key,
aesKey,
{name: 'AES-CTR'},
false,
['encrypt', 'decrypt']
['encrypt', 'decrypt'],
);
const hmac_prom = subtleCrypto.importKey(
const hmacProm = subtleCrypto.importKey(
'raw',
hmac_key,
hmacKey,
{
name: 'HMAC',
hash: {name: 'SHA-256'},
},
false,
['sign', 'verify']
['sign', 'verify'],
);
return Promise.all([aes_prom, hmac_prom]);
return Promise.all([aesProm, hmacProm]);
});
}
@@ -301,7 +301,7 @@ function packMegolmKeyFile(data) {
function encodeBase64(uint8Array) {
// Misinterpt the Uint8Array as Latin-1.
// window.btoa expects a unicode string with codepoints in the range 0-255.
var latin1String = String.fromCharCode.apply(null, uint8Array);
const latin1String = String.fromCharCode.apply(null, uint8Array);
// Use the builtin base64 encoder.
return window.btoa(latin1String);
}
@@ -313,10 +313,10 @@ function encodeBase64(uint8Array) {
*/
function decodeBase64(base64) {
// window.atob returns a unicode string with codepoints in the range 0-255.
var latin1String = window.atob(base64);
const latin1String = window.atob(base64);
// Encode the string as a Uint8Array
var uint8Array = new Uint8Array(latin1String.length);
for (var i = 0; i < latin1String.length; i++) {
const uint8Array = new Uint8Array(latin1String.length);
for (let i = 0; i < latin1String.length; i++) {
uint8Array[i] = latin1String.charCodeAt(i);
}
return uint8Array;