Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix-focus-steal

This commit is contained in:
Michael Telatynski 2017-05-27 14:29:06 +01:00
commit 8e1db84bee
80 changed files with 5133 additions and 725 deletions

View file

@ -24,6 +24,10 @@ In the interim, `vector-im/riot-web` and `matrix-org/matrix-react-sdk` should
be considered as a single project (for instance, matrix-react-sdk bugs be considered as a single project (for instance, matrix-react-sdk bugs
are currently filed against vector-im/riot-web rather than this project). are currently filed against vector-im/riot-web rather than this project).
Translation Status
==================
[![translationsstatus](https://translate.nordgedanken.de/widgets/riot-web/-/multi-auto.svg)](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget)
Developer Guide Developer Guide
=============== ===============
@ -190,4 +194,3 @@ Alternative instructions:
* Create an index.html file pulling in your compiled javascript and the * Create an index.html file pulling in your compiled javascript and the
CSS bundle from the skin you use. For now, you'll also need to manually CSS bundle from the skin you use. For now, you'll also need to manually
import CSS from any skins that your skin inherts from. import CSS from any skins that your skin inherts from.

View file

@ -55,11 +55,18 @@ module.exports = function (config) {
// some images to reduce noise from the tests // some images to reduce noise from the tests
{pattern: 'test/img/*', watched: false, included: false, {pattern: 'test/img/*', watched: false, included: false,
served: true, nocache: false}, served: true, nocache: false},
// translation files
{pattern: 'src/i18n/strings/*', watcheed: false, included: false, served: true},
{pattern: 'test/i18n/*', watched: false, included: false, served: true},
], ],
// redirect img links to the karma server
proxies: { proxies: {
// redirect img links to the karma server
"/img/": "/base/test/img/", "/img/": "/base/test/img/",
// special languages.json file for the tests
"/i18n/languages.json": "/base/test/i18n/languages.json",
// and redirect i18n requests
"/i18n/": "/base/src/i18n/strings/",
}, },
// list of files to exclude // list of files to exclude
@ -166,7 +173,6 @@ module.exports = function (config) {
'sinon': 'sinon/pkg/sinon.js', 'sinon': 'sinon/pkg/sinon.js',
}, },
root: [ root: [
path.resolve('./src'),
path.resolve('./test'), path.resolve('./test'),
], ],
}, },

View file

@ -50,6 +50,7 @@
"browser-request": "^0.3.3", "browser-request": "^0.3.3",
"classnames": "^2.1.2", "classnames": "^2.1.2",
"commonmark": "^0.27.0", "commonmark": "^0.27.0",
"counterpart": "^0.18.0",
"draft-js": "^0.8.1", "draft-js": "^0.8.1",
"draft-js-export-html": "^0.5.0", "draft-js-export-html": "^0.5.0",
"draft-js-export-markdown": "^0.2.0", "draft-js-export-markdown": "^0.2.0",
@ -63,7 +64,7 @@
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3", "linkifyjs": "^2.1.3",
"lodash": "^4.13.1", "lodash": "^4.13.1",
"matrix-js-sdk": "0.7.8", "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"optimist": "^0.6.1", "optimist": "^0.6.1",
"q": "^1.4.1", "q": "^1.4.1",
"react": "^15.4.0", "react": "^15.4.0",

View file

@ -16,6 +16,7 @@ limitations under the License.
*/ */
var MatrixClientPeg = require("./MatrixClientPeg"); var MatrixClientPeg = require("./MatrixClientPeg");
import { _t } from './languageHandler';
/** /**
* Allows a user to add a third party identifier to their Home Server and, * Allows a user to add a third party identifier to their Home Server and,
@ -44,7 +45,7 @@ class AddThreepid {
return res; return res;
}, function(err) { }, function(err) {
if (err.errcode == 'M_THREEPID_IN_USE') { 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) { } else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`; err.message = err.message + ` (Status ${err.httpStatus})`;
} }
@ -69,7 +70,7 @@ class AddThreepid {
return res; return res;
}, function(err) { }, function(err) {
if (err.errcode == 'M_THREEPID_IN_USE') { 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) { } else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`; err.message = err.message + ` (Status ${err.httpStatus})`;
} }
@ -91,7 +92,7 @@ class AddThreepid {
id_server: identityServerDomain id_server: identityServerDomain
}, this.bind).catch(function(err) { }, this.bind).catch(function(err) {
if (err.httpStatus === 401) { 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) { else if (err.httpStatus) {
err.message += ` (Status ${err.httpStatus})`; err.message += ` (Status ${err.httpStatus})`;

View file

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

View file

@ -21,6 +21,7 @@ var extend = require('./extend');
var dis = require('./dispatcher'); var dis = require('./dispatcher');
var MatrixClientPeg = require('./MatrixClientPeg'); var MatrixClientPeg = require('./MatrixClientPeg');
var sdk = require('./index'); var sdk = require('./index');
import { _t } from './languageHandler';
var Modal = require('./Modal'); var Modal = require('./Modal');
var encrypt = require("browser-encrypt-attachment"); var encrypt = require("browser-encrypt-attachment");
@ -347,14 +348,14 @@ class ContentMessages {
}, function(err) { }, function(err) {
error = err; error = err;
if (!upload.canceled) { 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) { 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"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Upload Failed", title: _t('Upload Failed'),
description: desc description: desc,
}); });
} }
}).finally(() => { }).finally(() => {

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -15,40 +16,89 @@ limitations under the License.
*/ */
'use strict'; 'use strict';
import { _t } from './languageHandler';
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; function getDaysArray() {
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 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) { function pad(n) {
return (n < 10 ? '0' : '') + 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 = { module.exports = {
formatDate: function(date) { formatDate: function(date) {
// date.toLocaleTimeString is completely system dependent.
// just go 24h for now
var now = new Date(); var now = new Date();
const days = getDaysArray();
const months = getMonthsArray();
if (date.toDateString() === now.toDateString()) { 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) { 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()) { else if (now.getFullYear() === date.getFullYear()) {
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); // 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 this.formatFullDate(date); return this.formatFullDate(date);
}
}, },
formatFullDate: function(date) { formatFullDate: function(date) {
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); 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: this.formatTime(date),
});
}, },
formatTime: function(date) { formatTime: function(date, showTwelveHour=false) {
return pad(date.getHours()) + ':' + pad(date.getMinutes()); if (showTwelveHour) {
return twelveHourTime(date);
} }
return pad(date.getHours()) + ':' + pad(date.getMinutes());
},
}; };

View file

@ -27,6 +27,7 @@ import DMRoomMap from './utils/DMRoomMap';
import RtsClient from './RtsClient'; import RtsClient from './RtsClient';
import Modal from './Modal'; import Modal from './Modal';
import sdk from './index'; import sdk from './index';
import { _t } from './languageHandler';
/** /**
* Called at startup, to attempt to build a logged-in Matrix session. It tries * Called at startup, to attempt to build a logged-in Matrix session. It tries
@ -229,14 +230,16 @@ function _handleRestoreFailure(e) {
let msg = e.message; let msg = e.message;
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") { if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
msg = "You need to log back in to generate end-to-end encryption keys " msg = _t(
+ "for this device and submit the public key to your homeserver. " 'You need to log back in to generate end-to-end ' +
+ "This is a once off; sorry for the inconvenience."; 'encryption keys for this device and submit the public key to your homeserver. ' +
'This is a once off; sorry for the inconvenience'
) + '.';
_clearLocalStorage(); _clearLocalStorage();
return q.reject(new Error( return q.reject(new Error(
"Unable to restore previous session: " + msg, _t('Unable to restore previous session') + ': ' + msg,
)); ));
} }

View file

@ -21,6 +21,7 @@ import TextForEvent from './TextForEvent';
import Avatar from './Avatar'; import Avatar from './Avatar';
import dis from './dispatcher'; import dis from './dispatcher';
import sdk from './index'; import sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal'; import Modal from './Modal';
/* /*
@ -134,13 +135,11 @@ const Notifier = {
if (result !== 'granted') { if (result !== 'granted') {
// The permission request was dismissed or denied // The permission request was dismissed or denied
const description = result === 'denied' const description = result === 'denied'
? 'Riot does not have permission to send you notifications' ? _t('Riot does not have permission to send you notifications - please check your browser settings')
+ ' - please check your browser settings' : _t('Riot was not given permission to send notifications - please try again');
: 'Riot was not given permission to send notifications'
+ ' - please try again';
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: 'Unable to enable Notifications', title: _t('Unable to enable Notifications'),
description, description,
}); });
return; return;

View file

@ -15,6 +15,7 @@ limitations under the License.
*/ */
var Matrix = require("matrix-js-sdk"); var Matrix = require("matrix-js-sdk");
import { _t } from './languageHandler';
/** /**
* Allows a user to reset their password on a homeserver. * Allows a user to reset their password on a homeserver.
@ -53,7 +54,7 @@ class PasswordReset {
return res; return res;
}, function(err) { }, function(err) {
if (err.errcode == 'M_THREEPID_NOT_FOUND') { 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) { } else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`; err.message = err.message + ` (Status ${err.httpStatus})`;
} }
@ -78,10 +79,10 @@ class PasswordReset {
} }
}, this.password).catch(function(err) { }, this.password).catch(function(err) {
if (err.httpStatus === 401) { 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) { 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) { else if (err.httpStatus) {
err.message += ` (Status ${err.httpStatus})`; err.message += ` (Status ${err.httpStatus})`;

View file

@ -13,14 +13,19 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
export const LEVEL_ROLE_MAP = { import { _t } from './languageHandler';
undefined: 'Default',
0: 'User', export function levelRoleMap() {
50: 'Moderator', return {
100: 'Admin', undefined: _t('Default'),
}; 0: _t('User'),
50: _t('Moderator'),
100: _t('Admin'),
};
}
export function textualPowerLevel(level, userDefault) { export function textualPowerLevel(level, userDefault) {
const LEVEL_ROLE_MAP = this.levelRoleMap();
if (LEVEL_ROLE_MAP[level]) { if (LEVEL_ROLE_MAP[level]) {
return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`); return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`);
} else { } else {

View file

@ -125,6 +125,7 @@ const SdkConfig = require('./SdkConfig');
const MatrixClientPeg = require("./MatrixClientPeg"); const MatrixClientPeg = require("./MatrixClientPeg");
const MatrixEvent = require("matrix-js-sdk").MatrixEvent; const MatrixEvent = require("matrix-js-sdk").MatrixEvent;
const dis = require("./dispatcher"); const dis = require("./dispatcher");
import { _t } from './languageHandler';
function sendResponse(event, res) { function sendResponse(event, res) {
const data = JSON.parse(JSON.stringify(event.data)); const data = JSON.parse(JSON.stringify(event.data));
@ -150,7 +151,7 @@ function inviteUser(event, roomId, userId) {
console.log(`Received request to invite ${userId} into room ${roomId}`); console.log(`Received request to invite ${userId} into room ${roomId}`);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, "You need to be logged in."); sendError(event, _t('You need to be logged in.'));
return; return;
} }
const room = client.getRoom(roomId); const room = client.getRoom(roomId);
@ -170,7 +171,7 @@ function inviteUser(event, roomId, userId) {
success: true, success: true,
}); });
}, function(err) { }, 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 +182,7 @@ function setPlumbingState(event, roomId, status) {
console.log(`Received request to set plumbing state to status "${status}" in room ${roomId}`); console.log(`Received request to set plumbing state to status "${status}" in room ${roomId}`);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, "You need to be logged in."); sendError(event, _t('You need to be logged in.'));
return; return;
} }
client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => { client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => {
@ -189,7 +190,7 @@ function setPlumbingState(event, roomId, status) {
success: true, success: true,
}); });
}, (err) => { }, (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 +198,7 @@ function setBotOptions(event, roomId, userId) {
console.log(`Received request to set options for bot ${userId} in room ${roomId}`); console.log(`Received request to set options for bot ${userId} in room ${roomId}`);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, "You need to be logged in."); sendError(event, _t('You need to be logged in.'));
return; return;
} }
client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => { client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => {
@ -205,20 +206,20 @@ function setBotOptions(event, roomId, userId) {
success: true, success: true,
}); });
}, (err) => { }, (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) { function setBotPower(event, roomId, userId, level) {
if (!(Number.isInteger(level) && level >= 0)) { if (!(Number.isInteger(level) && level >= 0)) {
sendError(event, "Power level must be positive integer."); sendError(event, _t('Power level must be positive integer.'));
return; return;
} }
console.log(`Received request to set power level to ${level} for bot ${userId} in room ${roomId}.`); console.log(`Received request to set power level to ${level} for bot ${userId} in room ${roomId}.`);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, "You need to be logged in."); sendError(event, _t('You need to be logged in.'));
return; return;
} }
@ -235,7 +236,7 @@ function setBotPower(event, roomId, userId, level) {
success: true, success: true,
}); });
}, (err) => { }, (err) => {
sendError(event, err.message ? err.message : "Failed to send request.", err); sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
}); });
}); });
} }
@ -258,12 +259,12 @@ function botOptions(event, roomId, userId) {
function returnStateEvent(event, roomId, eventType, stateKey) { function returnStateEvent(event, roomId, eventType, stateKey) {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, "You need to be logged in."); sendError(event, _t('You need to be logged in.'));
return; return;
} }
const room = client.getRoom(roomId); const room = client.getRoom(roomId);
if (!room) { if (!room) {
sendError(event, "This room is not recognised."); sendError(event, _t('This room is not recognised.'));
return; return;
} }
const stateEvent = room.currentState.getStateEvents(eventType, stateKey); const stateEvent = room.currentState.getStateEvents(eventType, stateKey);
@ -313,13 +314,13 @@ const onMessage = function(event) {
const roomId = event.data.room_id; const roomId = event.data.room_id;
const userId = event.data.user_id; const userId = event.data.user_id;
if (!roomId) { if (!roomId) {
sendError(event, "Missing room_id in request"); sendError(event, _t('Missing room_id in request'));
return; return;
} }
let promise = Promise.resolve(currentRoomId); let promise = Promise.resolve(currentRoomId);
if (!currentRoomId) { if (!currentRoomId) {
if (!currentRoomAlias) { if (!currentRoomAlias) {
sendError(event, "Must be viewing a room"); sendError(event, _t('Must be viewing a room'));
return; return;
} }
// no room ID but there is an alias, look it up. // no room ID but there is an alias, look it up.
@ -331,7 +332,7 @@ const onMessage = function(event) {
promise.then((viewingRoomId) => { promise.then((viewingRoomId) => {
if (roomId !== viewingRoomId) { if (roomId !== viewingRoomId) {
sendError(event, "Room " + roomId + " not visible"); sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId}));
return; return;
} }
@ -345,7 +346,7 @@ const onMessage = function(event) {
} }
if (!userId) { if (!userId) {
sendError(event, "Missing user_id in request"); sendError(event, _t('Missing user_id in request'));
return; return;
} }
switch (event.data.action) { switch (event.data.action) {
@ -370,7 +371,7 @@ const onMessage = function(event) {
} }
}, (err) => { }, (err) => {
console.error(err); console.error(err);
sendError(event, "Failed to lookup current room."); sendError(event, _t('Failed to lookup current room') + '.');
}); });
}; };

View file

@ -18,6 +18,7 @@ import MatrixClientPeg from "./MatrixClientPeg";
import dis from "./dispatcher"; import dis from "./dispatcher";
import Tinter from "./Tinter"; import Tinter from "./Tinter";
import sdk from './index'; import sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal'; import Modal from './Modal';
@ -41,7 +42,7 @@ class Command {
} }
getUsage() { getUsage() {
return "Usage: " + this.getCommandWithArgs(); return _t('Usage') + ': ' + this.getCommandWithArgs();
} }
} }
@ -68,8 +69,8 @@ const commands = {
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
// TODO Don't explain this away, actually show a search UI here. // TODO Don't explain this away, actually show a search UI here.
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "/ddg is not a command", title: _t('/ddg is not a command'),
description: "To use it, just wait for autocomplete results to load and tab through them.", description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
}); });
return success(); return success();
}), }),

View file

@ -16,7 +16,7 @@ limitations under the License.
var MatrixClientPeg = require("./MatrixClientPeg"); var MatrixClientPeg = require("./MatrixClientPeg");
var CallHandler = require("./CallHandler"); var CallHandler = require("./CallHandler");
import { _t } from './languageHandler';
import * as Roles from './Roles'; import * as Roles from './Roles';
function textForMemberEvent(ev) { function textForMemberEvent(ev) {
@ -25,45 +25,45 @@ function textForMemberEvent(ev) {
var targetName = ev.target ? ev.target.name : ev.getStateKey(); var targetName = ev.target ? ev.target.name : ev.getStateKey();
var ConferenceHandler = CallHandler.getConferenceHandler(); var ConferenceHandler = CallHandler.getConferenceHandler();
var reason = ev.getContent().reason ? ( var reason = ev.getContent().reason ? (
" Reason: " + ev.getContent().reason _t('Reason') + ': ' + ev.getContent().reason
) : ""; ) : "";
switch (ev.getContent().membership) { switch (ev.getContent().membership) {
case 'invite': case 'invite':
var threePidContent = ev.getContent().third_party_invite; var threePidContent = ev.getContent().third_party_invite;
if (threePidContent) { if (threePidContent) {
if (threePidContent.display_name) { if (threePidContent.display_name) {
return targetName + " accepted the invitation for " + return _t('%(targetName)s accepted the invitation for %(displayName)s.', {targetName: targetName, displayName: threePidContent.display_name});
threePidContent.display_name + ".";
} else { } else {
return targetName + " accepted an invitation."; return _t('%(targetName)s accepted an invitation.', {targetName: targetName});
} }
} }
else { else {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return senderName + " requested a VoIP conference"; return _t('%(senderName)s requested a VoIP conference', {senderName: senderName});
} }
else { else {
return senderName + " invited " + targetName + "."; return _t('%(senderName)s invited %(targetName)s.', {senderName: senderName, targetName: targetName});
} }
} }
case 'ban': case 'ban':
return senderName + " banned " + targetName + "." + reason; return _t(
'%(senderName)s banned %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
case 'join': case 'join':
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') { if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) { if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
return ev.getSender() + " changed their display name from " + 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});
ev.getPrevContent().displayname + " to " +
ev.getContent().displayname;
} else if (!ev.getPrevContent().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) { } 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) { } 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) { } 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) { } 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 { } else {
// suppress null rejoins // suppress null rejoins
return ''; return '';
@ -71,49 +71,54 @@ function textForMemberEvent(ev) {
} else { } else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return "VoIP conference started"; return _t('VoIP conference started');
} }
else { else {
return targetName + " joined the room."; return _t('%(targetName)s joined the room.', {targetName: targetName});
} }
} }
case 'leave': case 'leave':
if (ev.getSender() === ev.getStateKey()) { if (ev.getSender() === ev.getStateKey()) {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return "VoIP conference finished"; return _t('VoIP conference finished');
} }
else if (ev.getPrevContent().membership === "invite") { else if (ev.getPrevContent().membership === "invite") {
return targetName + " rejected the invitation."; return _t('%(targetName)s rejected the invitation.', {targetName: targetName});
} }
else { else {
return targetName + " left the room."; return _t('%(targetName)s left the room.', {targetName: targetName});
} }
} }
else if (ev.getPrevContent().membership === "ban") { 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") { 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") { else if (ev.getPrevContent().membership === "invite") {
return senderName + " withdrew " + targetName + "'s invitation." + reason; return _t(
'%(senderName)s withdrew %(targetName)s\'s inivitation.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
} }
else { else {
return targetName + " left the room."; return _t('%(targetName)s left the room.', {targetName: targetName});
} }
} }
} }
function textForTopicEvent(ev) { function textForTopicEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return _t('%(senderDisplayName)s changed the topic to "%(topic)s"', {senderDisplayName: senderDisplayName, topic: ev.getContent().topic});
return senderDisplayName + ' changed the topic to "' + ev.getContent().topic + '"';
} }
function textForRoomNameEvent(ev) { function textForRoomNameEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return senderDisplayName + ' changed the room name to "' + ev.getContent().name + '"'; return _t('%(senderDisplayName)s changed the room name to %(roomName)s', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name});
} }
function textForMessageEvent(ev) { function textForMessageEvent(ev) {
@ -122,66 +127,66 @@ function textForMessageEvent(ev) {
if (ev.getContent().msgtype === "m.emote") { if (ev.getContent().msgtype === "m.emote") {
message = "* " + senderDisplayName + " " + message; message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === "m.image") { } else if (ev.getContent().msgtype === "m.image") {
message = senderDisplayName + " sent an image."; message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName: senderDisplayName});
} }
return message; return message;
} }
function textForCallAnswerEvent(event) { function textForCallAnswerEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone"; var senderName = event.sender ? event.sender.name : _t('Someone');
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)"; var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return senderName + " answered the call." + supported; return _t('%(senderName)s answered the call', {senderName: senderName}) + ' ' + supported;
} }
function textForCallHangupEvent(event) { function textForCallHangupEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone"; var senderName = event.sender ? event.sender.name : _t('Someone');
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)"; var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return senderName + " ended the call." + supported; return _t('%(senderName)s ended the call', {senderName: senderName}) + ' ' + supported;
} }
function textForCallInviteEvent(event) { 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? // FIXME: Find a better way to determine this from the event?
var type = "voice"; var type = "voice";
if (event.getContent().offer && event.getContent().offer.sdp && if (event.getContent().offer && event.getContent().offer.sdp &&
event.getContent().offer.sdp.indexOf('m=video') !== -1) { event.getContent().offer.sdp.indexOf('m=video') !== -1) {
type = "video"; type = "video";
} }
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)"; var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return senderName + " placed a " + type + " call." + supported; return _t('%(senderName)s placed a %(callType)s call.', {senderName: senderName, callType: type}) + ' ' + supported;
} }
function textForThreePidInviteEvent(event) { function textForThreePidInviteEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); var senderName = event.sender ? event.sender.name : event.getSender();
return senderName + " sent an invitation to " + event.getContent().display_name + return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {senderName: senderName, targetDisplayName: event.getContent().display_name});
" to join the room.";
} }
function textForHistoryVisibilityEvent(event) { function textForHistoryVisibilityEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); var senderName = event.sender ? event.sender.name : event.getSender();
var vis = event.getContent().history_visibility; 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") { 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") { 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") { else if (vis === "shared") {
text += "all room members."; text += _t('all room members') + '.';
} }
else if (vis === "world_readable") { else if (vis === "world_readable") {
text += "anyone."; text += _t('anyone') + '.';
} }
else { else {
text += " unknown (" + vis + ")"; text += ' ' + _t('unknown') + ' (' + vis + ')';
} }
return text; return text;
} }
function textForEncryptionEvent(event) { function textForEncryptionEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); 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 // Currently will only display a change if a user's power level is changed
@ -204,6 +209,7 @@ function textForPowerEvent(event) {
} }
); );
let diff = []; let diff = [];
// XXX: This is also surely broken for i18n
users.forEach((userId) => { users.forEach((userId) => {
// Previous power level // Previous power level
const from = event.getPrevContent().users[userId]; const from = event.getPrevContent().users[userId];
@ -211,16 +217,14 @@ function textForPowerEvent(event) {
const to = event.getContent().users[userId]; const to = event.getContent().users[userId];
if (to !== from) { if (to !== from) {
diff.push( diff.push(
userId + _t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {userId: userId, fromPowerLevel: Roles.textualPowerLevel(from, userDefault), toPowerLevel: Roles.textualPowerLevel(to, userDefault)})
' from ' + Roles.textualPowerLevel(from, userDefault) +
' to ' + Roles.textualPowerLevel(to, userDefault)
); );
} }
}); });
if (!diff.length) { if (!diff.length) {
return ''; return '';
} }
return senderName + ' changed the power level of ' + diff.join(', '); return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s', {senderName: senderName, powerLevelDiffText: diff.join(", ")});
} }
var handlers = { var handlers = {

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
'use strict';
import q from 'q'; import q from 'q';
import MatrixClientPeg from './MatrixClientPeg'; import MatrixClientPeg from './MatrixClientPeg';
import Notifier from './Notifier'; import Notifier from './Notifier';
@ -23,10 +22,14 @@ import Notifier from './Notifier';
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage. * TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
*/ */
module.exports = { /*
* TODO: Find a way to translate the names of LABS_FEATURES. In other words, guarantee that languages were already loaded before building this array.
*/
export default {
LABS_FEATURES: [ LABS_FEATURES: [
{ {
name: 'New Composer & Autocomplete', name: "New Composer & Autocomplete",
id: 'rich_text_editor', id: 'rich_text_editor',
default: false, default: false,
}, },

View file

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

View file

@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
import {TextualCompletion} from './Components'; 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 = [ const COMMANDS = [
{ {
command: '/me', command: '/me',
@ -68,7 +70,7 @@ export default class CommandProvider extends AutocompleteProvider {
component: (<TextualCompletion component: (<TextualCompletion
title={result.command} title={result.command}
subtitle={result.args} subtitle={result.args}
description={result.description} description={ t_(result.description) }
/>), />),
range, range,
}; };
@ -78,7 +80,7 @@ export default class CommandProvider extends AutocompleteProvider {
} }
getName() { getName() {
return '*️⃣ Commands'; return '*️⃣ ' + _t('Commands');
} }
static getInstance(): CommandProvider { static getInstance(): CommandProvider {

View file

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import {emojioneList, shortnameToImage, shortnameToUnicode} from 'emojione'; import {emojioneList, shortnameToImage, shortnameToUnicode} from 'emojione';
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
@ -39,7 +40,7 @@ export default class EmojiProvider extends AutocompleteProvider {
} }
getName() { getName() {
return '😃 Emoji'; return '😃 ' + _t('Emoji');
} }
static getInstance() { static getInstance() {

View file

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import MatrixClientPeg from '../MatrixClientPeg'; import MatrixClientPeg from '../MatrixClientPeg';
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
@ -50,7 +51,7 @@ export default class RoomProvider extends AutocompleteProvider {
} }
getName() { getName() {
return '💬 Rooms'; return '💬 ' + _t('Rooms');
} }
static getInstance() { static getInstance() {

View file

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import Q from 'q'; import Q from 'q';
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
@ -51,7 +52,7 @@ export default class UserProvider extends AutocompleteProvider {
} }
getName() { getName() {
return '👥 Users'; return '👥 ' + _t('Users');
} }
setUserList(users) { setUserList(users) {

View file

@ -16,15 +16,16 @@ limitations under the License.
'use strict'; 'use strict';
var React = require("react"); import React from 'react';
var MatrixClientPeg = require("../../MatrixClientPeg"); import q from 'q';
var PresetValues = { import { _t } from '../../languageHandler';
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
const PresetValues = {
PrivateChat: "private_chat", PrivateChat: "private_chat",
PublicChat: "public_chat", PublicChat: "public_chat",
Custom: "custom", Custom: "custom",
}; };
var q = require('q');
var sdk = require('../../index');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'CreateRoom', displayName: 'CreateRoom',
@ -231,7 +232,7 @@ module.exports = React.createClass({
if (curr_phase == this.phases.ERROR) { if (curr_phase == this.phases.ERROR) {
error_box = ( error_box = (
<div className="mx_Error"> <div className="mx_Error">
An error occured: {this.state.error_string} {_t('An error occured: %(error_string)s', {error_string: this.state.error_string})}
</div> </div>
); );
} }
@ -248,27 +249,27 @@ module.exports = React.createClass({
<div className="mx_CreateRoom"> <div className="mx_CreateRoom">
<SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/> <SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/>
<div className="mx_CreateRoom_body"> <div className="mx_CreateRoom_body">
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder="Name"/> <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="Topic"/> <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 /> <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 /> <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 /> <Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
<div> <div>
<label> <label>
<input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/> <input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/>
Make this room private {_t('Make this room private')}
</label> </label>
</div> </div>
<div> <div>
<label> <label>
<input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/> <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> </label>
</div> </div>
<div className="mx_CreateRoom_encrypt"> <div className="mx_CreateRoom_encrypt">
<label> <label>
<input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/> <input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/>
Encrypt room {_t('Encrypt room')}
</label> </label>
</div> </div>
<div> <div>

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
var React = require('react'); import React from 'react';
var ReactDOM = require("react-dom"); import ReactDOM from 'react-dom';
var Matrix = require("matrix-js-sdk"); import Matrix from 'matrix-js-sdk';
var sdk = require('../../index'); import sdk from '../../index';
var MatrixClientPeg = require("../../MatrixClientPeg"); import MatrixClientPeg from '../../MatrixClientPeg';
var dis = require("../../dispatcher"); import dis from '../../dispatcher';
/* /*
* Component which shows the filtered file using a TimelinePanel * Component which shows the filtered file using a TimelinePanel
@ -116,7 +116,7 @@ var FilePanel = React.createClass({
showUrlPreview = { false } showUrlPreview = { false }
tileShape="file_grid" tileShape="file_grid"
opacity={ this.props.opacity } 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

@ -36,6 +36,7 @@ import PageTypes from '../../PageTypes';
import createRoom from "../../createRoom"; import createRoom from "../../createRoom";
import * as UDEHandler from '../../UnknownDeviceErrorHandler'; import * as UDEHandler from '../../UnknownDeviceErrorHandler';
import { _t } from '../../languageHandler';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MatrixChat', displayName: 'MatrixChat',
@ -375,8 +376,8 @@ module.exports = React.createClass({
break; break;
case 'reject_invite': case 'reject_invite':
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Reject invitation", title: _t('Reject invitation'),
description: "Are you sure you want to reject the invitation?", description: _t('Are you sure you want to reject the invitation?'),
onFinished: (confirm) => { onFinished: (confirm) => {
if (confirm) { if (confirm) {
// FIXME: controller shouldn't be loading a view :( // FIXME: controller shouldn't be loading a view :(
@ -391,7 +392,7 @@ module.exports = React.createClass({
}, (err) => { }, (err) => {
modal.close(); modal.close();
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to reject invitation", title: _t('Failed to reject invitation'),
description: err.toString(), description: err.toString(),
}); });
}); });
@ -437,11 +438,11 @@ module.exports = React.createClass({
//this._setPage(PageTypes.CreateRoom); //this._setPage(PageTypes.CreateRoom);
//this.notifyNewScreen('new'); //this.notifyNewScreen('new');
Modal.createDialog(TextInputDialog, { Modal.createDialog(TextInputDialog, {
title: "Create Room", title: _t('Create Room'),
description: "Room name (optional)", description: _t('Room name (optional)'),
button: "Create Room", button: _t('Create Room'),
onFinished: (shouldCreate, name) => { onFinished: (should_create, name) => {
if (shouldCreate) { if (should_create) {
const createOpts = {}; const createOpts = {};
if (name) createOpts.name = name; if (name) createOpts.name = name;
createRoom({createOpts}).done(); createRoom({createOpts}).done();
@ -653,16 +654,20 @@ module.exports = React.createClass({
_createChat: function() { _createChat: function() {
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog"); const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createDialog(ChatInviteDialog, { Modal.createDialog(ChatInviteDialog, {
title: "Start a new chat", title: _t('Start a chat'),
description: _t("Who would you like to communicate with?"),
placeholder: _t("Email, name or matrix ID"),
button: _t("Start Chat")
}); });
}, },
_invite: function(roomId) { _invite: function(roomId) {
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog"); const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createDialog(ChatInviteDialog, { Modal.createDialog(ChatInviteDialog, {
title: "Invite new room members", title: _t('Invite new room members'),
button: "Send Invites", description: _t('Who would you like to add to this room?'),
description: "Who would you like to add to this room?", button: _t('Send Invites'),
placeholder: _t("Email, name or matrix ID"),
roomId: roomId, roomId: roomId,
}); });
}, },
@ -886,8 +891,8 @@ module.exports = React.createClass({
cli.on('Session.logged_out', function(call) { cli.on('Session.logged_out', function(call) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Signed Out", title: _t('Signed Out'),
description: "For security, this session has been signed out. Please sign in again.", description: _t('For security, this session has been signed out. Please sign in again.'),
}); });
dis.dispatch({ dis.dispatch({
action: 'logout', action: 'logout',
@ -1201,7 +1206,7 @@ module.exports = React.createClass({
<div className="mx_MatrixChat_splash"> <div className="mx_MatrixChat_splash">
<Spinner /> <Spinner />
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }> <a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
Logout { _t('Logout') }
</a> </a>
</div> </div>
); );

View file

@ -84,6 +84,12 @@ module.exports = React.createClass({
// shape parameter to be passed to EventTiles // shape parameter to be passed to EventTiles
tileShape: React.PropTypes.string, tileShape: React.PropTypes.string,
// show twelve hour timestamps
isTwelveHour: React.PropTypes.bool,
// show timestamps always
alwaysShowTimestamps: React.PropTypes.bool,
}, },
componentWillMount: function() { componentWillMount: function() {
@ -230,8 +236,8 @@ module.exports = React.createClass({
}, },
_getEventTiles: function() { _getEventTiles: function() {
var EventTile = sdk.getComponent('rooms.EventTile'); const EventTile = sdk.getComponent('rooms.EventTile');
var DateSeparator = sdk.getComponent('messages.DateSeparator'); const DateSeparator = sdk.getComponent('messages.DateSeparator');
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary'); const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
this.eventNodes = {}; this.eventNodes = {};
@ -413,8 +419,8 @@ module.exports = React.createClass({
}, },
_getTilesForEvent: function(prevEvent, mxEv, last) { _getTilesForEvent: function(prevEvent, mxEv, last) {
var EventTile = sdk.getComponent('rooms.EventTile'); const EventTile = sdk.getComponent('rooms.EventTile');
var DateSeparator = sdk.getComponent('messages.DateSeparator'); const DateSeparator = sdk.getComponent('messages.DateSeparator');
var ret = []; var ret = [];
// is this a continuation of the previous message? // is this a continuation of the previous message?
@ -468,7 +474,6 @@ module.exports = React.createClass({
if (this.props.manageReadReceipts) { if (this.props.manageReadReceipts) {
readReceipts = this._getReadReceiptsForEvent(mxEv); readReceipts = this._getReadReceiptsForEvent(mxEv);
} }
ret.push( ret.push(
<li key={eventId} <li key={eventId}
ref={this._collectEventNode.bind(this, eventId)} ref={this._collectEventNode.bind(this, eventId)}
@ -482,6 +487,7 @@ module.exports = React.createClass({
checkUnmounting={this._isUnmounting} checkUnmounting={this._isUnmounting}
eventSendStatus={mxEv.status} eventSendStatus={mxEv.status}
tileShape={this.props.tileShape} tileShape={this.props.tileShape}
isTwelveHour={this.props.isTwelveHour}
last={last} isSelectedEvent={highlight}/> last={last} isSelectedEvent={highlight}/>
</li> </li>
); );
@ -615,8 +621,13 @@ module.exports = React.createClass({
var style = this.props.hidden ? { display: 'none' } : {}; var style = this.props.hidden ? { display: 'none' } : {};
style.opacity = this.props.opacity; style.opacity = this.props.opacity;
var className = this.props.className + " mx_fadable";
if (this.props.alwaysShowTimestamps) {
className += " mx_MessagePanel_alwaysShowTimestamps";
}
return ( return (
<ScrollPanel ref="scrollPanel" className={ this.props.className + " mx_fadable" } <ScrollPanel ref="scrollPanel" className={ className }
onScroll={ this.props.onScroll } onScroll={ this.props.onScroll }
onResize={ this.onResize } onResize={ this.onResize }
onFillRequest={ this.props.onFillRequest } onFillRequest={ this.props.onFillRequest }

View file

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

View file

@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
var React = require('react'); import React from 'react';
var sdk = require('../../index'); import { _t } from '../../languageHandler';
var dis = require("../../dispatcher"); import sdk from '../../index';
var WhoIsTyping = require("../../WhoIsTyping"); import dis from '../../dispatcher';
var MatrixClientPeg = require("../../MatrixClientPeg"); import WhoIsTyping from '../../WhoIsTyping';
const MemberAvatar = require("../views/avatars/MemberAvatar"); import MatrixClientPeg from '../../MatrixClientPeg';
import MemberAvatar from '../views/avatars/MemberAvatar';
const HIDE_DEBOUNCE_MS = 10000; const HIDE_DEBOUNCE_MS = 10000;
const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_HIDDEN = 0;
@ -175,8 +176,8 @@ module.exports = React.createClass({
<div className="mx_RoomStatusBar_scrollDownIndicator" <div className="mx_RoomStatusBar_scrollDownIndicator"
onClick={ this.props.onScrollToBottomClick }> onClick={ this.props.onScrollToBottomClick }>
<img src="img/scrolldown.svg" width="24" height="24" <img src="img/scrolldown.svg" width="24" height="24"
alt="Scroll to bottom of page" alt={ _t("Scroll to bottom of page") }
title="Scroll to bottom of page"/> title={ _t("Scroll to bottom of page") }/>
</div> </div>
); );
} }
@ -250,10 +251,10 @@ module.exports = React.createClass({
<div className="mx_RoomStatusBar_connectionLostBar"> <div className="mx_RoomStatusBar_connectionLostBar">
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ "/> <img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ "/>
<div className="mx_RoomStatusBar_connectionLostBar_title"> <div className="mx_RoomStatusBar_connectionLostBar_title">
Connectivity to the server has been lost. {_t('Connectivity to the server has been lost.')}
</div> </div>
<div className="mx_RoomStatusBar_connectionLostBar_desc"> <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>
</div> </div>
); );
@ -266,7 +267,7 @@ module.exports = React.createClass({
<TabCompleteBar tabComplete={this.props.tabComplete} /> <TabCompleteBar tabComplete={this.props.tabComplete} />
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|"> <div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
<TintableSvg src="img/eol.svg" width="22" height="16"/> <TintableSvg src="img/eol.svg" width="22" height="16"/>
Auto-complete {_t('Auto-complete')}
</div> </div>
</div> </div>
</div> </div>
@ -283,13 +284,12 @@ module.exports = React.createClass({
<div className="mx_RoomStatusBar_connectionLostBar_desc"> <div className="mx_RoomStatusBar_connectionLostBar_desc">
<a className="mx_RoomStatusBar_resend_link" <a className="mx_RoomStatusBar_resend_link"
onClick={ this.props.onResendAllClick }> onClick={ this.props.onResendAllClick }>
Resend all {_t('Resend all')}
</a> or <a </a> {_t('or')} <a
className="mx_RoomStatusBar_resend_link" className="mx_RoomStatusBar_resend_link"
onClick={ this.props.onCancelAllClick }> onClick={ this.props.onCancelAllClick }>
cancel all {_t('cancel all')}
</a> now. You can also select individual messages to </a> {_t('now. You can also select individual messages to resend or cancel.')}
resend or cancel.
</div> </div>
</div> </div>
); );
@ -324,7 +324,7 @@ module.exports = React.createClass({
if (this.props.hasActiveCall) { if (this.props.hasActiveCall) {
return ( return (
<div className="mx_RoomStatusBar_callBar"> <div className="mx_RoomStatusBar_callBar">
<b>Active call</b> <b>{_t('Active call')}</b>
</div> </div>
); );
} }

View file

@ -25,6 +25,7 @@ var ReactDOM = require("react-dom");
var q = require("q"); var q = require("q");
var classNames = require("classnames"); var classNames = require("classnames");
var Matrix = require("matrix-js-sdk"); var Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler';
var UserSettingsStore = require('../../UserSettingsStore'); var UserSettingsStore = require('../../UserSettingsStore');
var MatrixClientPeg = require("../../MatrixClientPeg"); var MatrixClientPeg = require("../../MatrixClientPeg");
@ -296,7 +297,7 @@ module.exports = React.createClass({
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
if (newProps.roomAddress != this.props.roomAddress) { if (newProps.roomAddress != this.props.roomAddress) {
throw new Error("changing room on a RoomView is not supported"); throw new Error(_t("changing room on a RoomView is not supported"));
} }
if (newProps.eventId != this.props.eventId) { if (newProps.eventId != this.props.eventId) {
@ -370,10 +371,10 @@ module.exports = React.createClass({
onPageUnload(event) { onPageUnload(event) {
if (ContentMessages.getCurrentUploads().length > 0) { if (ContentMessages.getCurrentUploads().length > 0) {
return event.returnValue = return event.returnValue =
'You seem to be uploading files, are you sure you want to quit?'; _t("You seem to be uploading files, are you sure you want to quit?");
} else if (this._getCallForRoom() && this.state.callState !== 'ended') { } else if (this._getCallForRoom() && this.state.callState !== 'ended') {
return event.returnValue = return event.returnValue =
'You seem to be in a call, are you sure you want to quit?'; _t("You seem to be in a call, are you sure you want to quit?");
} }
}, },
@ -530,14 +531,14 @@ module.exports = React.createClass({
if (!userHasUsedEncryption) { if (!userHasUsedEncryption) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Warning!", title: _t("Warning!"),
hasCancelButton: false, hasCancelButton: false,
description: ( description: (
<div> <div>
<p>End-to-end encryption is in beta and may not be reliable.</p> <p>{ _t("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>{ _t("You should not 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>{ _t("Devices will not 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("Encrypted messages will not be visible on clients that do not yet implement encryption") }.</p>
</div> </div>
), ),
}); });
@ -708,10 +709,10 @@ module.exports = React.createClass({
if (!unsentMessages.length) return ""; if (!unsentMessages.length) return "";
for (const event of unsentMessages) { for (const event of unsentMessages) {
if (!event.error || event.error.name !== "UnknownDeviceError") { 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) { _getUnsentMessages: function(room) {
@ -871,15 +872,15 @@ module.exports = React.createClass({
) { ) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, { Modal.createDialog(NeedToRegisterDialog, {
title: "Failed to join the room", title: _t("Failed to join the room"),
description: "This room is private or inaccessible to guests. You may be able to join if you register." description: _t("This room is private or inaccessible to guests. You may be able to join if you register") + "."
}); });
} else { } else {
var msg = error.message ? error.message : JSON.stringify(error); var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to join room", title: _t("Failed to join room"),
description: msg description: msg,
}); });
} }
}).done(); }).done();
@ -939,8 +940,8 @@ module.exports = React.createClass({
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, { Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register", title: _t("Please Register"),
description: "Guest users can't upload files. Please register to upload." description: _t("Guest users can't upload files. Please register to upload") + "."
}); });
return; return;
} }
@ -959,8 +960,8 @@ module.exports = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to upload file " + file + " " + error); console.error("Failed to upload file " + file + " " + error);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to upload file", title: _t('Failed to upload file'),
description: ((error && error.message) ? error.message : "Server may be unavailable, overloaded, or the file too big"), description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")),
}); });
}); });
}, },
@ -1046,8 +1047,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Search failed: " + error); console.error("Search failed: " + error);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Search failed", title: _t("Search failed"),
description: ((error && error.message) ? error.message : "Server may be unavailable, overloaded, or search timed out :("), description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")),
}); });
}).finally(function() { }).finally(function() {
self.setState({ self.setState({
@ -1082,12 +1083,12 @@ module.exports = React.createClass({
if (!this.state.searchResults.next_batch) { if (!this.state.searchResults.next_batch) {
if (this.state.searchResults.results.length == 0) { if (this.state.searchResults.results.length == 0) {
ret.push(<li key="search-top-marker"> 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> </li>
); );
} else { } else {
ret.push(<li key="search-top-marker"> 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> </li>
); );
} }
@ -1124,10 +1125,10 @@ module.exports = React.createClass({
// it. We should tell the js sdk to go and find out about // it. We should tell the js sdk to go and find out about
// it. But that's not an issue currently, as synapse only // it. But that's not an issue currently, as synapse only
// returns results for rooms we're joined to. // 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"}> ret.push(<li key={mxEv.getId() + "-room"}>
<h1>Room: { roomName }</h1> <h1>{ _t("Room") }: { roomName }</h1>
</li>); </li>);
lastRoomId = roomId; lastRoomId = roomId;
} }
@ -1173,7 +1174,7 @@ module.exports = React.createClass({
}); });
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(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"), description: fails.map(function(result) { return result.reason; }).join("\n"),
}); });
// still editing room settings // still editing room settings
@ -1209,11 +1210,11 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() { MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' }); dis.dispatch({ action: 'view_next_room' });
}, function(err) { }, function(err) {
var errCode = err.errcode || "unknown error code"; var errCode = err.errcode || _t("unknown error code");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: `Failed to forget room (${errCode})` description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
}); });
}); });
}, },
@ -1234,8 +1235,8 @@ module.exports = React.createClass({
var msg = error.message ? error.message : JSON.stringify(error); var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to reject invite", title: _t("Failed to reject invite"),
description: msg description: msg,
}); });
self.setState({ self.setState({
@ -1681,7 +1682,7 @@ module.exports = React.createClass({
if (call.type === "video") { if (call.type === "video") {
zoomButton = ( 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 }}/> <TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
</div> </div>
); );
@ -1689,14 +1690,14 @@ module.exports = React.createClass({
videoMuteButton = videoMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}> <div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
<TintableSvg src={call.isLocalVideoMuted() ? "img/video-unmute.svg" : "img/video-mute.svg"} <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"/> width="31" height="27"/>
</div>; </div>;
} }
voiceMuteButton = voiceMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}> <div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
<TintableSvg src={call.isMicrophoneMuted() ? "img/voice-unmute.svg" : "img/voice-mute.svg"} <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"/> width="21" height="26"/>
</div>; </div>;

View file

@ -23,12 +23,14 @@ var Matrix = require("matrix-js-sdk");
var EventTimeline = Matrix.EventTimeline; var EventTimeline = Matrix.EventTimeline;
var sdk = require('../../index'); var sdk = require('../../index');
import { _t } from '../../languageHandler';
var MatrixClientPeg = require("../../MatrixClientPeg"); var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher"); var dis = require("../../dispatcher");
var ObjectUtils = require('../../ObjectUtils'); var ObjectUtils = require('../../ObjectUtils');
var Modal = require("../../Modal"); var Modal = require("../../Modal");
var UserActivity = require("../../UserActivity"); var UserActivity = require("../../UserActivity");
var KeyCode = require('../../KeyCode'); var KeyCode = require('../../KeyCode');
import UserSettingsStore from '../../UserSettingsStore';
var PAGINATE_SIZE = 20; var PAGINATE_SIZE = 20;
var INITIAL_SIZE = 20; var INITIAL_SIZE = 20;
@ -122,7 +124,7 @@ var TimelinePanel = React.createClass({
let initialReadMarker = null; let initialReadMarker = null;
if (this.props.manageReadMarkers) { if (this.props.manageReadMarkers) {
const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read'); const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read');
if (readmarker){ if (readmarker) {
initialReadMarker = readmarker.getContent().event_id; initialReadMarker = readmarker.getContent().event_id;
} else { } else {
initialReadMarker = this._getCurrentReadReceipt(); initialReadMarker = this._getCurrentReadReceipt();
@ -171,6 +173,12 @@ var TimelinePanel = React.createClass({
// cache of matrixClient.getSyncState() (but from the 'sync' event) // cache of matrixClient.getSyncState() (but from the 'sync' event)
clientSyncState: MatrixClientPeg.get().getSyncState(), clientSyncState: MatrixClientPeg.get().getSyncState(),
// should the event tiles have twelve hour times
isTwelveHour: UserSettingsStore.getSyncedSetting('showTwelveHourTimestamps'),
// always show timestamps on event tiles?
alwaysShowTimestamps: UserSettingsStore.getSyncedSetting('alwaysShowTimestamps'),
}; };
}, },
@ -907,14 +915,11 @@ var TimelinePanel = React.createClass({
}); });
}; };
} }
var message = "Tried to load a specific point in this room's timeline, but "; var message = (error.errcode == 'M_FORBIDDEN')
if (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") + "."
message += "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") + ".";
} else {
message += "was unable to find it.";
}
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to load timeline position", title: _t("Failed to load timeline position"),
description: message, description: message,
onFinished: onFinished, onFinished: onFinished,
}); });
@ -1106,7 +1111,6 @@ var TimelinePanel = React.createClass({
const forwardPaginating = ( const forwardPaginating = (
this.state.forwardPaginating || this.state.clientSyncState == 'PREPARED' this.state.forwardPaginating || this.state.clientSyncState == 'PREPARED'
); );
return ( return (
<MessagePanel ref="messagePanel" <MessagePanel ref="messagePanel"
hidden={ this.props.hidden } hidden={ this.props.hidden }
@ -1125,6 +1129,8 @@ var TimelinePanel = React.createClass({
onFillRequest={ this.onMessageListFillRequest } onFillRequest={ this.onMessageListFillRequest }
onUnfillRequest={ this.onMessageListUnfillRequest } onUnfillRequest={ this.onMessageListUnfillRequest }
opacity={ this.props.opacity } opacity={ this.props.opacity }
isTwelveHour={ this.state.isTwelveHour }
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
className={ this.props.className } className={ this.props.className }
tileShape={ this.props.tileShape } tileShape={ this.props.tileShape }
/> />

View file

@ -29,6 +29,8 @@ const Email = require('../../email');
const AddThreepid = require('../../AddThreepid'); const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig'); const SdkConfig = require('../../SdkConfig');
import AccessibleButton from '../views/elements/AccessibleButton'; import AccessibleButton from '../views/elements/AccessibleButton';
import { _t } from '../../languageHandler';
import * as languageHandler from '../../languageHandler';
import * as FormattingUtils from '../../utils/FormattingUtils'; import * as FormattingUtils from '../../utils/FormattingUtils';
// if this looks like a release, use the 'version' from package.json; else use // if this looks like a release, use the 'version' from package.json; else use
@ -53,6 +55,8 @@ const gHVersionLabel = function(repo, token='') {
// Enumerate some simple 'flip a bit' UI settings (if any). // Enumerate some simple 'flip a bit' UI settings (if any).
// 'id' gives the key name in the im.vector.web.settings account data event // 'id' gives the key name in the im.vector.web.settings account data event
// 'label' is how we describe it in the UI. // 'label' is how we describe it in the UI.
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
// since they will be translated when rendered.
const SETTINGS_LABELS = [ const SETTINGS_LABELS = [
{ {
id: 'autoplayGifsAndVideos', id: 'autoplayGifsAndVideos',
@ -66,7 +70,6 @@ const SETTINGS_LABELS = [
id: 'dontSendTypingNotifications', id: 'dontSendTypingNotifications',
label: "Don't send typing notifications", label: "Don't send typing notifications",
}, },
/*
{ {
id: 'alwaysShowTimestamps', id: 'alwaysShowTimestamps',
label: 'Always show message timestamps', label: 'Always show message timestamps',
@ -75,6 +78,7 @@ const SETTINGS_LABELS = [
id: 'showTwelveHourTimestamps', id: 'showTwelveHourTimestamps',
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)', label: 'Show timestamps in 12 hour format (e.g. 2:30pm)',
}, },
/*
{ {
id: 'useCompactLayout', id: 'useCompactLayout',
label: 'Use compact timeline layout', label: 'Use compact timeline layout',
@ -86,6 +90,8 @@ const SETTINGS_LABELS = [
*/ */
]; ];
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
// since they will be translated when rendered.
const CRYPTO_SETTINGS_LABELS = [ const CRYPTO_SETTINGS_LABELS = [
{ {
id: 'blacklistUnverifiedDevices', id: 'blacklistUnverifiedDevices',
@ -119,7 +125,6 @@ const THEMES = [
}, },
]; ];
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'UserSettings', displayName: 'UserSettings',
@ -197,6 +202,10 @@ module.exports = React.createClass({
this._syncedSettings = syncedSettings; this._syncedSettings = syncedSettings;
this._localSettings = UserSettingsStore.getLocalSettings(); this._localSettings = UserSettingsStore.getLocalSettings();
this.setState({
language: languageHandler.getCurrentLanguage(),
});
}, },
componentDidMount: function() { componentDidMount: function() {
@ -232,8 +241,8 @@ module.exports = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to load user settings: " + error); console.error("Failed to load user settings: " + error);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Can't load user settings", title: _t("Can't load user settings"),
description: ((error && error.message) ? error.message : "Server may be unavailable or overloaded"), description: ((error && error.message) ? error.message : _t("Server may be unavailable or overloaded")),
}); });
}); });
}, },
@ -248,8 +257,8 @@ module.exports = React.createClass({
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, { Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register", title: _t("Please Register"),
description: "Guests can't set avatars. Please register.", description: _t("Guests can't set avatars. Please register") + ".",
}); });
return; return;
} }
@ -274,8 +283,8 @@ module.exports = React.createClass({
console.error("Failed to set avatar: " + err); console.error("Failed to set avatar: " + err);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to set avatar", title: _t("Failed to set avatar"),
description: ((err && err.message) ? err.message : "Operation failed"), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
}); });
}, },
@ -283,19 +292,16 @@ module.exports = React.createClass({
onLogoutClicked: function(ev) { onLogoutClicked: function(ev) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Sign out?", title: _t("Sign out"),
description: description:
<div> <div>
For security, logging out will delete any end-to-end encryption keys from this browser. { _t("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.") }.
If you want to be able to decrypt your conversation history from future Riot sessions,
please export your room keys for safe-keeping.
</div>, </div>,
button: "Sign out", button: _t("Sign out"),
extraButtons: [ extraButtons: [
<button key="export" className="mx_Dialog_primary" <button key="export" className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}> onClick={this._onExportE2eKeysClicked}>
Export E2E room keys { _t("Export E2E room keys") }
</button>, </button>,
], ],
onFinished: (confirmed) => { onFinished: (confirmed) => {
@ -312,14 +318,14 @@ module.exports = React.createClass({
onPasswordChangeError: function(err) { onPasswordChangeError: function(err) {
let errMsg = err.error || ""; let errMsg = err.error || "";
if (err.httpStatus === 403) { if (err.httpStatus === 403) {
errMsg = "Failed to change password. Is your password correct?"; errMsg = _t("Failed to change password. Is your password correct?");
} else if (err.httpStatus) { } else if (err.httpStatus) {
errMsg += ` (HTTP status ${err.httpStatus})`; errMsg += ` (HTTP status ${err.httpStatus})`;
} }
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to change password: " + errMsg); console.error("Failed to change password: " + errMsg);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: errMsg, description: errMsg,
}); });
}, },
@ -327,10 +333,8 @@ module.exports = React.createClass({
onPasswordChanged: function() { onPasswordChanged: function() {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Success", title: _t("Success"),
description: `Your password was successfully changed. You will not description: _t("Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them") + ".",
receive push notifications on other devices until you
log back in to them.`,
}); });
}, },
@ -356,8 +360,8 @@ module.exports = React.createClass({
const emailAddress = this.refs.add_email_input.value; const emailAddress = this.refs.add_email_input.value;
if (!Email.looksValid(emailAddress)) { if (!Email.looksValid(emailAddress)) {
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Invalid Email Address", title: _t("Invalid Email Address"),
description: "This doesn't appear to be a valid email address", description: _t("This doesn't appear to be a valid email address"),
}); });
return; return;
} }
@ -366,17 +370,17 @@ module.exports = React.createClass({
// same here. // same here.
this._addThreepid.addEmailAddress(emailAddress, true).done(() => { this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Verification Pending", title: _t("Verification Pending"),
description: "Please check your email and click on the link it contains. Once this is done, click continue.", description: _t("Please check your email and click on the link it contains. Once this is done, click continue."),
button: 'Continue', button: _t('Continue'),
onFinished: this.onEmailDialogFinished, onFinished: this.onEmailDialogFinished,
}); });
}, (err) => { }, (err) => {
this.setState({email_add_pending: false}); this.setState({email_add_pending: false});
console.error("Unable to add email address " + emailAddress + " " + err); console.error("Unable to add email address " + emailAddress + " " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Unable to add email address", title: _t("Unable to add email address"),
description: ((err && err.message) ? err.message : "Operation failed"), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
}); });
ReactDOM.findDOMNode(this.refs.add_email_input).blur(); ReactDOM.findDOMNode(this.refs.add_email_input).blur();
@ -386,9 +390,9 @@ module.exports = React.createClass({
onRemoveThreepidClicked: function(threepid) { onRemoveThreepidClicked: function(threepid) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Remove Contact Information?", title: _t("Remove Contact Information?"),
description: "Remove " + threepid.address + "?", description: _t("Remove ") + threepid.address + "?",
button: 'Remove', button: _t('Remove'),
onFinished: (submit) => { onFinished: (submit) => {
if (submit) { if (submit) {
this.setState({ this.setState({
@ -400,8 +404,8 @@ module.exports = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Unable to remove contact information: " + err); console.error("Unable to remove contact information: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Unable to remove contact information", title: _t("Unable to remove contact information"),
description: ((err && err.message) ? err.message : "Operation failed"), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
}).done(); }).done();
} }
@ -427,22 +431,22 @@ module.exports = React.createClass({
this.setState({email_add_pending: false}); this.setState({email_add_pending: false});
}, (err) => { }, (err) => {
this.setState({email_add_pending: false}); this.setState({email_add_pending: false});
if (err.errcode === 'M_THREEPID_AUTH_FAILED') { if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
let message = "Unable to verify email address. "; let message = _t("Unable to verify email address. ");
message += "Please check your email and click on the link it contains. Once this is done, click continue."; message += _t("Please check your email and click on the link it contains. Once this is done, click continue.");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Verification Pending", title: _t("Verification Pending"),
description: message, description: message,
button: 'Continue', button: _t('Continue'),
onFinished: this.onEmailDialogFinished, onFinished: this.onEmailDialogFinished,
}); });
} else { } else {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Unable to verify email address: " + err); console.error("Unable to verify email address: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Unable to verify email address", title: _t("Unable to verify email address"),
description: ((err && err.message) ? err.message : "Operation failed"), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
} }
}); });
@ -532,20 +536,41 @@ module.exports = React.createClass({
<div> <div>
<h3>Referral</h3> <h3>Referral</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
Refer a friend to Riot: <a href={href}>{href}</a> {_t("Refer a friend to Riot: ")} <a href={href}>{href}</a>
</div> </div>
</div> </div>
); );
}, },
onLanguageChange: function(l) {
UserSettingsStore.setLocalSetting('language', l);
this.setState({
language: l,
});
PlatformPeg.get().reload();
},
_renderLanguageSetting: function () {
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
return <div>
<label htmlFor="languageSelector">{_t('Interface Language')}</label>
<LanguageDropdown ref="language" onOptionChange={this.onLanguageChange}
className="mx_UserSettings_language"
value={this.state.language}
/>
</div>;
},
_renderUserInterfaceSettings: function() { _renderUserInterfaceSettings: function() {
return ( return (
<div> <div>
<h3>User Interface</h3> <h3>{ _t("User Interface") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
{ this._renderUrlPreviewSelector() } { this._renderUrlPreviewSelector() }
{ SETTINGS_LABELS.map( this._renderSyncedSetting ) } { SETTINGS_LABELS.map( this._renderSyncedSetting ) }
{ THEMES.map( this._renderThemeSelector ) } { THEMES.map( this._renderThemeSelector ) }
{ this._renderLanguageSetting() }
</div> </div>
</div> </div>
); );
@ -559,7 +584,7 @@ module.exports = React.createClass({
onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) } onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
/> />
<label htmlFor="urlPreviewsDisabled"> <label htmlFor="urlPreviewsDisabled">
Disable inline URL previews by default { _t("Disable inline URL previews by default") }
</label> </label>
</div>; </div>;
}, },
@ -572,7 +597,7 @@ module.exports = React.createClass({
onChange={ (e) => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) } onChange={ (e) => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
/> />
<label htmlFor={ setting.id }> <label htmlFor={ setting.id }>
{ setting.label } { _t(setting.label) }
</label> </label>
</div>; </div>;
}, },
@ -606,7 +631,7 @@ module.exports = React.createClass({
const deviceId = client.deviceId; const deviceId = client.deviceId;
let identityKey = client.getDeviceEd25519Key(); let identityKey = client.getDeviceEd25519Key();
if (!identityKey) { if (!identityKey) {
identityKey = "<not supported>"; identityKey = _t("<not supported>");
} else { } else {
identityKey = FormattingUtils.formatCryptoKey(identityKey); identityKey = FormattingUtils.formatCryptoKey(identityKey);
} }
@ -618,18 +643,18 @@ module.exports = React.createClass({
<div className="mx_UserSettings_importExportButtons"> <div className="mx_UserSettings_importExportButtons">
<AccessibleButton className="mx_UserSettings_button" <AccessibleButton className="mx_UserSettings_button"
onClick={this._onExportE2eKeysClicked}> onClick={this._onExportE2eKeysClicked}>
Export E2E room keys { _t("Export E2E room keys") }
</AccessibleButton> </AccessibleButton>
<AccessibleButton className="mx_UserSettings_button" <AccessibleButton className="mx_UserSettings_button"
onClick={this._onImportE2eKeysClicked}> onClick={this._onImportE2eKeysClicked}>
Import E2E room keys { _t("Import E2E room keys") }
</AccessibleButton> </AccessibleButton>
</div> </div>
); );
} }
return ( return (
<div> <div>
<h3>Cryptography</h3> <h3>{ _t("Cryptography") }</h3>
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection"> <div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
<ul> <ul>
<li><label>Device ID:</label> <span><code>{deviceId}</code></span></li> <li><label>Device ID:</label> <span><code>{deviceId}</code></span></li>
@ -660,7 +685,7 @@ module.exports = React.createClass({
} }
/> />
<label htmlFor={ setting.id }> <label htmlFor={ setting.id }>
{ setting.label } { _t(setting.label) }
</label> </label>
</div>; </div>;
}, },
@ -681,11 +706,11 @@ module.exports = React.createClass({
} }
return ( return (
<div> <div>
<h3>Bug Report</h3> <h3>{ _t("Bug Report") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<p>Found a bug?</p> <p>{ _t("Found a bug?") }</p>
<button className="mx_UserSettings_button danger" <button className="mx_UserSettings_button danger"
onClick={this._onBugReportClicked}>Report it onClick={this._onBugReportClicked}>{_t('Report it')}
</button> </button>
</div> </div>
</div> </div>
@ -708,8 +733,8 @@ module.exports = React.createClass({
e.target.checked = false; e.target.checked = false;
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, { Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register", title: _t("Please Register"),
description: "Guests can't use labs features. Please register.", description: _t("Guests can't use labs features. Please register") + ".",
}); });
return; return;
} }
@ -722,9 +747,9 @@ module.exports = React.createClass({
)); ));
return ( return (
<div> <div>
<h3>Labs</h3> <h3>{ _t("Labs") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<p>These are experimental features that may break in unexpected ways. Use with caution.</p> <p>{ _t("These are experimental features that may break in unexpected ways") }. { _t("Use with caution") }.</p>
{features} {features}
</div> </div>
</div> </div>
@ -736,10 +761,10 @@ module.exports = React.createClass({
if (MatrixClientPeg.get().isGuest()) return null; if (MatrixClientPeg.get().isGuest()) return null;
return <div> return <div>
<h3>Deactivate Account</h3> <h3>{ _t("Deactivate Account") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<AccessibleButton className="mx_UserSettings_button danger" <AccessibleButton className="mx_UserSettings_button danger"
onClick={this._onDeactivateAccountClicked}>Deactivate my account onClick={this._onDeactivateAccountClicked}> { _t("Deactivate my account") }
</AccessibleButton> </AccessibleButton>
</div> </div>
</div>; </div>;
@ -747,11 +772,11 @@ module.exports = React.createClass({
_renderClearCache: function() { _renderClearCache: function() {
return <div> return <div>
<h3>Clear Cache</h3> <h3>{ _t("Clear Cache") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<AccessibleButton className="mx_UserSettings_button danger" <AccessibleButton className="mx_UserSettings_button danger"
onClick={this._onClearCacheClicked}> onClick={this._onClearCacheClicked}>
Clear Cache and Reload { _t("Clear Cache and Reload") }
</AccessibleButton> </AccessibleButton>
</div> </div>
</div>; </div>;
@ -780,7 +805,7 @@ module.exports = React.createClass({
} }
return <div> return <div>
<h3>Bulk Options</h3> <h3>{ _t("Bulk Options") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
{reject} {reject}
</div> </div>
@ -800,7 +825,8 @@ module.exports = React.createClass({
}, },
nameForMedium: function(medium) { nameForMedium: function(medium) {
if (medium === 'msisdn') return 'Phone'; if (medium === 'msisdn') return _t('Phone');
if (medium === 'email') return _t('Email');
return medium[0].toUpperCase() + medium.slice(1); return medium[0].toUpperCase() + medium.slice(1);
}, },
@ -849,7 +875,7 @@ module.exports = React.createClass({
/> />
</div> </div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor"> <div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<img src="img/cancel-small.svg" width="14" height="14" alt="Remove" onClick={this.onRemoveThreepidClicked.bind(this, val)} /> <img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") } onClick={this.onRemoveThreepidClicked.bind(this, val)} />
</div> </div>
</div> </div>
); );
@ -861,14 +887,14 @@ module.exports = React.createClass({
addEmailSection = ( addEmailSection = (
<div className="mx_UserSettings_profileTableRow" key="_newEmail"> <div className="mx_UserSettings_profileTableRow" key="_newEmail">
<div className="mx_UserSettings_profileLabelCell"> <div className="mx_UserSettings_profileLabelCell">
<label>Email</label> <label>{_t('Email')}</label>
</div> </div>
<div className="mx_UserSettings_profileInputCell"> <div className="mx_UserSettings_profileInputCell">
<EditableText <EditableText
ref="add_email_input" ref="add_email_input"
className="mx_UserSettings_editable" className="mx_UserSettings_editable"
placeholderClassName="mx_UserSettings_threepidPlaceholder" placeholderClassName="mx_UserSettings_threepidPlaceholder"
placeholder={ "Add email address" } placeholder={ _t("Add email address") }
blurToCancel={ false } blurToCancel={ false }
onValueChanged={ this._onAddEmailEditFinished } /> onValueChanged={ this._onAddEmailEditFinished } />
</div> </div>
@ -890,7 +916,7 @@ module.exports = React.createClass({
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
accountJsx = ( accountJsx = (
<div className="mx_UserSettings_button" onClick={this.onUpgradeClicked}> <div className="mx_UserSettings_button" onClick={this.onUpgradeClicked}>
Create an account { _t("Create an account") }
</div> </div>
); );
} else { } else {
@ -908,7 +934,7 @@ module.exports = React.createClass({
let notificationArea; let notificationArea;
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) { if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
notificationArea = (<div> notificationArea = (<div>
<h3>Notifications</h3> <h3>{ _t("Notifications") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<Notifications threepids={this.state.threepids} brand={this.props.brand} /> <Notifications threepids={this.state.threepids} brand={this.props.brand} />
@ -927,7 +953,7 @@ module.exports = React.createClass({
return ( return (
<div className="mx_UserSettings"> <div className="mx_UserSettings">
<SimpleRoomHeader <SimpleRoomHeader
title="Settings" title={ _t("Settings") }
collapsedRhs={ this.props.collapsedRhs } collapsedRhs={ this.props.collapsedRhs }
onCancelClick={ this.props.onClose } onCancelClick={ this.props.onClose }
/> />
@ -935,13 +961,13 @@ module.exports = React.createClass({
<GeminiScrollbar className="mx_UserSettings_body" <GeminiScrollbar className="mx_UserSettings_body"
autoshow={true}> autoshow={true}>
<h3>Profile</h3> <h3>{ _t("Profile") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<div className="mx_UserSettings_profileTable"> <div className="mx_UserSettings_profileTable">
<div className="mx_UserSettings_profileTableRow"> <div className="mx_UserSettings_profileTableRow">
<div className="mx_UserSettings_profileLabelCell"> <div className="mx_UserSettings_profileLabelCell">
<label htmlFor="displayName">Display name</label> <label htmlFor="displayName">{ _t('Display name') }</label>
</div> </div>
<div className="mx_UserSettings_profileInputCell"> <div className="mx_UserSettings_profileInputCell">
<ChangeDisplayName /> <ChangeDisplayName />
@ -958,7 +984,7 @@ module.exports = React.createClass({
<div className="mx_UserSettings_avatarPicker_edit"> <div className="mx_UserSettings_avatarPicker_edit">
<label htmlFor="avatarInput" ref="file_label"> <label htmlFor="avatarInput" ref="file_label">
<img src="img/camera.svg" className="mx_filterFlipColor" <img src="img/camera.svg" className="mx_filterFlipColor"
alt="Upload avatar" title="Upload avatar" alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
width="17" height="15" /> width="17" height="15" />
</label> </label>
<input id="avatarInput" type="file" onChange={this.onAvatarSelected}/> <input id="avatarInput" type="file" onChange={this.onAvatarSelected}/>
@ -966,12 +992,12 @@ module.exports = React.createClass({
</div> </div>
</div> </div>
<h3>Account</h3> <h3>{ _t("Account") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section cadcampoHide">
<AccessibleButton className="mx_UserSettings_logout mx_UserSettings_button" onClick={this.onLogoutClicked}> <AccessibleButton className="mx_UserSettings_logout mx_UserSettings_button" onClick={this.onLogoutClicked}>
Sign out { _t("Sign out") }
</AccessibleButton> </AccessibleButton>
{accountJsx} {accountJsx}
@ -988,34 +1014,31 @@ module.exports = React.createClass({
{this._renderBulkOptions()} {this._renderBulkOptions()}
{this._renderBugReport()} {this._renderBugReport()}
<h3>Advanced</h3> <h3>{ _t("Advanced") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<div className="mx_UserSettings_advanced"> <div className="mx_UserSettings_advanced">
Logged in as {this._me} { _t("Logged in as:") } {this._me}
</div> </div>
<div className="mx_UserSettings_advanced"> <div className="mx_UserSettings_advanced">
Access Token: <span className="mx_UserSettings_advanced_spoiler" {_t('Access Token:')} <span className="mx_UserSettings_advanced_spoiler" onClick={this._showSpoiler} data-spoiler={ MatrixClientPeg.get().getAccessToken() }>&lt;{ _t("click to reveal") }&gt;</span>
onClick={this._showSpoiler}
data-spoiler={ MatrixClientPeg.get().getAccessToken() }
>&lt;click to reveal&gt;</span>
</div> </div>
<div className="mx_UserSettings_advanced"> <div className="mx_UserSettings_advanced">
Homeserver is { MatrixClientPeg.get().getHomeserverUrl() } { _t("Homeserver is") } { MatrixClientPeg.get().getHomeserverUrl() }
</div> </div>
<div className="mx_UserSettings_advanced"> <div className="mx_UserSettings_advanced">
Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() } { _t("Identity Server is") } { MatrixClientPeg.get().getIdentityServerUrl() }
</div> </div>
<div className="mx_UserSettings_advanced"> <div className="mx_UserSettings_advanced">
matrix-react-sdk version: {(REACT_SDK_VERSION !== '<local>') {_t('matrix-react-sdk version:')} {(REACT_SDK_VERSION !== '<local>')
? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION) ? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
: REACT_SDK_VERSION : REACT_SDK_VERSION
}<br/> }<br/>
riot-web version: {(this.state.vectorVersion !== undefined) {_t('riot-web version:')} {(this.state.vectorVersion !== undefined)
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion) ? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
: 'unknown' : 'unknown'
}<br/> }<br/>
olm version: {olmVersionString}<br/> { _t("olm version:") } {olmVersionString}<br/>
</div> </div>
</div> </div>

View file

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

View file

@ -18,8 +18,8 @@ limitations under the License.
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import { _t } from '../../../languageHandler';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import url from 'url';
import sdk from '../../../index'; import sdk from '../../../index';
import Login from '../../../Login'; import Login from '../../../Login';
@ -222,15 +222,17 @@ module.exports = React.createClass({
(this.state.enteredHomeserverUrl.startsWith("http:") || (this.state.enteredHomeserverUrl.startsWith("http:") ||
!this.state.enteredHomeserverUrl.startsWith("http"))) !this.state.enteredHomeserverUrl.startsWith("http")))
{ {
const urlStart = <a href='https://www.google.com/search?&q=enable%20unsafe%20scripts'>;
const urlEnd = </a>;
errorText = <span> errorText = <span>
Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. { _t('Can\'t connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s', {urlStart: urlStart, urlEnd: urlEnd})}
Either use HTTPS or <a href='https://www.google.com/search?&q=enable%20unsafe%20scripts'>enable unsafe scripts</a>
</span>; </span>;
} }
else { else {
const urlStart = <a href={this.state.enteredHomeserverUrl}>;
const urlEnd = </a>;
errorText = <span> errorText = <span>
Can't connect to homeserver - please check your connectivity and ensure { _t('Can\'t connect to homeserver - please check your connectivity and ensure your %(urlStart)s homeserver\'s SSL certificate %(urlEnd)s is trusted', {urlStart: urlStart, urlEnd: urlEnd})}
your <a href={ this.state.enteredHomeserverUrl }>homeserver's SSL certificate</a> is trusted.
</span>; </span>;
} }
} }
@ -242,12 +244,6 @@ module.exports = React.createClass({
switch (step) { switch (step) {
case 'm.login.password': case 'm.login.password':
const PasswordLogin = sdk.getComponent('login.PasswordLogin'); const PasswordLogin = sdk.getComponent('login.PasswordLogin');
// HSs that are not matrix.org may not be configured to have their
// domain name === domain part.
let hsDomain = url.parse(this.state.enteredHomeserverUrl).hostname;
if (hsDomain !== 'matrix.org') {
hsDomain = null;
}
return ( return (
<PasswordLogin <PasswordLogin
onSubmit={this.onPasswordLogin} onSubmit={this.onPasswordLogin}
@ -259,7 +255,6 @@ module.exports = React.createClass({
onPhoneNumberChanged={this.onPhoneNumberChanged} onPhoneNumberChanged={this.onPhoneNumberChanged}
onForgotPasswordClick={this.props.onForgotPasswordClick} onForgotPasswordClick={this.props.onForgotPasswordClick}
loginIncorrect={this.state.loginIncorrect} loginIncorrect={this.state.loginIncorrect}
hsDomain={hsDomain}
/> />
); );
case 'm.login.cas': case 'm.login.cas':
@ -273,8 +268,7 @@ module.exports = React.createClass({
} }
return ( return (
<div> <div>
Sorry, this homeserver is using a login which is not { _t('Sorry, this homeserver is using a login which is not recognised ')}({step})
recognised ({step})
</div> </div>
); );
} }
@ -291,7 +285,7 @@ module.exports = React.createClass({
if (this.props.enableGuest) { if (this.props.enableGuest) {
loginAsGuestJsx = loginAsGuestJsx =
<a className="mx_Login_create" onClick={this._onLoginAsGuestClick} href="#"> <a className="mx_Login_create" onClick={this._onLoginAsGuestClick} href="#">
Login as guest { _t('Login as guest')}
</a>; </a>;
} }
@ -299,7 +293,7 @@ module.exports = React.createClass({
if (this.props.onCancelClick) { if (this.props.onCancelClick) {
returnToAppJsx = returnToAppJsx =
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#"> <a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
Return to app { _t('Return to app')}
</a>; </a>;
} }
@ -308,7 +302,7 @@ module.exports = React.createClass({
<div className="mx_Login_box"> <div className="mx_Login_box">
<LoginHeader /> <LoginHeader />
<div> <div>
<h2>Sign in <h2>{ _t('Sign in')}
{ loader } { loader }
</h2> </h2>
{ this.componentForStep(this.state.currentFlow) } { this.componentForStep(this.state.currentFlow) }
@ -324,7 +318,7 @@ module.exports = React.createClass({
{ this.state.errorText } { this.state.errorText }
</div> </div>
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#"> <a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
Create a new account { _t('Create an account')}
</a> </a>
{ loginAsGuestJsx } { loginAsGuestJsx }
{ returnToAppJsx } { returnToAppJsx }

View file

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

View file

@ -27,6 +27,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import RegistrationForm from '../../views/login/RegistrationForm'; import RegistrationForm from '../../views/login/RegistrationForm';
import CaptchaForm from '../../views/login/CaptchaForm'; import CaptchaForm from '../../views/login/CaptchaForm';
import RtsClient from '../../../RtsClient'; import RtsClient from '../../../RtsClient';
import { _t } from '../../../languageHandler';
const MIN_PASSWORD_LENGTH = 6; const MIN_PASSWORD_LENGTH = 6;
@ -162,7 +163,7 @@ module.exports = React.createClass({
msisdn_available |= flow.stages.indexOf('m.login.msisdn') > -1; msisdn_available |= flow.stages.indexOf('m.login.msisdn') > -1;
} }
if (!msisdn_available) { 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({ this.setState({
@ -260,29 +261,29 @@ module.exports = React.createClass({
var errMsg; var errMsg;
switch (errCode) { switch (errCode) {
case "RegistrationForm.ERR_PASSWORD_MISSING": case "RegistrationForm.ERR_PASSWORD_MISSING":
errMsg = "Missing password."; errMsg = _t('Missing password.');
break; break;
case "RegistrationForm.ERR_PASSWORD_MISMATCH": case "RegistrationForm.ERR_PASSWORD_MISMATCH":
errMsg = "Passwords don't match."; errMsg = _t('Passwords don\'t match.');
break; break;
case "RegistrationForm.ERR_PASSWORD_LENGTH": 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; break;
case "RegistrationForm.ERR_EMAIL_INVALID": 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; break;
case "RegistrationForm.ERR_PHONE_NUMBER_INVALID": 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; break;
case "RegistrationForm.ERR_USERNAME_INVALID": 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; break;
case "RegistrationForm.ERR_USERNAME_BLANK": case "RegistrationForm.ERR_USERNAME_BLANK":
errMsg = "You need to enter a user name"; errMsg = _t('You need to enter a user name.');
break; break;
default: default:
console.error("Unknown error code: %s", errCode); console.error("Unknown error code: %s", errCode);
errMsg = "An unknown error occurred."; errMsg = _t('An unknown error occurred.');
break; break;
} }
this.setState({ this.setState({
@ -400,7 +401,7 @@ module.exports = React.createClass({
if (this.props.onCancelClick) { if (this.props.onCancelClick) {
returnToAppJsx = ( returnToAppJsx = (
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#"> <a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
Return to app {_t('Return to app')}
</a> </a>
); );
} }
@ -413,10 +414,10 @@ module.exports = React.createClass({
this.state.teamSelected.domain + "/icon.png" : this.state.teamSelected.domain + "/icon.png" :
null} null}
/> />
<h2>Create an account</h2> <h2>{_t('Create an account')}</h2>
{registerBody} {registerBody}
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#"> <a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
I already have an account {_t('I already have an account')}
</a> </a>
{returnToAppJsx} {returnToAppJsx}
<LoginFooter /> <LoginFooter />

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import sdk from '../../../index'; import sdk from '../../../index';
import { getAddressType, inviteMultipleToRoom } from '../../../Invite'; import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
import createRoom from '../../../createRoom'; import createRoom from '../../../createRoom';
@ -48,11 +49,7 @@ module.exports = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
title: "Start a chat",
description: "Who would you like to communicate with?",
value: "", value: "",
placeholder: "Email, name or matrix ID",
button: "Start Chat",
focus: true focus: true
}; };
}, },

View file

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

View file

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

View file

@ -20,6 +20,7 @@ import Matrix from 'matrix-js-sdk';
import React from 'react'; import React from 'react';
import sdk from '../../../index'; import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
@ -46,12 +47,6 @@ export default React.createClass({
title: React.PropTypes.string, title: React.PropTypes.string,
}, },
getDefaultProps: function() {
return {
title: "Authentication",
};
},
getInitialState: function() { getInitialState: function() {
return { return {
authError: null, authError: null,
@ -105,7 +100,7 @@ export default React.createClass({
return ( return (
<BaseDialog className="mx_InteractiveAuthDialog" <BaseDialog className="mx_InteractiveAuthDialog"
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
title={this.state.authError ? 'Error' : this.props.title} title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))}
> >
{content} {content}
</BaseDialog> </BaseDialog>

View file

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

View file

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

View file

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

View file

@ -0,0 +1,128 @@
/*
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 { _t } from '../../../languageHandler';
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.getAllLanguageKeysFromJson().then((langKeys) => {
const langs = [];
langKeys.forEach((languageKey) => {
langs.push({
value: languageKey,
label: _t(languageKey)
});
});
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

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
const MemberAvatar = require('../avatars/MemberAvatar.js'); const MemberAvatar = require('../avatars/MemberAvatar.js');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MemberEventListSummary', displayName: 'MemberEventListSummary',
@ -203,30 +204,146 @@ module.exports = React.createClass({
* @param {boolean} plural whether there were multiple users undergoing the same * @param {boolean} plural whether there were multiple users undergoing the same
* transition. * transition.
* @param {number} repeats the number of times the transition was repeated in a row. * @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) { _getDescriptionForTransition(t, plural, repeats) {
const beConjugated = plural ? "were" : "was"; // The empty interpolations 'severalUsers' and 'oneUser'
const invitation = "their invitation" + (plural || (repeats > 1) ? "s" : ""); // are there only to show translators to non-English languages
// that the verb is conjugated to plural or singular Subject.
let res = null; let res = null;
const map = { switch(t) {
"joined": "joined", case "joined":
"left": "left", if (repeats > 1) {
"joined_and_left": "joined and left", res = (plural)
"left_and_joined": "left and rejoined", ? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats })
"invite_reject": "rejected " + invitation, : _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats });
"invite_withdrawal": "had " + invitation + " withdrawn", } else {
"invited": beConjugated + " invited", res = (plural)
"banned": beConjugated + " banned", ? _t("%(severalUsers)sjoined", { severalUsers: "" })
"unbanned": beConjugated + " unbanned", : _t("%(oneUser)sjoined", { oneUser: "" });
"kicked": beConjugated + " kicked", }
"changed_name": "changed name",
"changed_avatar": "changed avatar",
};
if (Object.keys(map).includes(t)) { break;
res = map[t] + (repeats > 1 ? " " + repeats + " times" : "" ); 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; return res;
@ -254,11 +371,12 @@ module.exports = React.createClass({
return items[0]; return items[0];
} else if (remaining) { } else if (remaining) {
items = items.slice(0, itemLimit); items = items.slice(0, itemLimit);
const other = " other" + (remaining > 1 ? "s" : ""); return (remaining > 1)
return items.join(', ') + ' and ' + remaining + other; ? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } )
: _t("%(items)s and one other", { items: items.join(', ') });
} else { } else {
const lastItem = items.pop(); const lastItem = items.pop();
return items.join(', ') + ' and ' + lastItem; return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
} }
}, },

View file

@ -19,10 +19,8 @@ limitations under the License.
import React from 'react'; import React from 'react';
import * as Roles from '../../../Roles'; import * as Roles from '../../../Roles';
var LEVEL_ROLE_MAP = {};
var reverseRoles = {}; var reverseRoles = {};
Object.keys(Roles.LEVEL_ROLE_MAP).forEach(function(key) {
reverseRoles[Roles.LEVEL_ROLE_MAP[key]] = key;
});
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'PowerSelector', displayName: 'PowerSelector',
@ -44,10 +42,17 @@ module.exports = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
custom: (Roles.LEVEL_ROLE_MAP[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) { onSelectChange: function(event) {
this.setState({ custom: event.target.value === "Custom" }); this.setState({ custom: event.target.value === "Custom" });
if (event.target.value !== "Custom") { if (event.target.value !== "Custom") {
@ -94,7 +99,7 @@ module.exports = React.createClass({
selectValue = "Custom"; selectValue = "Custom";
} }
else { else {
selectValue = Roles.LEVEL_ROLE_MAP[this.props.value] || "Custom"; selectValue = LEVEL_ROLE_MAP[this.props.value] || "Custom";
} }
var select; var select;
if (this.props.disabled) { if (this.props.disabled) {
@ -105,7 +110,7 @@ module.exports = React.createClass({
const levels = [0, 50, 100]; const levels = [0, 50, 100];
let options = levels.map((level) => { let options = levels.map((level) => {
return { return {
value: Roles.LEVEL_ROLE_MAP[level], value: LEVEL_ROLE_MAP[level],
// Give a userDefault (users_default in the power event) of 0 but // Give a userDefault (users_default in the power event) of 0 but
// because level !== undefined, this should never be used. // because level !== undefined, this should never be used.
text: Roles.textualPowerLevel(level, 0), text: Roles.textualPowerLevel(level, 0),

View file

@ -19,6 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import sdk from '../../../index'; import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {field_input_incorrect} from '../../../UiEffects'; import {field_input_incorrect} from '../../../UiEffects';
@ -121,32 +122,16 @@ class PasswordLogin extends React.Component {
autoFocus autoFocus
/>; />;
case PasswordLogin.LOGIN_FIELD_MXID: case PasswordLogin.LOGIN_FIELD_MXID:
const mxidInputClasses = classNames({ return <input
"mx_Login_field": true, className="mx_Login_field mx_Login_username"
"mx_Login_username": true,
"mx_Login_field_has_prefix": true,
"mx_Login_field_has_suffix": Boolean(this.props.hsDomain),
});
let suffix = null;
if (this.props.hsDomain) {
suffix = <div className="mx_Login_field_suffix">
:{this.props.hsDomain}
</div>;
}
return <div className="mx_Login_field_group">
<div className="mx_Login_field_prefix">@</div>
<input
className={mxidInputClasses}
key="username_input" key="username_input"
type="text" type="text"
name="username" // make it a little easier for browser's remember-password name="username" // make it a little easier for browser's remember-password
onChange={this.onUsernameChanged} onChange={this.onUsernameChanged}
placeholder="username" placeholder={_t('User name')}
value={this.state.username} value={this.state.username}
autoFocus autoFocus
/> />;
{suffix}
</div>;
case PasswordLogin.LOGIN_FIELD_PHONE: case PasswordLogin.LOGIN_FIELD_PHONE:
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
return <div className="mx_Login_phoneSection"> return <div className="mx_Login_phoneSection">
@ -179,7 +164,7 @@ class PasswordLogin extends React.Component {
if (this.props.onForgotPasswordClick) { if (this.props.onForgotPasswordClick) {
forgotPasswordJsx = ( forgotPasswordJsx = (
<a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#"> <a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#">
Forgot your password? { _t('Forgot your password?') }
</a> </a>
); );
} }
@ -197,24 +182,24 @@ class PasswordLogin extends React.Component {
<div> <div>
<form onSubmit={this.onSubmitForm}> <form onSubmit={this.onSubmitForm}>
<div className="mx_Login_type_container"> <div className="mx_Login_type_container">
<label className="mx_Login_type_label">I want to sign in with my</label> <label className="mx_Login_type_label">{ _t('I want to sign in with') }</label>
<Dropdown <Dropdown
className="mx_Login_type_dropdown" className="mx_Login_type_dropdown"
value={this.state.loginType} value={this.state.loginType}
onOptionChange={this.onLoginTypeChange}> onOptionChange={this.onLoginTypeChange}>
<span key={PasswordLogin.LOGIN_FIELD_MXID}>Matrix ID</span> <span key={PasswordLogin.LOGIN_FIELD_MXID}>{ _t('my Matrix ID') }</span>
<span key={PasswordLogin.LOGIN_FIELD_EMAIL}>Email Address</span> <span key={PasswordLogin.LOGIN_FIELD_EMAIL}>{ _t('Email Address') }</span>
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>Phone</span> <span key={PasswordLogin.LOGIN_FIELD_PHONE}>{ _t('Phone') }</span>
</Dropdown> </Dropdown>
</div> </div>
{loginField} {loginField}
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password" <input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
name="password" name="password"
value={this.state.password} onChange={this.onPasswordChanged} value={this.state.password} onChange={this.onPasswordChanged}
placeholder="Password" /> placeholder={ _t('Password') } />
<br /> <br />
{forgotPasswordJsx} {forgotPasswordJsx}
<input className="mx_Login_submit" type="submit" value="Sign in" /> <input className="mx_Login_submit" type="submit" value={ _t('Sign in') } />
</form> </form>
</div> </div>
); );
@ -237,7 +222,6 @@ PasswordLogin.propTypes = {
onPhoneNumberChanged: React.PropTypes.func, onPhoneNumberChanged: React.PropTypes.func,
onPasswordChanged: React.PropTypes.func, onPasswordChanged: React.PropTypes.func,
loginIncorrect: React.PropTypes.bool, loginIncorrect: React.PropTypes.bool,
hsDomain: React.PropTypes.string,
}; };
module.exports = PasswordLogin; module.exports = PasswordLogin;

View file

@ -100,7 +100,7 @@ module.exports = React.createClass({
if (this.refs.email.value == '') { if (this.refs.email.value == '') {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Warning", title: "Warning!",
description: description:
<div> <div>
If you don't specify an email address, you won't be able to reset your password.<br/> If you don't specify an email address, you won't be able to reset your password.<br/>

View file

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

View file

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

View file

@ -16,8 +16,10 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); var React = require('react');
var classNames = require("classnames"); var classNames = require("classnames");
import { _t } from '../../../languageHandler';
var Modal = require('../../../Modal'); var Modal = require('../../../Modal');
var sdk = require('../../../index'); var sdk = require('../../../index');
@ -129,6 +131,9 @@ module.exports = WithMatrixClient(React.createClass({
* for now. * for now.
*/ */
tileShape: React.PropTypes.string, tileShape: React.PropTypes.string,
// show twelve hour timestamps
isTwelveHour: React.PropTypes.bool,
}, },
getInitialState: function() { getInitialState: function() {
@ -404,9 +409,10 @@ module.exports = WithMatrixClient(React.createClass({
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1); var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted; const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted;
var classes = classNames({ const classes = classNames({
mx_EventTile: true, mx_EventTile: true,
mx_EventTile_info: isInfoMessage, mx_EventTile_info: isInfoMessage,
mx_EventTile_12hr: this.props.isTwelveHour,
mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting', mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting',
mx_EventTile_sending: isSending, mx_EventTile_sending: isSending,
mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent', mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
@ -464,9 +470,9 @@ module.exports = WithMatrixClient(React.createClass({
if (needsSenderProfile) { if (needsSenderProfile) {
let aux = null; let aux = null;
if (!this.props.tileShape) { if (!this.props.tileShape) {
if (msgtype === 'm.image') aux = "sent an image"; if (msgtype === 'm.image') aux = _t('sent an image');
else if (msgtype === 'm.video') aux = "sent a video"; else if (msgtype === 'm.video') aux = _t('sent a video');
else if (msgtype === 'm.file') aux = "uploaded a file"; else if (msgtype === 'm.file') aux = _t('uploaded a file');
sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />; sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />;
} }
else { else {
@ -474,11 +480,10 @@ module.exports = WithMatrixClient(React.createClass({
} }
} }
var editButton = ( const editButton = (
<span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} /> <span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} />
); );
let e2e;
var e2e;
// cosmetic padlocks: // cosmetic padlocks:
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') { if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12" />; e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12" />;
@ -499,11 +504,10 @@ module.exports = WithMatrixClient(React.createClass({
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Unencrypted message" src="img/e2e-unencrypted.svg" width="12" height="12"/>; e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Unencrypted message" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
} }
const timestamp = this.props.mxEvent.getTs() ? 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") { 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 ( return (
<div className={classes}> <div className={classes}>
<div className="mx_EventTile_roomName"> <div className="mx_EventTile_roomName">

View file

@ -31,6 +31,7 @@ import classNames from 'classnames';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import sdk from '../../../index'; import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import createRoom from '../../../createRoom'; import createRoom from '../../../createRoom';
import DMRoomMap from '../../../utils/DMRoomMap'; import DMRoomMap from '../../../utils/DMRoomMap';
import Unread from '../../../Unread'; import Unread from '../../../Unread';
@ -219,7 +220,7 @@ module.exports = WithMatrixClient(React.createClass({
onKick: function() { onKick: function() {
const membership = this.props.member.membership; 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"); const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createDialog(ConfirmUserActionDialog, { Modal.createDialog(ConfirmUserActionDialog, {
member: this.props.member, member: this.props.member,
@ -241,7 +242,7 @@ module.exports = WithMatrixClient(React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Kick error: " + err); console.error("Kick error: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to kick", title: _t("Failed to kick"),
description: ((err && err.message) ? err.message : "Operation failed"), description: ((err && err.message) ? err.message : "Operation failed"),
}); });
} }
@ -256,7 +257,7 @@ module.exports = WithMatrixClient(React.createClass({
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createDialog(ConfirmUserActionDialog, { Modal.createDialog(ConfirmUserActionDialog, {
member: this.props.member, 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', askReason: this.props.member.membership != 'ban',
danger: this.props.member.membership != 'ban', danger: this.props.member.membership != 'ban',
onFinished: (proceed, reason) => { onFinished: (proceed, reason) => {
@ -283,8 +284,8 @@ module.exports = WithMatrixClient(React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Ban error: " + err); console.error("Ban error: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: "Failed to ban user", description: _t("Failed to ban user"),
}); });
} }
).finally(()=>{ ).finally(()=>{
@ -333,8 +334,8 @@ module.exports = WithMatrixClient(React.createClass({
}, function(err) { }, function(err) {
console.error("Mute error: " + err); console.error("Mute error: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: "Failed to mute user", description: _t("Failed to mute user"),
}); });
} }
).finally(()=>{ ).finally(()=>{
@ -376,14 +377,14 @@ module.exports = WithMatrixClient(React.createClass({
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') { if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, { Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register", title: _t("Please Register"),
description: "This action cannot be performed by a guest user. Please register to be able to do this." description: _t("This action cannot be performed by a guest user. Please register to be able to do this") + ".",
}); });
} else { } else {
console.error("Toggle moderator error:" + err); console.error("Toggle moderator error:" + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: "Failed to toggle moderator status", description: _t("Failed to toggle moderator status"),
}); });
} }
} }
@ -403,8 +404,8 @@ module.exports = WithMatrixClient(React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to change power level " + err); console.error("Failed to change power level " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: "Failed to change power level", description: _t("Failed to change power level"),
}); });
} }
).finally(()=>{ ).finally(()=>{
@ -432,13 +433,13 @@ module.exports = WithMatrixClient(React.createClass({
if (parseInt(myPower) === parseInt(powerLevel)) { if (parseInt(myPower) === parseInt(powerLevel)) {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Warning", title: _t("Warning!"),
description: description:
<div> <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/> { _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/>
Are you sure? { _t("Are you sure?") }
</div>, </div>,
button: "Continue", button: _t("Continue"),
onFinished: function(confirmed) { onFinished: function(confirmed) {
if (confirmed) { if (confirmed) {
self._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); self._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
@ -581,9 +582,9 @@ module.exports = WithMatrixClient(React.createClass({
// still loading // still loading
devComponents = <Spinner />; devComponents = <Spinner />;
} else if (devices === null) { } else if (devices === null) {
devComponents = "Unable to load device list"; devComponents = _t("Unable to load device list");
} else if (devices.length === 0) { } else if (devices.length === 0) {
devComponents = "No devices with registered encryption keys"; devComponents = _t("No devices with registered encryption keys");
} else { } else {
devComponents = []; devComponents = [];
for (var i = 0; i < devices.length; i++) { for (var i = 0; i < devices.length; i++) {
@ -595,7 +596,7 @@ module.exports = WithMatrixClient(React.createClass({
return ( return (
<div> <div>
<h3>Devices</h3> <h3>{ _t("Devices") }</h3>
<div className="mx_MemberInfo_devices"> <div className="mx_MemberInfo_devices">
{devComponents} {devComponents}
</div> </div>
@ -644,11 +645,11 @@ module.exports = WithMatrixClient(React.createClass({
<div className="mx_RoomTile_avatar"> <div className="mx_RoomTile_avatar">
<img src="img/create-big.svg" width="26" height="26" /> <img src="img/create-big.svg" width="26" height="26" />
</div> </div>
<div className={labelClasses}><i>Start new chat</i></div> <div className={labelClasses}><i>{ _t("Start a chat") }</i></div>
</AccessibleButton>; </AccessibleButton>;
startChat = <div> startChat = <div>
<h3>Direct chats</h3> <h3>{ _t("Direct chats") }</h3>
{tiles} {tiles}
{startNewChat} {startNewChat}
</div>; </div>;
@ -661,7 +662,7 @@ module.exports = WithMatrixClient(React.createClass({
if (this.state.can.kick) { if (this.state.can.kick) {
const membership = this.props.member.membership; const membership = this.props.member.membership;
const kickLabel = membership === "invite" ? "Disinvite" : "Kick"; const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
kickButton = ( kickButton = (
<AccessibleButton className="mx_MemberInfo_field" <AccessibleButton className="mx_MemberInfo_field"
onClick={this.onKick}> onClick={this.onKick}>
@ -670,9 +671,9 @@ module.exports = WithMatrixClient(React.createClass({
); );
} }
if (this.state.can.ban) { if (this.state.can.ban) {
let label = 'Ban'; let label = _t("Ban");
if (this.props.member.membership == 'ban') { if (this.props.member.membership == 'ban') {
label = 'Unban'; label = _t("Unban");
} }
banButton = ( banButton = (
<AccessibleButton className="mx_MemberInfo_field" <AccessibleButton className="mx_MemberInfo_field"
@ -682,7 +683,7 @@ module.exports = WithMatrixClient(React.createClass({
); );
} }
if (this.state.can.mute) { if (this.state.can.mute) {
const muteLabel = this.state.muted ? "Unmute" : "Mute"; const muteLabel = this.state.muted ? _t("Unmute") : _t("Mute");
muteButton = ( muteButton = (
<AccessibleButton className="mx_MemberInfo_field" <AccessibleButton className="mx_MemberInfo_field"
onClick={this.onMuteToggle}> onClick={this.onMuteToggle}>
@ -691,7 +692,7 @@ module.exports = WithMatrixClient(React.createClass({
); );
} }
if (this.state.can.toggleMod) { 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}> giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
{giveOpLabel} {giveOpLabel}
</AccessibleButton>; </AccessibleButton>;
@ -742,7 +743,7 @@ module.exports = WithMatrixClient(React.createClass({
{ this.props.member.userId } { this.props.member.userId }
</div> </div>
<div className="mx_MemberInfo_profileField"> <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>
<div className="mx_MemberInfo_profileField"> <div className="mx_MemberInfo_profileField">
<PresenceLabel activeAgo={ presenceLastActiveAgo } <PresenceLabel activeAgo={ presenceLastActiveAgo }

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
var React = require('react'); var React = require('react');
import { _t } from '../../../languageHandler';
var classNames = require('classnames'); var classNames = require('classnames');
var Matrix = require("matrix-js-sdk"); var Matrix = require("matrix-js-sdk");
var q = require('q'); var q = require('q');
@ -207,7 +208,9 @@ module.exports = React.createClass({
// For now we'll pretend this is any entity. It should probably be a separate tile. // For now we'll pretend this is any entity. It should probably be a separate tile.
var EntityTile = sdk.getComponent("rooms.EntityTile"); var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); 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 ( return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={ <EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} /> <BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
@ -364,7 +367,7 @@ module.exports = React.createClass({
<form autoComplete="off"> <form autoComplete="off">
<input className="mx_MemberList_query" id="mx_MemberList_query" type="text" <input className="mx_MemberList_query" id="mx_MemberList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery} onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder="Filter room members" /> placeholder={ _t('Filter room members') } />
</form> </form>
); );

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
var React = require('react'); var React = require('react');
import { _t } from '../../../languageHandler';
var CallHandler = require('../../../CallHandler'); var CallHandler = require('../../../CallHandler');
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
var Modal = require('../../../Modal'); var Modal = require('../../../Modal');
@ -93,8 +93,8 @@ export default class MessageComposer extends React.Component {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
let NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); let NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, { Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register", title: _t('Please Register'),
description: "Guest users can't upload files. Please register to upload.", description: _t('Guest users can\'t upload files. Please register to upload') + '.',
}); });
return; return;
} }
@ -118,10 +118,10 @@ export default class MessageComposer extends React.Component {
} }
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Upload Files", title: _t('Upload Files'),
description: ( description: (
<div> <div>
<p>Are you sure you want upload the following files?</p> <p>{ _t('Are you sure you want upload the following files?') }</p>
<ul style={{listStyle: 'none', textAlign: 'left'}}> <ul style={{listStyle: 'none', textAlign: 'left'}}>
{fileList} {fileList}
</ul> </ul>
@ -240,11 +240,11 @@ export default class MessageComposer extends React.Component {
if (roomIsEncrypted) { if (roomIsEncrypted) {
// FIXME: show a /!\ if there are untrusted devices in the room... // FIXME: show a /!\ if there are untrusted devices in the room...
e2eImg = 'img/e2e-verified.svg'; e2eImg = 'img/e2e-verified.svg';
e2eTitle = 'Encrypted room'; e2eTitle = _t('Encrypted room');
e2eClass = 'mx_MessageComposer_e2eIcon'; e2eClass = 'mx_MessageComposer_e2eIcon';
} else { } else {
e2eImg = 'img/e2e-unencrypted.svg'; e2eImg = 'img/e2e-unencrypted.svg';
e2eTitle = 'Unencrypted room'; e2eTitle = _t('Unencrypted room');
e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor'; e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
} }
@ -257,16 +257,16 @@ export default class MessageComposer extends React.Component {
if (this.props.callState && this.props.callState !== 'ended') { if (this.props.callState && this.props.callState !== 'ended') {
hangupButton = hangupButton =
<div key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}> <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>; </div>;
} }
else { else {
callButton = 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"/> <TintableSvg src="img/icon-call.svg" width="35" height="35"/>
</div>; </div>;
videoCallButton = 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"/> <TintableSvg src="img/icons-video.svg" width="35" height="35"/>
</div>; </div>;
} }
@ -280,7 +280,7 @@ export default class MessageComposer extends React.Component {
// complex because of conference calls. // complex because of conference calls.
var uploadButton = ( var uploadButton = (
<div key="controls_upload" className="mx_MessageComposer_upload" <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"/> <TintableSvg src="img/icons-upload.svg" width="35" height="35"/>
<input ref="uploadInput" type="file" <input ref="uploadInput" type="file"
style={uploadInputStyle} style={uploadInputStyle}
@ -300,7 +300,7 @@ export default class MessageComposer extends React.Component {
); );
const placeholderText = roomIsEncrypted ? const placeholderText = roomIsEncrypted ?
"Send an encrypted message…" : "Send a message (unencrypted)…"; _t('Send an encrypted message') + '…' : _t('Send a message (unencrypted)') + '…';
controls.push( controls.push(
<MessageComposerInput <MessageComposerInput
@ -325,7 +325,7 @@ export default class MessageComposer extends React.Component {
} else { } else {
controls.push( controls.push(
<div key="controls_error" className="mx_MessageComposer_noperm_error"> <div key="controls_error" className="mx_MessageComposer_noperm_error">
You do not have permission to post to this room { _t('You do not have permission to post to this room') }
</div> </div>
); );
} }
@ -354,7 +354,7 @@ export default class MessageComposer extends React.Component {
mx_filterFlipColor: true, mx_filterFlipColor: true,
}); });
return <img className={className} return <img className={className}
title={name} title={ _t(name) }
onMouseDown={disabled ? null : onFormatButtonClicked} onMouseDown={disabled ? null : onFormatButtonClicked}
key={name} key={name}
src={`img/button-text-${name}${suffix}.svg`} src={`img/button-text-${name}${suffix}.svg`}
@ -374,11 +374,11 @@ export default class MessageComposer extends React.Component {
<div className="mx_MessageComposer_formatbar" style={this.state.showFormatting ? {} : {display: 'none'}}> <div className="mx_MessageComposer_formatbar" style={this.state.showFormatting ? {} : {display: 'none'}}>
{formatButtons} {formatButtons}
<div style={{flex: 1}}></div> <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} onMouseDown={this.onToggleMarkdownClicked}
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor" className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} /> 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} onClick={this.onToggleFormattingClicked}
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor" className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
src="img/icon-text-cancel.svg" /> src="img/icon-text-cancel.svg" />

View file

@ -30,6 +30,7 @@ import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
import SlashCommands from '../../../SlashCommands'; import SlashCommands from '../../../SlashCommands';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import sdk from '../../../index'; import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import KeyCode from '../../../KeyCode'; import KeyCode from '../../../KeyCode';
@ -504,8 +505,8 @@ export default class MessageComposerInput extends React.Component {
console.error("Command failure: %s", err); console.error("Command failure: %s", err);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Server error", title: _t("Server error"),
description: ((err && err.message) ? err.message : "Server unavailable, overloaded, or something else went wrong."), description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong") + "."),
}); });
}); });
} }
@ -513,8 +514,8 @@ export default class MessageComposerInput extends React.Component {
console.error(cmd.error); console.error(cmd.error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Command error", title: _t("Command error"),
description: cmd.error description: cmd.error,
}); });
} }
return true; return true;
@ -719,7 +720,7 @@ export default class MessageComposerInput extends React.Component {
<div className={className}> <div className={className}>
<img className="mx_MessageComposer_input_markdownIndicator mx_filterFlipColor" <img className="mx_MessageComposer_input_markdownIndicator mx_filterFlipColor"
onMouseDown={this.onMarkdownToggleClicked} 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`} /> src={`img/button-md-${!this.state.isRichtextEnabled}.png`} />
<Editor ref="editor" <Editor ref="editor"
placeholder={this.props.placeholder} placeholder={this.props.placeholder}

View file

@ -20,6 +20,7 @@ var SlashCommands = require("../../../SlashCommands");
var Modal = require("../../../Modal"); var Modal = require("../../../Modal");
var MemberEntry = require("../../../TabCompleteEntries").MemberEntry; var MemberEntry = require("../../../TabCompleteEntries").MemberEntry;
var sdk = require('../../../index'); var sdk = require('../../../index');
import { _t } from '../../../languageHandler';
import UserSettingsStore from "../../../UserSettingsStore"; import UserSettingsStore from "../../../UserSettingsStore";
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
@ -294,8 +295,8 @@ export default React.createClass({
else { else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Unknown command", title: _t("Unknown command"),
description: "Usage: /markdown on|off" description: _t("Usage") + ": /markdown on|off",
}); });
} }
return; return;
@ -314,8 +315,8 @@ export default React.createClass({
console.error("Command failure: %s", err); console.error("Command failure: %s", err);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Server error", title: _t("Server error"),
description: ((err && err.message) ? err.message : "Server unavailable, overloaded, or something else went wrong."), description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong") + "."),
}); });
}); });
} }
@ -323,8 +324,8 @@ export default React.createClass({
console.error(cmd.error); console.error(cmd.error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Command error", title: _t("Command error"),
description: cmd.error description: cmd.error,
}); });
} }
return; return;

View file

@ -19,6 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var classNames = require('classnames'); var classNames = require('classnames');
var sdk = require('../../../index'); var sdk = require('../../../index');
import { _t } from '../../../languageHandler';
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
var Modal = require("../../../Modal"); var Modal = require("../../../Modal");
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
@ -119,8 +120,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to set avatar: " + errMsg); console.error("Failed to set avatar: " + errMsg);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: "Failed to set avatar.", description: _t("Failed to set avatar") + ".",
}); });
}).done(); }).done();
}, },
@ -205,7 +206,7 @@ module.exports = React.createClass({
// don't display the search count until the search completes and // don't display the search count until the search completes and
// gives us a valid (possibly zero) searchCount. // gives us a valid (possibly zero) searchCount.
if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) { 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("(~%(searchCount)s results)", { searchCount: this.props.searchInfo.searchCount }) }</div>;
} }
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'... // XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
@ -220,7 +221,7 @@ module.exports = React.createClass({
} }
} }
var roomName = 'Join Room'; var roomName = _t("Join Room");
if (this.props.oobData && this.props.oobData.name) { if (this.props.oobData && this.props.oobData.name) {
roomName = this.props.oobData.name; roomName = this.props.oobData.name;
} else if (this.props.room) { } else if (this.props.room) {
@ -261,7 +262,7 @@ module.exports = React.createClass({
<div className="mx_RoomHeader_avatarPicker_edit"> <div className="mx_RoomHeader_avatarPicker_edit">
<label htmlFor="avatarInput" ref="file_label"> <label htmlFor="avatarInput" ref="file_label">
<img src="img/camera.svg" <img src="img/camera.svg"
alt="Upload avatar" title="Upload avatar" alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
width="17" height="15" /> width="17" height="15" />
</label> </label>
<input id="avatarInput" type="file" onChange={ this.onAvatarSelected }/> <input id="avatarInput" type="file" onChange={ this.onAvatarSelected }/>
@ -296,7 +297,7 @@ module.exports = React.createClass({
var forget_button; var forget_button;
if (this.props.onForgetClick) { if (this.props.onForgetClick) {
forget_button = 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"/> <TintableSvg src="img/leave.svg" width="26" height="20"/>
</AccessibleButton>; </AccessibleButton>;
} }
@ -304,7 +305,7 @@ module.exports = React.createClass({
let search_button; let search_button;
if (this.props.onSearchClick && this.props.inRoom) { if (this.props.onSearchClick && this.props.inRoom) {
search_button = search_button =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search"> <AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title={ _t("Search") }>
<TintableSvg src="img/icons-search.svg" width="35" height="35"/> <TintableSvg src="img/icons-search.svg" width="35" height="35"/>
</AccessibleButton>; </AccessibleButton>;
} }
@ -312,7 +313,7 @@ module.exports = React.createClass({
var rightPanel_buttons; var rightPanel_buttons;
if (this.props.collapsedRhs) { if (this.props.collapsedRhs) {
rightPanel_buttons = 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"/> <TintableSvg src="img/maximise.svg" width="10" height="16"/>
</AccessibleButton>; </AccessibleButton>;
} }

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
var React = require("react"); var React = require("react");
var ReactDOM = require("react-dom"); var ReactDOM = require("react-dom");
import { _t } from '../../../languageHandler';
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var CallHandler = require('../../../CallHandler'); var CallHandler = require('../../../CallHandler');
@ -470,13 +471,12 @@ module.exports = React.createClass({
render: function() { render: function() {
var RoomSubList = sdk.getComponent('structures.RoomSubList'); var RoomSubList = sdk.getComponent('structures.RoomSubList');
var self = this; var self = this;
return ( return (
<GeminiScrollbar className="mx_RoomList_scrollbar" <GeminiScrollbar className="mx_RoomList_scrollbar"
autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll"> autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll">
<div className="mx_RoomList"> <div className="mx_RoomList">
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] } <RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
label="Invites" label={ _t('Invites') }
editable={ false } editable={ false }
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -487,9 +487,9 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } /> onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['m.favourite'] } <RoomSubList list={ self.state.lists['m.favourite'] }
label="Favourites" label={ _t('Favourites') }
tagName="m.favourite" tagName="m.favourite"
verb="favourite" verb={ _t('to favourite') }
editable={ true } editable={ true }
order="manual" order="manual"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -500,9 +500,9 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } /> onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] } <RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
label="People" label={ _t('People') }
tagName="im.vector.fake.direct" tagName="im.vector.fake.direct"
verb="tag direct chat" verb={ _t('to tag direct chat') }
editable={ true } editable={ true }
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -514,9 +514,9 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } /> onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] } <RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
label="Rooms" label={ _t('Rooms') }
editable={ true } editable={ true }
verb="restore" verb={ _t('to restore') }
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
@ -531,7 +531,7 @@ module.exports = React.createClass({
key={ tagName } key={ tagName }
label={ tagName } label={ tagName }
tagName={ tagName } tagName={ tagName }
verb={ "tag as " + tagName } verb={ _t('to tag as %(tagName)s', {tagName: tagName}) }
editable={ true } editable={ true }
order="manual" order="manual"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -545,9 +545,9 @@ module.exports = React.createClass({
}) } }) }
<RoomSubList list={ self.state.lists['m.lowpriority'] } <RoomSubList list={ self.state.lists['m.lowpriority'] }
label="Low priority" label={ _t('Low priority') }
tagName="m.lowpriority" tagName="m.lowpriority"
verb="demote" verb={ _t('to demote') }
editable={ true } editable={ true }
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
@ -558,7 +558,7 @@ module.exports = React.createClass({
onShowMoreRooms={ self.onShowMoreRooms } /> onShowMoreRooms={ self.onShowMoreRooms } />
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] } <RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
label="Historical" label={ _t('Historical') }
editable={ false } editable={ false }
order="recent" order="recent"
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }

View file

@ -21,6 +21,8 @@ var React = require('react');
var sdk = require('../../../index'); var sdk = require('../../../index');
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomPreviewBar', displayName: 'RoomPreviewBar',
@ -84,7 +86,7 @@ module.exports = React.createClass({
_roomNameElement: function(fallback) { _roomNameElement: function(fallback) {
fallback = fallback || 'a room'; fallback = fallback || 'a room';
const name = this.props.room ? this.props.room.name : (this.props.room_alias || ""); 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() { render: function() {
@ -128,13 +130,14 @@ module.exports = React.createClass({
</div>; </div>;
} }
} }
// TODO: find a way to respect HTML in counterpart!
joinBlock = ( joinBlock = (
<div> <div>
<div className="mx_RoomPreviewBar_invite_text"> <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>
<div className="mx_RoomPreviewBar_join_text"> <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? { _t('Would you like to') } <a onClick={ this.props.onJoinClick }>{ _t('accept') }</a> { _t('or') } <a onClick={ this.props.onRejectClick }>{ _t('decline') }</a> { _t('this invitation?') }
</div> </div>
{emailMatchBlock} {emailMatchBlock}
</div> </div>
@ -186,8 +189,8 @@ module.exports = React.createClass({
joinBlock = ( joinBlock = (
<div> <div>
<div className="mx_RoomPreviewBar_join_text"> <div className="mx_RoomPreviewBar_join_text">
You are trying to access { name }.<br/> { _t('You are trying to access %(roomName)s', {roomName: name}) }.<br/>
<a onClick={ this.props.onJoinClick }><b>Click here</b></a> to join the discussion! <a onClick={ this.props.onJoinClick }><b>{ _t('Click here') }</b></a> { _t('to join the discussion') }!
</div> </div>
</div> </div>
); );
@ -196,7 +199,7 @@ module.exports = React.createClass({
if (this.props.canPreview) { if (this.props.canPreview) {
previewBlock = ( previewBlock = (
<div className="mx_RoomPreviewBar_preview_text"> <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> </div>
); );
} }

View file

@ -17,6 +17,7 @@ limitations under the License.
import q from 'q'; import q from 'q';
import React from 'react'; import React from 'react';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import sdk from '../../../index'; import sdk from '../../../index';
@ -56,8 +57,8 @@ const BannedUser = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to unban: " + err); console.error("Failed to unban: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t('Error'),
description: "Failed to unban", description: _t('Failed to unban'),
}); });
}).done(); }).done();
}, },
@ -70,7 +71,7 @@ const BannedUser = React.createClass({
<AccessibleButton className="mx_RoomSettings_unbanButton" <AccessibleButton className="mx_RoomSettings_unbanButton"
onClick={this._onUnbanClick} onClick={this._onUnbanClick}
> >
Unban { _t('Unban') }
</AccessibleButton> </AccessibleButton>
{this.props.member.userId} {this.props.member.userId}
</li> </li>
@ -400,13 +401,13 @@ module.exports = React.createClass({
var value = ev.target.value; var value = ev.target.value;
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Privacy warning", title: _t('Privacy warning'),
description: description:
<div> <div>
Changes to who can read history will only apply to future messages in this room.<br/> { _t('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('The visibility of existing history will be unchanged') }.
</div>, </div>,
button: "Continue", button: _t('Continue'),
onFinished: function(confirmed) { onFinished: function(confirmed) {
if (confirmed) { if (confirmed) {
self.setState({ self.setState({
@ -523,11 +524,11 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.props.room.roomId).done(function() { MatrixClientPeg.get().forget(this.props.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' }); dis.dispatch({ action: 'view_next_room' });
}, function(err) { }, function(err) {
var errCode = err.errcode || "unknown error code"; var errCode = err.errcode || _t('unknown error code');
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t('Error'),
description: `Failed to forget room (${errCode})` description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
}); });
}); });
}, },
@ -537,14 +538,14 @@ module.exports = React.createClass({
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Warning!", title: _t('Warning!'),
description: ( description: (
<div> <div>
<p>End-to-end encryption is in beta and may not be reliable.</p> <p>{ _t('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>{ _t('You should not 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>{ _t('Devices will not 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>{ _t('Once encryption is enabled for a room it cannot 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('Encrypted messages will not be visible on clients that do not yet implement encryption') }.</p>
</div> </div>
), ),
onFinished: confirm=>{ onFinished: confirm=>{
@ -572,7 +573,7 @@ module.exports = React.createClass({
<input type="checkbox" ref="blacklistUnverified" <input type="checkbox" ref="blacklistUnverified"
defaultChecked={ isGlobalBlacklistUnverified || isRoomBlacklistUnverified } defaultChecked={ isGlobalBlacklistUnverified || isRoomBlacklistUnverified }
disabled={ isGlobalBlacklistUnverified || (this.refs.encrypt && !this.refs.encrypt.checked) }/> 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>; </label>;
if (!isEncrypted && if (!isEncrypted &&
@ -582,7 +583,7 @@ module.exports = React.createClass({
<label> <label>
<input type="checkbox" ref="encrypt" onClick={ this.onEnableEncryptionClick }/> <input type="checkbox" ref="encrypt" onClick={ this.onEnableEncryptionClick }/>
<img className="mx_RoomSettings_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12" /> <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> </label>
{ settings } { settings }
</div> </div>
@ -596,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-verified.svg" width="10" height="12" />
: <img className="mx_RoomSettings_e2eIcon" src="img/e2e-unencrypted.svg" width="12" 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 ? "Encryption is enabled in this room" : "Encryption is not enabled in this room" }.
</label> </label>
{ settings } { settings }
</div> </div>
@ -647,12 +648,12 @@ module.exports = React.createClass({
if (Object.keys(user_levels).length) { if (Object.keys(user_levels).length) {
userLevelsSection = userLevelsSection =
<div> <div>
<h3>Privileged Users</h3> <h3>{ _t('Privileged Users') }</h3>
<ul className="mx_RoomSettings_userLevels"> <ul className="mx_RoomSettings_userLevels">
{Object.keys(user_levels).map(function(user, i) { {Object.keys(user_levels).map(function(user, i) {
return ( return (
<li className="mx_RoomSettings_userLevel" key={user}> <li className="mx_RoomSettings_userLevel" key={user}>
{ user } is a <PowerSelector value={ user_levels[user] } disabled={true}/> { user } { _t('is a') } <PowerSelector value={ user_levels[user] } disabled={true}/>
</li> </li>
); );
})} })}
@ -660,7 +661,7 @@ module.exports = React.createClass({
</div>; </div>;
} }
else { 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"); var banned = this.props.room.getMembersWithMembership("ban");
@ -668,7 +669,7 @@ module.exports = React.createClass({
if (banned.length) { if (banned.length) {
bannedUsersSection = bannedUsersSection =
<div> <div>
<h3>Banned users</h3> <h3>{ _t('Banned users') }</h3>
<ul className="mx_RoomSettings_banned"> <ul className="mx_RoomSettings_banned">
{banned.map(function(member) { {banned.map(function(member) {
return ( return (
@ -683,7 +684,7 @@ module.exports = React.createClass({
if (this._yankValueFromEvent("m.room.create", "m.federate") === false) { if (this._yankValueFromEvent("m.room.create", "m.federate") === false) {
unfederatableSection = ( unfederatableSection = (
<div className="mx_RoomSettings_powerLevel"> <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> </div>
); );
} }
@ -694,14 +695,14 @@ module.exports = React.createClass({
if (myMember.membership === "join") { if (myMember.membership === "join") {
leaveButton = ( leaveButton = (
<AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onLeaveClick }> <AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onLeaveClick }>
Leave room { _t('Leave room') }
</AccessibleButton> </AccessibleButton>
); );
} }
else if (myMember.membership === "leave") { else if (myMember.membership === "leave") {
leaveButton = ( leaveButton = (
<AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onForgetClick }> <AccessibleButton className="mx_RoomSettings_leaveButton" onClick={ this.onForgetClick }>
Forget room { _t('Forget room') }
</AccessibleButton> </AccessibleButton>
); );
} }
@ -711,8 +712,8 @@ module.exports = React.createClass({
// TODO: support editing custom user_levels // TODO: support editing custom user_levels
var tags = [ var tags = [
{ name: "m.favourite", label: "Favourite", ref: "tag_favourite" }, { name: "m.favourite", label: _t('Favourite'), ref: "tag_favourite" },
{ name: "m.lowpriority", label: "Low priority", ref: "tag_lowpriority" }, { name: "m.lowpriority", label: _t('Low priority'), ref: "tag_lowpriority" },
]; ];
Object.keys(this.state.tags).sort().forEach(function(tagName) { Object.keys(this.state.tags).sort().forEach(function(tagName) {
@ -753,7 +754,7 @@ module.exports = React.createClass({
if (this.state.join_rule === "public" && aliasCount == 0) { if (this.state.join_rule === "public" && aliasCount == 0) {
addressWarning = addressWarning =
<div className="mx_RoomSettings_warning"> <div className="mx_RoomSettings_warning">
To link to a room it must have <a href="#addresses">an address</a>. { _t('To link to a room it must have') } <a href="#addresses"> { _t('an address') }</a>.
</div>; </div>;
} }
@ -761,10 +762,10 @@ module.exports = React.createClass({
if (this.state.join_rule !== "public" && this.state.guest_access === "forbidden") { if (this.state.join_rule !== "public" && this.state.guest_access === "forbidden") {
inviteGuestWarning = inviteGuestWarning =
<div className="mx_RoomSettings_warning"> <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" }); this.setState({ join_rule: "invite", guest_access: "can_join" });
e.preventDefault(); e.preventDefault();
}}>Click here to fix</a>. }}>{ _t('Click here to fix') }</a>.
</div>; </div>;
} }
@ -776,7 +777,7 @@ module.exports = React.createClass({
console.error(this.state.scalar_error); console.error(this.state.scalar_error);
integrationsError = ( integrationsError = (
<span className="mx_RoomSettings_integrationsButton_errorPopup"> <span className="mx_RoomSettings_integrationsButton_errorPopup">
Could not connect to the integration server { _t('Could not connect to the integration server') }
</span> </span>
); );
} }
@ -784,7 +785,7 @@ module.exports = React.createClass({
if (this.scalarClient.hasCredentials()) { if (this.scalarClient.hasCredentials()) {
integrationsButton = ( integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" onClick={ this.onManageIntegrations }> <div className="mx_RoomSettings_integrationsButton" onClick={ this.onManageIntegrations }>
Manage Integrations { _t('Manage Integrations') }
</div> </div>
); );
} else if (this.state.scalar_error) { } else if (this.state.scalar_error) {
@ -797,7 +798,7 @@ module.exports = React.createClass({
} else { } else {
integrationsButton = ( integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" style={{opacity: 0.5}}> <div className="mx_RoomSettings_integrationsButton" style={{opacity: 0.5}}>
Manage Integrations { _t('Manage Integrations') }
</div> </div>
); );
} }
@ -813,28 +814,28 @@ module.exports = React.createClass({
<div className="mx_RoomSettings_toggles"> <div className="mx_RoomSettings_toggles">
<div className="mx_RoomSettings_settings"> <div className="mx_RoomSettings_settings">
<h3>Who can access this room?</h3> <h3>{ _t('Who can access this room?') }</h3>
{ inviteGuestWarning } { inviteGuestWarning }
<label> <label>
<input type="radio" name="roomVis" value="invite_only" <input type="radio" name="roomVis" value="invite_only"
disabled={ !this.mayChangeRoomAccess() } disabled={ !this.mayChangeRoomAccess() }
onChange={this._onRoomAccessRadioToggle} onChange={this._onRoomAccessRadioToggle}
checked={this.state.join_rule !== "public"}/> checked={this.state.join_rule !== "public"}/>
Only people who have been invited { _t('Only people who have been invited') }
</label> </label>
<label> <label>
<input type="radio" name="roomVis" value="public_no_guests" <input type="radio" name="roomVis" value="public_no_guests"
disabled={ !this.mayChangeRoomAccess() } disabled={ !this.mayChangeRoomAccess() }
onChange={this._onRoomAccessRadioToggle} onChange={this._onRoomAccessRadioToggle}
checked={this.state.join_rule === "public" && this.state.guest_access !== "can_join"}/> 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>
<label> <label>
<input type="radio" name="roomVis" value="public_with_guests" <input type="radio" name="roomVis" value="public_with_guests"
disabled={ !this.mayChangeRoomAccess() } disabled={ !this.mayChangeRoomAccess() }
onChange={this._onRoomAccessRadioToggle} onChange={this._onRoomAccessRadioToggle}
checked={this.state.join_rule === "public" && this.state.guest_access === "can_join"}/> 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> </label>
{ addressWarning } { addressWarning }
<br/> <br/>
@ -847,7 +848,7 @@ module.exports = React.createClass({
</label> </label>
</div> </div>
<div className="mx_RoomSettings_settings"> <div className="mx_RoomSettings_settings">
<h3>Who can read history?</h3> <h3>{ _t('Who can read history?') }</h3>
<label> <label>
<input type="radio" name="historyVis" value="world_readable" <input type="radio" name="historyVis" value="world_readable"
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) } disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
@ -860,28 +861,28 @@ module.exports = React.createClass({
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) } disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
checked={historyVisibility === "shared"} checked={historyVisibility === "shared"}
onChange={this._onHistoryRadioToggle} /> 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>
<label> <label>
<input type="radio" name="historyVis" value="invited" <input type="radio" name="historyVis" value="invited"
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) } disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
checked={historyVisibility === "invited"} checked={historyVisibility === "invited"}
onChange={this._onHistoryRadioToggle} /> onChange={this._onHistoryRadioToggle} />
Members only (since they were invited) { _t('Members only') } ({ _t('since they were invited') })
</label> </label>
<label > <label >
<input type="radio" name="historyVis" value="joined" <input type="radio" name="historyVis" value="joined"
disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) } disabled={ !roomState.mayClientSendStateEvent("m.room.history_visibility", cli) }
checked={historyVisibility === "joined"} checked={historyVisibility === "joined"}
onChange={this._onHistoryRadioToggle} /> onChange={this._onHistoryRadioToggle} />
Members only (since they joined) { _t('Members only') } ({ _t('since they joined') })
</label> </label>
</div> </div>
</div> </div>
<div> <div>
<h3>Room Colour</h3> <h3>{ _t('Room Colour') }</h3>
<ColorSettings ref="color_settings" room={this.props.room} /> <ColorSettings ref="color_settings" room={this.props.room} />
</div> </div>
@ -899,41 +900,41 @@ module.exports = React.createClass({
<UrlPreviewSettings ref="url_preview_settings" room={this.props.room} /> <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_powerLevels mx_RoomSettings_settings">
<div className="mx_RoomSettings_powerLevel"> <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}/> <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>
<div className="mx_RoomSettings_powerLevel"> <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}/> <PowerSelector ref="events_default" value={send_level} controlled={false} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <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}/> <PowerSelector ref="invite" value={invite_level} controlled={false} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <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}/> <PowerSelector ref="state_default" value={state_level} controlled={false} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <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}/> <PowerSelector ref="kick" value={kick_level} controlled={false} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <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}/> <PowerSelector ref="ban" value={ban_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To redact other users' messages, you must be a </span> <span className="mx_RoomSettings_powerLevelKey">{ _t('To redact 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}/> <PowerSelector ref="redact" value={redact_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/>
</div> </div>
{Object.keys(events_levels).map(function(event_type, i) { {Object.keys(events_levels).map(function(event_type, i) {
return ( return (
<div className="mx_RoomSettings_powerLevel" key={event_type}> <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}/> <PowerSelector value={ events_levels[event_type] } controlled={false} disabled={true} onChange={self.onPowerLevelsChanged}/>
</div> </div>
); );
@ -946,9 +947,9 @@ module.exports = React.createClass({
{ bannedUsersSection } { bannedUsersSection }
<h3>Advanced</h3> <h3>{ _t('Advanced') }</h3>
<div className="mx_RoomSettings_settings"> <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>
</div> </div>
); );

View file

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

View file

@ -18,6 +18,7 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); var React = require('react');
import { _t } from '../../../languageHandler';
var sdk = require('../../../index'); var sdk = require('../../../index');
module.exports = React.createClass({ module.exports = React.createClass({
@ -34,8 +35,8 @@ module.exports = React.createClass({
<div className="mx_TopUnreadMessagesBar_scrollUp" <div className="mx_TopUnreadMessagesBar_scrollUp"
onClick={this.props.onScrollUpClick}> onClick={this.props.onScrollUpClick}>
<img src="img/scrollto.svg" width="24" height="24" <img src="img/scrollto.svg" width="24" height="24"
alt="Scroll to unread messages" alt={ _t('Scroll to unread messages') }
title="Scroll to unread messages"/> title={ _t('Scroll to unread messages') }/>
Jump to first unread message. Jump to first unread message.
</div> </div>
<img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor" <img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor"
@ -46,4 +47,3 @@ module.exports = React.createClass({
); );
}, },
}); });

View file

@ -15,13 +15,13 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import { _t } from '../../../languageHandler';
import sdk from '../../../index'; import sdk from '../../../index';
import AddThreepid from '../../../AddThreepid'; import AddThreepid from '../../../AddThreepid';
import WithMatrixClient from '../../../wrappers/WithMatrixClient'; import WithMatrixClient from '../../../wrappers/WithMatrixClient';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
export default WithMatrixClient(React.createClass({ export default WithMatrixClient(React.createClass({
displayName: 'AddPhoneNumber', displayName: 'AddPhoneNumber',
@ -83,7 +83,7 @@ export default WithMatrixClient(React.createClass({
console.error("Unable to add phone number: " + err); console.error("Unable to add phone number: " + err);
let msg = err.message; let msg = err.message;
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: _t("Error"),
description: msg, description: msg,
}); });
}).finally(() => { }).finally(() => {
@ -98,20 +98,19 @@ export default WithMatrixClient(React.createClass({
if (this._unmounted) return; if (this._unmounted) return;
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
let msgElements = [ let msgElements = [
<div key="_static" >A text message has been sent to +{msisdn}. <div key="_static" >{ _t("A text message has been sent to +%(msisdn)s. Please enter the verification code it contains", { msisdn: msisdn} ) }</div>
Please enter the verification code it contains</div>
]; ];
if (err) { if (err) {
let msg = err.error; let msg = err.error;
if (err.errcode == 'M_THREEPID_AUTH_FAILED') { 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>); msgElements.push(<div key="_error" className="error">{msg}</div>);
} }
Modal.createDialog(TextInputDialog, { Modal.createDialog(TextInputDialog, {
title: "Enter Code", title: _t("Enter Code"),
description: <div>{msgElements}</div>, description: <div>{msgElements}</div>,
button: "Submit", button: _t("Submit"),
onFinished: (should_verify, token) => { onFinished: (should_verify, token) => {
if (!should_verify) { if (!should_verify) {
this._addThreepid = null; this._addThreepid = null;
@ -147,7 +146,7 @@ export default WithMatrixClient(React.createClass({
return ( return (
<form className="mx_UserSettings_profileTableRow" onSubmit={this._onAddMsisdnSubmit}> <form className="mx_UserSettings_profileTableRow" onSubmit={this._onAddMsisdnSubmit}>
<div className="mx_UserSettings_profileLabelCell"> <div className="mx_UserSettings_profileLabelCell">
<label>Phone</label> <label>{_t('Phone')}</label>
</div> </div>
<div className="mx_UserSettings_profileInputCell"> <div className="mx_UserSettings_profileInputCell">
<div className="mx_UserSettings_phoneSection"> <div className="mx_UserSettings_phoneSection">
@ -159,7 +158,7 @@ export default WithMatrixClient(React.createClass({
<input type="text" <input type="text"
ref={this._collectAddMsisdnInput} ref={this._collectAddMsisdnInput}
className="mx_UserSettings_phoneNumberField" className="mx_UserSettings_phoneNumberField"
placeholder="Add phone number" placeholder={ _t('Add phone number') }
value={this.state.phoneNumber} value={this.state.phoneNumber}
onChange={this._onPhoneNumberChange} onChange={this._onPhoneNumberChange}
/> />

View file

@ -21,6 +21,7 @@ var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal"); var Modal = require("../../../Modal");
var sdk = require("../../../index"); var sdk = require("../../../index");
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'ChangePassword', displayName: 'ChangePassword',
@ -47,11 +48,11 @@ module.exports = React.createClass({
onCheckPassword: function(oldPass, newPass, confirmPass) { onCheckPassword: function(oldPass, newPass, confirmPass) {
if (newPass !== confirmPass) { if (newPass !== confirmPass) {
return { return {
error: "New passwords don't match." error: _t("New passwords don't match") + "."
}; };
} else if (!newPass || newPass.length === 0) { } else if (!newPass || newPass.length === 0) {
return { return {
error: "Passwords can't be empty" error: _t("Passwords can't be empty")
}; };
} }
} }
@ -69,19 +70,21 @@ module.exports = React.createClass({
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: "Warning", title: _t("Warning!"),
description: description:
<div> <div>
Changing password will currently reset any end-to-end encryption keys on all devices, { _t(
making encrypted chat history unreadable, unless you first export your room keys 'Changing password will currently reset any end-to-end encryption keys on all devices, ' +
and re-import them afterwards. 'making encrypted chat history unreadable, unless you first export your room keys ' +
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">will be improved</a>. '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>, </div>,
button: "Continue", button: _t("Continue"),
extraButtons: [ extraButtons: [
<button className="mx_Dialog_primary" <button className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}> onClick={this._onExportE2eKeysClicked}>
Export E2E room keys { _t('Export E2E room keys') }
</button> </button>
], ],
onFinished: (confirmed) => { onFinished: (confirmed) => {
@ -150,7 +153,7 @@ module.exports = React.createClass({
<div className={this.props.className}> <div className={this.props.className}>
<div className={rowClassName}> <div className={rowClassName}>
<div className={rowLabelClassName}> <div className={rowLabelClassName}>
<label htmlFor="passwordold">Current password</label> <label htmlFor="passwordold">{ _t('Current password') }</label>
</div> </div>
<div className={rowInputClassName}> <div className={rowInputClassName}>
<input id="passwordold" type="password" ref="old_input" /> <input id="passwordold" type="password" ref="old_input" />
@ -158,7 +161,7 @@ module.exports = React.createClass({
</div> </div>
<div className={rowClassName}> <div className={rowClassName}>
<div className={rowLabelClassName}> <div className={rowLabelClassName}>
<label htmlFor="password1">New password</label> <label htmlFor="password1">{ _t('New password') }</label>
</div> </div>
<div className={rowInputClassName}> <div className={rowInputClassName}>
<input id="password1" type="password" ref="new_input" /> <input id="password1" type="password" ref="new_input" />
@ -166,7 +169,7 @@ module.exports = React.createClass({
</div> </div>
<div className={rowClassName}> <div className={rowClassName}>
<div className={rowLabelClassName}> <div className={rowLabelClassName}>
<label htmlFor="password2">Confirm password</label> <label htmlFor="password2">{ _t('Confirm password') }</label>
</div> </div>
<div className={rowInputClassName}> <div className={rowInputClassName}>
<input id="password2" type="password" ref="confirm_input" /> <input id="password2" type="password" ref="confirm_input" />
@ -174,7 +177,7 @@ module.exports = React.createClass({
</div> </div>
<AccessibleButton className={buttonClassName} <AccessibleButton className={buttonClassName}
onClick={this.onClickChange}> onClick={this.onClickChange}>
Change Password { _t('Change Password') }
</AccessibleButton> </AccessibleButton>
</div> </div>
); );

View file

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

View file

@ -17,6 +17,7 @@ limitations under the License.
var MatrixClientPeg = require('./MatrixClientPeg'); var MatrixClientPeg = require('./MatrixClientPeg');
var Modal = require('./Modal'); var Modal = require('./Modal');
var sdk = require('./index'); var sdk = require('./index');
import { _t } from './languageHandler';
var dis = require("./dispatcher"); var dis = require("./dispatcher");
var Rooms = require("./Rooms"); var Rooms = require("./Rooms");
@ -43,8 +44,8 @@ function createRoom(opts) {
if (client.isGuest()) { if (client.isGuest()) {
setTimeout(()=>{ setTimeout(()=>{
Modal.createDialog(NeedToRegisterDialog, { Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register", title: _t('Please Register'),
description: "Guest users can't create new rooms. Please register to create room and start a chat." description: _t('Guest users can\'t create new rooms. Please register to create room and start a chat') + '.'
}); });
}, 0); }, 0);
return q(null); return q(null);
@ -104,8 +105,8 @@ function createRoom(opts) {
}, function(err) { }, function(err) {
console.error("Failed to create room " + roomId + " " + err); console.error("Failed to create room " + roomId + " " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failure to create room", title: _t("Failure to create room"),
description: "Server may be unavailable, overloaded, or you hit a bug.", description: _t("Server may be unavailable, overloaded, or you hit a bug") + ".",
}); });
return null; return null;
}); });

View file

@ -0,0 +1 @@
{}

215
src/i18n/strings/da.json Normal file
View file

@ -0,0 +1,215 @@
{
"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",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar.": "Kan ikke oprette forbindelse til hjemmeserver via HTTP, når en HTTPS-URL er i din browserbjælke.",
"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 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"
}

723
src/i18n/strings/de_DE.json Normal file
View file

@ -0,0 +1,723 @@
{
"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": "Historisch",
"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 Identity 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 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": "Suche in DuckDuckGo nach Ergebnissen",
"Commands": "Kommandos",
"Emoji": "Smileys",
"Sorry, this homeserver is using a login which is not recognised ": "Entschuldigung, dieser homeserver nutzt eine Anmeldetechnik die nicht bekannt ist ",
"Login as guest": "Anmelden als Gast",
"Return to app": "Zurück zur Anwendung",
"Sign in": "Anmelden",
"Create a new account": "Erstelle einen neuen Benutzer",
"Send an encrypted message": "Sende eine verschlüsselte Nachricht",
"Send a message (unencrypted)": "Sende eine Nachricht (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": "Füge E-Mail-Adresse hinzu",
"Advanced": "Erweitert",
"all room members, from the point they joined": "Alle Raum-Mitglieder - seitdem 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": "Jeder der den Raum-Link kennt - abgesehen von Gästen",
"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 upload the following files?": "Bist du sicher, dass du die folgenden Dateien hochladen willst?",
"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 bzgl. wer die Historie lesen kann betrifft nur zukünftige Nachrichten in diesem Raum",
"Clear Cache and Reload": "Leere Cache und lade neu",
"Click here": "Klicke hier",
"Confirm your new password": "Bestätige dein neues Passwort",
"Continue": "Fortfahren",
"Create an account": "Erstelle einen Account",
"Create Room": "Erstelle Raum",
"Cryptography": "Kryptografie",
"Deactivate Account": "Deaktiviere Account",
"Deactivate my account": "Deaktiviere meinen Account",
"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, die Historie vor dem Beitritt in den Raum 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": "Ende-zu-Ende-Verschlüsselung ist im Beta-Status und ist evtl. nicht zuverlässig",
"Failed to send email": "Fehler beim Senden der E-Mail",
"Account": "Konto",
"Add phone number": "Füge Telefonnummer hinzu",
"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 keine Benachrichtigungen an anderen Geräten empfangen bis du dich dort erneut anmeldest",
"all room members": "Alle Raum-Mitglieder",
"all room members, from the point they are invited": "Alle Raum-Mitglieder - seitdem sie eingeladen wurden",
"answered the call.": "beantwortete den Anruf.",
"Can't load user settings": "Kann Nutzereinstellungen nicht laden",
"changed name": "änderte Namen",
"changed the power level of": "änderte Berechtigungslevel von",
"Clear Cache": "Leere Cache",
"Click here to fix": "Klicke hier zum reparieren",
"*️⃣ Commands": "*️⃣ Befehle",
"Default": "Standard",
"demote": "Zum zurückstufen",
"Export E2E room keys": "Exportiere E2E-Raum-Schlüssel",
"Failed to change password. Is your password correct?": "Passwort-Änderung schlug fehl. Ist dein Passwort korrekt?",
"Failed to forget room": "Vergessen des Raums schlug fehl",
"Failed to leave room": "Fehler beim Verlassen des Raums",
"Failed to reject invitation": "Fehler beim Abweisen der Einladung",
"Failed to set avatar.": "Fehler beim Setzen des Avatars.",
"Failed to unban": "Entbannen fehlgeschlagen",
"Failed to upload file": "Dateiupload fehlgeschlagen",
"Favourite": "Favorit",
"favourite": "Favoriten",
"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üsselungsschlüssel von diesem Browser gelöscht. Wenn du in späteren Riot-Sitzungen die Konversationshistorie entschlüsseln möchtest, exportiere bitte deine Schlüssel zur sicheren Verwahrung.",
"For security, this session has been signed out. Please sign in again": "Zur Sicherheit wurde diese Sitzung abgemeldet. 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 werden",
"Guests can't set avatars. Please register.": "Gäste können keine Avatare setzen. Bitte registriere dich.",
"Guest users can't upload files. Please register to upload.": "Gäste können keine Dateien hochladen. Bitte registrieren um hochzuladen.",
"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": "Importe E2E-Raum-Schlüssel",
"Invalid Email Address": "Ungültige E-Mail-Adresse",
"invited": "eingeladen",
"Invite new room members": "Lade neue Raum-Mitglieder ein",
"is a": "ist ein",
"is trusted": "wird vertraut",
"I want to sign in with": "Ich möchte mich anmelden mit",
"joined and left": "trat bei und ging",
"joined": "trat bei",
"joined the room": "trat dem Raum bei",
"Leave room": "Verlasse Raum",
"left and rejoined": "ging(en) und trat(en) erneut bei",
"left": "ging",
"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": "Verwalte Integrationen",
"Members only": "Nur Mitglieder",
"Mobile phone number": "Mobile Telefonnummer",
"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": "Keine Nutzer haben 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": "Lade eine(n) Freund(in) zu Riot ein",
"rejected": "abgeleht",
"Once you&#39;ve followed the link it contains, click below": "Nachdem du dem Link gefolgt bist, klicke unten",
"rejected the invitation.": "lehnte die Einladung ab.",
"Reject invitation": "Einladung ablehnen",
"Remove Contact Information?": "Lösche Kontakt-Informationen?",
"removed their display name": "löschte den eigenen Anzeigenamen",
"Remove": "Entferne",
"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 Anmeldung zurückkehren",
"Room Colour": "Raumfarbe",
"Room name (optional)": "Raumname (optional)",
"Scroll to unread messages": "Scrolle zu ungelesenen Nachrichten",
"Send Invites": "Sende Einladungen",
"Send Reset Email": "Sende Rücksetz-E-mail",
"sent an image": "sandte ein Bild",
"sent an invitation to": "sandte eine Einladung an",
"sent a video": "sandte ein Video",
"Server may be unavailable or overloaded": "Server könnte nicht verfügbar oder überlastet sein",
"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": "seitdem diese Option gewählt wird",
"since they joined": "seitdem sie beitraten",
"since they were invited": "seitdem sie eingeladen wurden",
"Someone": "Jemand",
"Start a chat": "Starte einen Chat",
"Start Chat": "Starte Chat",
"Success": "Erfolg",
"tag direct chat": "Zum kennzeichnen als direkten Chat",
"The default role for new room members is": "Die Standard-Rolle for 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 existenten Historie bleibt unverändert",
"This doesn't appear to be a valid email address": "Die scheint keine valide 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": "Dieser Raum ist über entfernte Matrix-Server nicht zugreifbar",
"This room's internal ID is": "Die interne ID dieses Raumes ist",
"To ban users": "Zum Nutzer bannen",
"To configure the room": "Zum Raum konfigurieren",
"To invite users into the room": "Zum Nutzer in den Raum einladen",
"to join the discussion": "Zum mitdiskutieren",
"To kick users": "Zum Nutzer kicken",
"Admin": "Administrator",
"Server may be unavailable, overloaded, or you hit a bug": "Server könnte nicht verfügbar oder überlastet sein oder du bist auf einen Fehler gestoßen",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar": "Verbindung zum Homeserver ist über HTTP nicht möglich, wenn HTTPS in deiner Browserleiste steht",
"Could not connect to the integration server": "Konnte keine Verbindung zum Integrations-Server herstellen",
"Disable inline URL previews by default": "Deaktiviere URL-Vorschau im Chat standardmäßig",
"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, gebe deine E-Mail-Adresse, die mit deinem Account verbunden ist, 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": "Unfähig die E-Mail-Adresse hinzuzufügen",
"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": "Avatar hochladen",
"uploaded a file": "lud eine Datei hoch",
"Upload Files": "Dateien hochladen",
"Upload file": "Datei hochladen",
"User Interface": "Nutzerschnittstelle",
"User name": "Nutzername",
"Users": "Nutzer",
"User": "Nutzer",
"Verification Pending": "Verifizierung ausstehend",
"Video call": "Videoanruf",
"Voice call": "Sprachanruf",
"VoIP conference finished": "VoIP-Konferenz beendet",
"VoIP conference started": "VoIP-Konferenz gestartet",
"(warning: cannot be disabled again!)": "(Warnung: Kann nicht wieder deaktiviert werden!)",
"was banned": "wurde gebannt",
"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 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 are trying to access": "Du möchtest Zugang zu %(sth)s bekommen",
"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": "Du wurdest in diesen Raum eingeladen von %(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": "Du wurdest von allen Geräten ausgeloggt und wirst keine Push-Benachrichtigungen mehr bekommen. Um Benachrichtigungen zu reaktivieren melde dich auf jedem Gerät neu an",
"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 sind in Entwicklung und evtl. nicht zuverlässig",
"Conference calls are not supported in encrypted rooms": "Konferenzgespräche sind in verschlüsselten Räumen nicht unterstützt",
"Conference calls are not supported in this client": "Konferenzgespräche sind in diesem Client nicht unterstützt",
"Existing Call": "Existierender Anruf",
"Failed to set up conference call": "Aufbau des Konferenzgesprächs fehlgeschlagen",
"Failed to verify email address: make sure you clicked the link in the email": "Verifizierung der E-Mail-Adresse fehlgeschlagen: Stelle sicher, dass du den Link in der E-Mail anklicktest",
"Failure to create room": "Raumerstellung fehlgeschlagen",
"Guest users can't create new rooms. Please register to create room and start a chat": "Gäste können keine neuen Räume erstellen. Bitte registrieren um einen Raum zu erstellen und einen Chat 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 wurde nicht gefunden",
"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": "Frühere Sitzung nicht wiederherstellbar",
"Unable to capture screen": "Unfähig den Bildschirm aufzunehmen",
"Unable to enable Notifications": "Unfähig Benachrichtigungen zu aktivieren",
"Upload Failed": "Upload fehlgeschlagen",
"VoIP is unsupported": "VoIP ist nicht unterstützt",
"You are already in a call": "Du bist bereits bei einem Anruf",
"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 kein VoIP-Gespräch 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üsselungscodes für dieses Gerät zu generieren und den öffentl. Schlüssel an deinen Homeserver zu senden. Dies muss einmal gemacht werden. Entschuldige die Unannehmlichkeit",
"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 Homeserver verknüpft 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:": "Setze einen Anzeigenamen:",
"Upload an avatar:": "Lade einen Avatar hoch:",
"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 passen nicht zusammen.",
"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 sieht nicht nach einer validen E-Mail-Adresse aus.",
"This doesn't look like a valid phone number.": "Dies sieht nicht nach einer validen Telefonnummer aus.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "Benutzernamen sollen nur Buchstaben, Nummern, Binde- und Unterstriche enthalten.",
"An unknown error occurred.": "Ein unbekannter Fehler trat auf.",
"I already have an account": "Ich habe bereits einen Account",
"An error occured: %(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": "Teile Nachrichtenhistorie mit neuen Nutzern",
"Encrypt room": "Entschlüssele Raum",
"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 akzeptierte eine Einladung",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s akzeptierte eine 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 tippen",
"%(senderName)s answered the call.": "%(senderName)s beantwortete den Anruf.",
"%(senderName)s banned %(targetName)s.": "%(senderName)s bannte %(targetName)s.",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s": "%(senderName)s änderte den Anzeigenamen von %(oldDisplayName)s zu %(displayName)s",
"%(senderName)s changed their profile picture": "%(senderName)s änderte das Profilbild",
"%(senderName)s changed the power level of %(powerLevelDiffText)s": "%(senderName)s änderte das Berechtigungslevel von %(powerLevelDiffText)s",
"%(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 änderte das Thema zu \"%(topic)s\"",
"/ddg is not a command": "/ddg ist kein Kommando",
"%(senderName)s ended the call": "%(senderName)s beendete den Anruf",
"Failed to lookup current room": "Aktuellen Raum nachzuschlagen schlug fehl",
"Failed to send request.": "Anfrage zu senden schlug fehl.",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s von %(fromPowerLevel)s zu %(toPowerLevel)s",
"%(senderName)s invited %(targetName)s.": "%(senderName)s lud %(targetName)s ein.",
"%(displayName)s is typing": "%(displayName)s tippt",
"%(targetName)s joined the room.": "%(targetName)s trat dem Raum bei.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s kickte %(targetName)s.",
"%(targetName)s left the room.": "%(targetName)s verließ den Raum.",
"%(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)": "(nicht von diesem Browser 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 Zahl sein.",
"Reason": "Grund",
"%(targetName)s rejected the invitation.": "%(targetName)s lehnte die Einladung ab.",
"%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s löschte den Anzeigenamen (%(oldDisplayName)s)",
"%(senderName)s removed their profile picture": "%(senderName)s löschte das Profilbild",
"%(senderName)s requested a VoIP conference": "%(senderName)s fragte nach einer VoIP-Konferenz",
"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 sandte eine Einladung an %(targetDisplayName)s um diesem Raum beizutreten.",
"%(senderName)s set a profile picture": "%(senderName)s setzte ein Profilbild",
"%(senderName)s set their display name to %(displayName)s": "%(senderName)s setzte den Anzeigenamen zu %(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 dies zu nutzen, warte auf die Autovervollständigungsergebnisse und benutze die TAB Taste.",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s)": "%(senderName)s schaltete Ende-zu-Ende-Verschlüsselung ein (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 benutzen",
"%(senderName)s withdrew %(targetName)s's inivitation.": "%(senderName)s zog die Einladung für %(targetName)s zurück.",
"You need to be able to invite users to do that.": "Du musst in der Lage sein Nutzer einzuladen um dies zu tun.",
"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 untergebrochen.",
"Sent messages will be stored until your connection has returned.": "Gesendete Nachrichten werden gespeichert bis die Verbindung wiederhergestellt wurde.",
"Auto-complete": "Autovervollständigung",
"Resend all": "Alles 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 Verzeichnis anzusehen",
"to demote": "um die Priorität 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 redact other users' messages": "Um Nachrichten anderer zu verbergen",
"You are trying to access %(roomName)s": "Du versuchst auf %(roomName)s zuzugreifen",
"af": "Afrikaans",
"ar-ae": "Arabisch (U.A.E.)",
"ar-bh": "Arabisch (Bahrain)",
"ar-dz": "Arabisch (Algeria)",
"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 (Qatar)",
"ar-sa": "Arabisch (Saudi Arabien)",
"ar-sy": "Arabisch (Syrien)",
"ar-tn": "Arabisch (Tunisien)",
"ar-ye": "Arabisch (Yemen)",
"be": "Weißrussisch",
"bg": "Bulgarisch",
"cs": "Tschechisch",
"de-at": "Deutsch (Österreich)",
"de-ch": "Deutsch (Schweiz)",
"de-li": "Deutsch (Liechtenstein)",
"de-lu": "Deutsch (Luxemburg)",
"el": "Griechisch",
"en-au": "Englisch (Australien)",
"en-bz": "Englisch (Belize)",
"en-ca": "Englisch (Kanada)",
"en-gb": "Englisch (Vereintes 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üd-Afrika)",
"es-ar": "Spanisch (Argentinien)",
"es-bo": "Spanisch (Bolivien)",
"es-cl": "Spanisch (Chile)",
"es-co": "Spanisch (Kolombien)",
"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 (Mexico)",
"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 (Johab)",
"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 von Moldavien)",
"ro": "Romanian",
"ru-mo": "Russisch",
"sb": "Sorbian",
"sk": "Slowakisch",
"sl": "Slowenisch",
"sq": "Albanisch",
"sr": "Serbisch (lateinisch)",
"sv-fi": "Schwedisch (Finnland)",
"sv": "Schwedisch",
"sx": "Sutu",
"sz": "Samisch (Lappish)",
"th": "Thailändisch",
"tn": "Setswana",
"tr": "Türkisch",
"ts": "Tsonga",
"uk": "Ukrainisch",
"ur": "Urdu",
"ve": "Tshivenda",
"vi": "Vietnamesisch",
"zh-cn": "Chinesisch (PRC)",
"zh-hk": "Chinesisch (Hong Kong SAR)",
"zh-sg": "Chinesisch (Singapur)",
"zh-tw": "Chinesisch (Taiwan)",
"zu": "Zulu",
"ca": "katalanisch",
"fi": "Finnish",
"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": "Banne",
"Can't connect to homeserver - please check your connectivity and ensure your %(urlStart)s homeserver's SSL certificate %(urlEnd)s is trusted": "Kann nicht zum Heimserver verbinden - bitte checke eine Verbindung und stelle sicher, dass dem %(urlStart)s SSL-Zertifikat deines Heimservers %(urlEnd)s vertraut wird",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s": "Kann nicht zum Heimserver via HTTP verbinden, wenn eine HTTPS-Url in deiner Adresszeile steht. Nutzer HTTPS oder %(urlStart)s aktiviere unsichere Skripte %(urlEnd)s",
"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": "Klicke um das Video stumm zu stellen",
"Command error": "Befehlsfehler",
"Decrypt %(text)s": "Entschlüssele %(text)s",
"Delete": "Lösche",
"Devices": "Geräte",
"Direct chats": "Direkte Chats",
"Disinvite": "Ausladen",
"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": "Raumbeitritt fehlgeschlagen",
"Failed to kick": "Kicken fehlgeschlagen",
"Failed to mute user": "Nutzer lautlos zu stellen fehlgeschlagen",
"Failed to reject invite": "Einladung abzulehnen fehlgeschlagen",
"Failed to save settings": "Einstellungen speichern fehlgeschlagen",
"Failed to set display name": "Anzeigenamen zu ändern fehlgeschlagen",
"Fill screen": "Fülle Bildschirm",
"Guest users can't upload files. Please register to upload": "Gäste können Dateien nicht hochlagen. Bitte registrieren um hochzuladen",
"Hide Text Formatting Toolbar": "Verberge Text-Formatierungs-Toolbar",
"Incorrect verification code": "Verifikationscode inkorrekt",
"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 valides Adressformat",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' hat kein valides Aliasformat",
"Join Room": "Raum beitreten",
"Kick": "Kicke",
"Level": "Level",
"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üsselungsschlüsseln",
"No more results": "Keine weiteren Ergebnisse",
"No results": "Keine Ergebnisse",
"OK": "OK",
"Revoke Moderator": "Moderator zurückziehen",
"Search": "Suche",
"Search failed": "Suche fehlgeschlagen",
"Server error": "Serverfehler",
"Server may be unavailable, overloaded, or search timed out :(": "Server ist entweder nicht verfügbar, überlastet oder die Suchezeit ist abgelaufen :(",
"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 ist entweder nicht verfügbar, überlastet oder etwas anderes schlug fehl",
"Some of your messages have not been sent": "Einige deiner Nachrichten wurden noch 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 zugreifbar. Du kannst evtl. zugreifen, 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": "Versuchte einen spezifischen Punkt in der Raum-Chronik zu laden, aber er konnte nicht gefunden werden",
"Turn Markdown off": "Markdown abschalten",
"Turn Markdown on": "Markdown einschalten",
"Unable to load device list": "Laden der Geräteliste nicht möglich",
"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 nicht in der Lage sein, diese Änderung zu ändern da den Nutzer auf dasselbe Berechtigungslevel wie du hebst",
"Make Moderator": "Mache zum Moderator",
"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": "Klicke um Video zu reaktivieren",
"Click to unmute audio": "Klicke um Ton zu reaktivieren",
"Failed to load timeline position": "Laden der Position im Zeitstrahl fehlgeschlagen",
"Failed to toggle moderator status": "Umschalten des Moderatorstatus fehlgeschlagen",
"Enable encryption": "Verschlüsselung aktivieren",
"The main address for this room is": "Die Hauptadresse für diesen Raum ist",
"Autoplay GIFs and videos": "Automatisch GIF's und Videos abspielen",
"Don't send typing notifications": "Nicht senden, wenn ich tippe",
"Hide read receipts": "Verberge Lesebestätigungen",
"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)strat(en) %(repeats)s mal bei",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)strat %(repeats)s mal bei",
"%(severalUsers)sjoined": "%(severalUsers)straten bei",
"%(oneUser)sjoined": "%(oneUser)strat bei",
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sgingen %(repeats)s mal",
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sging %(repeats)s mal",
"%(severalUsers)sleft": "%(severalUsers)sgingen",
"%(oneUser)sleft": "%(oneUser)sging",
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)straten bei und gingen %(repeats)s mal",
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)strat bei und ging %(repeats)s mal",
"%(severalUsers)sjoined and left": "%(severalUsers)straten bei und gingen",
"%(oneUser)sjoined and left": "%(oneUser)strat bei und ging",
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)s gingen und traten erneut bei - %(repeats)s mal",
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sging und trat %(repeats)s mal erneut bei",
"%(severalUsers)sleft and rejoined": "%(severalUsers)s gingen und traten erneut bei",
"%(oneUser)sleft left and rejoined": "%(oneUser)sging und trat erneut bei",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)s lehnten %(repeats)s mal ihre Einladung ab",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)s lehnte seine/ihre Einladung %(repeats)s mal ab",
"%(severalUsers)srejected their invitations": "%(severalUsers)slehnten ihre Einladung ab",
"%(oneUser)srejected their invitation": "%(oneUser)slehnte seine/ihre Einladung ab",
"%(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 gebannt",
"was banned %(repeats)s times": "wurde %(repeats)s mal gebannt",
"were banned": "wurden gebannt",
"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 gekickt",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)sänderten %(repeats)s mal ihre Namen",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)sänderte %(repeats)s mal seinen/ihren Namen",
"%(severalUsers)schanged their name": "%(severalUsers)sänderten ihre Namen",
"%(oneUser)schanged their name": "%(oneUser)sänderte seinen/ihren Namen",
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)sänderten %(repeats)s mal ihren Avatar",
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)sänderte %(repeats)s mal seinen/ihren Avatar",
"%(severalUsers)schanged their avatar": "%(severalUsers)sänderten ihre Avatare",
"%(oneUser)schanged their avatar": "%(oneUser)sänderte seinen/ihren Avatar",
"%(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)s ging und trat erneut bei",
"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": "Immer Nachrichten-Zeitstempel anzeigen",
"Authentication": "Authentifikation",
"An error has occurred.": "Ein Fehler passierte.",
"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": "Neue Passwörter nicht gleich",
"olm version:": "Version von olm:",
"Passwords can't be empty": "Passwörter dürfen nicht leer sein",
"Registration required": "Registrierung benötigt",
"Report it": "Melde es",
"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)": "Zeige Zeitstempel im 12-Stunden format",
"to tag as %(tagName)s": "um als \"%(tagName)s\" zu markieren"
}

667
src/i18n/strings/en_EN.json Normal file
View file

@ -0,0 +1,667 @@
{
"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",
"ko":"Korean (Johab)",
"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 (Cyrillic)",
"sr":"Serbian (Latin)",
"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 email address": "Add email address",
"Add phone number": "Add phone number",
"Admin": "Admin",
"Advanced": "Advanced",
"Algorithm": "Algorithm",
"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 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 reject the invitation?": "Are you sure you want to reject the invitation?",
"Are you sure you want upload the following files?": "Are you sure you want 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 %(urlStart)s homeserver's SSL certificate %(urlEnd)s is trusted": "Can't connect to homeserver - please check your connectivity and ensure your %(urlStart)s homeserver's SSL certificate %(urlEnd)s is trusted",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s",
"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 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",
"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",
"/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",
"Delete": "Delete",
"demote": "demote",
"Deops user with given id": "Deops user with given id",
"Device ID": "Device ID",
"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 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, name or matrix ID": "Email, name or matrix ID",
"Emoji": "Emoji",
"Enable encryption": "Enable encryption",
"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",
"Event information": "Event information",
"Existing Call": "Existing Call",
"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 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 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": "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",
"Found a bug?": "Found a bug?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s",
"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",
"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 E2E room keys": "Import E2E room keys",
"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",
"%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.",
"Invite new room members": "Invite new room members",
"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",
"I want to sign in with": "I want to 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",
"%(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",
"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",
"Moderator": "Moderator",
"Must be viewing a room": "Must be viewing a room",
"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",
"or": "or",
"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",
"Registration required": "Registration required",
"rejected": "rejected",
"%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.",
"Reject invitation": "Reject invitation",
"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",
"%(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",
"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 name (optional)": "Room name (optional)",
"Rooms": "Rooms",
"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",
"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 recognised ",
"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",
"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",
"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 room has no local addresses": "This room has no local addresses",
"This room is not recognised.": "This room is not recognised.",
"This room is private or inaccessible to guests. You may be able to join if you register": "This room is private or inaccessible to guests. You may be able to join if you register",
"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 favourite",
"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 redact other users' messages": "To redact 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": "unknown",
"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 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",
"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!",
"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 inivitation.": "%(senderName)s withdrew %(targetName)s's inivitation.",
"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 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 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",
"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:",
"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 occured: %(error_string)s": "An error occured: %(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",
"Room name (optional)": "Room name (optional)",
"Who would you like to add to this room?": "Who would you like to add to this 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"
}

385
src/i18n/strings/fr.json Normal file
View file

@ -0,0 +1,385 @@
{
"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 (Johab)",
"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 (Latin)",
"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.": "anyone",
"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 or 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": "Event information",
"Existing Call": "Existing Call",
"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": "Echec lors de l'oublie du salon %(errCode)s",
"Please Register": "Veuillez vous enregistrer",
"Remove": "Supprimer",
"was banned": "banned",
"was invited": "invited",
"was kicked": "kicked",
"was unbanned": "unbanned",
"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",
"%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s a accepté une invitation de %(displayName)s",
"Account": "Compte",
"Add email address": "Ajouter une adresse e-mail",
"Add phone number": "Ajouter un numéro de téléphone",
"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é.",
"%(senderName)s answered the call": "%(senderName)s a répondu à lappel",
"Anyone who knows the room's link, apart from guests": "Tout ceux qui connaissent le lien du salon, à part les invités",
"Anyone who knows the room's link, including guests": "Tout ceux qui connaissent le lien du salon, y compris les invités",
"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 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 %(urlStart)s homeserver's SSL certificate %(urlEnd)s is trusted": "Connexion au Home Server impossible - merci de vérifier votre connectivité et que le %(urlStart)s certificat SSL de votre Home Server %(urlEnd)s est de confiance",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s": "Impossible de se connecter au homeserver en HTTP si l'URL dans la barre de votre explorateur est en HTTPS. Utilisez HTTPS ou %(urlStart)s activez le support des scripts non-vérifiés %(urlEnd)s",
"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 recharger",
"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": "Désactiver le compte",
"Deactivate my account": "Désactiver 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": "ID 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": "Échec lors de lenvoi 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.",
"%(senderName)s banned %(targetName)s.": "%(senderName)s a banni %(targetName)s.",
"Email": "E-mail",
"Failed to send request.": "Erreur lors de l'envoi de la requête.",
"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 utilisateurs invités 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 utilisateurs invités 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",
"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",
"I want to 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": "Identifié en tant que qu'invité",
"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 Matrix ID",
"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": "Personne",
"Permissions": "Permissions",
"Phone": "Téléphone"
}

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

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

503
src/i18n/strings/pt.json Normal file
View file

@ -0,0 +1,503 @@
{
"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": "todos os membros da sala",
"an address": "um endereço",
"and": "e",
"An email has been sent to": "Um email foi enviado para",
"A new password must be entered.": "Uma nova senha precisa ser informada.",
"answered the call.": "respondeu à chamada.",
"anyone": "qualquer um",
"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 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",
"changed the topic to": "mudou o tópico para",
"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 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",
"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 membros de 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 novo membros para 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)",
"I want to 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",
"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 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": "Servidor pode estar indisponível, sobrecarregado ou aconteceu um erro de execução",
"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?": "Quem Você gostaria que fosse adicionado 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",
"%(senderDisplayName)s changed the topic to \"%(topic)s\"": "%(senderDisplayName)s alterou o tópico para \"%(topic)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",
"Refer a friend to Riot": "Recomende Riot a um/a amigo/a",
"%(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 redact 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 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 inivitation.": "%(senderName)s desfez o convite a %(targetName)s'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 occured: %(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)",
"ar-eg": "Árabe (Egipto)",
"ar-tn": "Árabe (Tunisia)",
"be": "Bielorusso",
"bg": "Búlgaro",
"ca": "Catalão",
"cs": "Checo",
"de-at": "Alemao (Austria)",
"el": "Grego",
"en-au": "Inglês (Austrália)",
"en-ca": "Inglês (Canadá)",
"en-ie": "Inglês (Irlanda)",
"en-nz": "Inglês (Nova Zelândia)",
"en-us": "Inglês (Estados Unidos)",
"es-ar": "Espanhol (Argentina)",
"es-mx": "Espanhol (Mexico)",
"es-py": "Espanhol (Paraguai)",
"es": "Espanhol (Espanha)",
"et": "Estoniano",
"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": "Hindi",
"hr": "Croata",
"hu": "Húngaro",
"id": "Indonésio",
"is": "Islandês",
"it": "Italiano",
"ja": "Japonês",
"ji": "iídiche",
"lt": "Lituano",
"lv": "Letão",
"mt": "Maltês",
"nl-be": "Holandês (Bélgica)",
"nl": "Holandês",
"no": "Norueguês",
"pl": "Polaco",
"pt": "Português",
"ro": "Romeno",
"sk": "Eslovaco",
"sl": "Esloveno",
"sq": "Albanês",
"sr": "Sérvio (Latim)",
"sv": "Sueco",
"th": "Tailandês",
"tn": "tswana",
"tr": "Turco",
"ts": "tsonga",
"uk": "Ucraniano",
"ur": "urdu",
"ve": "venda",
"vi": "Vietnamita",
"xh": "xosa",
"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",
"Sunday": "Domingo",
"Monday": "Segunda",
"Tuesday": "Terça",
"Wednesday": "Quarta",
"Thursday": "Quinta",
"Friday": "Sexta",
"Saturday": "Sábado"
}

705
src/i18n/strings/pt_BR.json Normal file
View file

@ -0,0 +1,705 @@
{
"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": "qualquer um",
"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 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)",
"I want to 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": "Servidor pode estar indisponível, sobrecarregado ou aconteceu um erro de execução",
"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",
"Refer a friend to Riot": "Recomende Riot a um/a amigo/a",
"%(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 redact 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 inivitation.": "%(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 occured: %(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 (latino)",
"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 (Johab)",
"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 %(urlStart)s homeserver's SSL certificate %(urlEnd)s is trusted": "Não consigo conectar ao servidor padrão - favor checar sua conexão à internet e verificar se o certificado SSL do seu %(urlStart)s servidor padrão %(urlEnd)s é confiável",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s": "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 %(urlStart)s habilite scripts não seguros no seu navegador %(urlEnd)s",
"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",
"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": "Localizar",
"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",
"This room is private or inaccessible to guests. You may be able to join if you register": "Esta sala é privada ou inacessível para visitantes. Você poderá ingressar nela se registrar-se",
"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"
}

655
src/i18n/strings/ru.json Normal file
View file

@ -0,0 +1,655 @@
{
"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 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": "является",
"I want to 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 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 occured: %(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 inivitation.": "%(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, %(monthName)s %(day)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.": "Для его использования, просто подождите результатов автозаполнения для загрузки на вкладке и через них.",
"%(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": "Корейский (Johab)",
"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...": "и %(overflowCounts)s других...",
"Are you sure?": "Вы уверены?",
"Autoplay GIFs and videos": "Проигрывать GIF и видео автоматически",
"Can't connect to homeserver - please check your connectivity and ensure your %(urlStart)s homeserver's SSL certificate %(urlEnd)s is trusted": "Невозможно соединиться с домашним сервером - проверьте своё соединение и убедитесь, что %(urlStart)s SSL-сертификат вашего домашнего сервера %(urlEnd)s включён в доверяемые",
"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": "Войти в комнату",
"%(targetName)s joined the room.": "%(targetName)s вошёл в комнату.",
"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.": "%(senderDisplayName)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.": "Эта комната не опознана.",
"This room is private or inaccessible to guests. You may be able to join if you register": "Эта комната личная или недоступна для гостей. Мы может быть войдёте, если зарегистрируйтесь",
"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, %(monthName)s %(day)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изменил своё изображение"
}

View file

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

163
src/languageHandler.js Normal file
View file

@ -0,0 +1,163 @@
/*
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) {
return counterpart.translate(...args);
}
// Allow overriding the text displayed when no translation exists
// Currently only use din 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) {
throw new Error("Unable to find an appropriate language");
}
return getLanguage(i18nFolder + availLangs[langToUse]);
}).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']);
}
}).then((langData) => {
if (langData) counterpart.registerTranslations('en', langData);
});
};
export function getAllLanguageKeysFromJson() {
return getLangsJson().then((langs) => {
return Object.keys(langs);
});
}
export function getLanguagesFromBrowser() {
if (navigator.languages) 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

@ -22,7 +22,7 @@ import ReactTestUtils from 'react-addons-test-utils';
import sinon from 'sinon'; import sinon from 'sinon';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import MatrixClientPeg from 'MatrixClientPeg'; import MatrixClientPeg from '../../../../src/MatrixClientPeg';
import * as test_utils from '../../../test-utils'; import * as test_utils from '../../../test-utils';

View file

@ -4,6 +4,7 @@ const ReactDOM = require("react-dom");
const ReactTestUtils = require('react-addons-test-utils'); const ReactTestUtils = require('react-addons-test-utils');
const sdk = require('matrix-react-sdk'); const sdk = require('matrix-react-sdk');
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary'); const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
import * as languageHandler from '../../../../src/languageHandler';
const testUtils = require('../../../test-utils'); const testUtils = require('../../../test-utils');
describe('MemberEventListSummary', function() { describe('MemberEventListSummary', function() {
@ -82,9 +83,11 @@ describe('MemberEventListSummary', function() {
return eventsForUsers; return eventsForUsers;
}; };
beforeEach(function() { beforeEach(function(done) {
testUtils.beforeEach(this); testUtils.beforeEach(this);
sandbox = testUtils.stubClient(); sandbox = testUtils.stubClient();
languageHandler.setLanguage('en').done(done);
}); });
afterEach(function() { afterEach(function() {
@ -356,7 +359,7 @@ describe('MemberEventListSummary', function() {
const summaryText = summary.innerText; const summaryText = summary.innerText;
expect(summaryText).toBe( expect(summaryText).toBe(
"user_1 and 1 other were unbanned, joined and left 2 times and were banned" "user_1 and one other were unbanned, joined and left 2 times and were banned"
); );
}); });
@ -559,7 +562,7 @@ describe('MemberEventListSummary', function() {
const summaryText = summary.innerText; const summaryText = summary.innerText;
expect(summaryText).toBe( expect(summaryText).toBe(
"user_1 and 1 other rejected their invitations and " + "user_1 and one other rejected their invitations and " +
"had their invitations withdrawn" "had their invitations withdrawn"
); );
}); });
@ -595,7 +598,7 @@ describe('MemberEventListSummary', function() {
const summaryText = summary.innerText; const summaryText = summary.innerText;
expect(summaryText).toBe( expect(summaryText).toBe(
"user_1 rejected their invitations 2 times" "user_1 rejected their invitation 2 times"
); );
}); });
@ -650,7 +653,7 @@ describe('MemberEventListSummary', function() {
const summaryText = summary.innerText; const summaryText = summary.innerText;
expect(summaryText).toBe( expect(summaryText).toBe(
"user_1, user_2 and 1 other joined" "user_1, user_2 and one other joined"
); );
}); });

View file

@ -8,7 +8,7 @@ import * as testUtils from '../../../test-utils';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import UserSettingsStore from '../../../../src/UserSettingsStore'; import UserSettingsStore from '../../../../src/UserSettingsStore';
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput'); const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
import MatrixClientPeg from 'MatrixClientPeg'; import MatrixClientPeg from '../../../../src/MatrixClientPeg';
function addTextToDraft(text) { function addTextToDraft(text) {
const components = document.getElementsByClassName('public-DraftEditor-content'); const components = document.getElementsByClassName('public-DraftEditor-content');

3
test/i18n/languages.json Normal file
View file

@ -0,0 +1,3 @@
{
"en": "en_EN.json"
}

View file

@ -16,7 +16,7 @@ limitations under the License.
"use strict"; "use strict";
import * as MegolmExportEncryption from 'utils/MegolmExportEncryption'; import * as MegolmExportEncryption from '../../src/utils/MegolmExportEncryption';
import * as testUtils from '../test-utils'; import * as testUtils from '../test-utils';
import expect from 'expect'; import expect from 'expect';