Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into set_default_federate_by_settings

This commit is contained in:
Michael Telatynski 2017-10-04 22:35:29 +01:00
commit 7492f2dffa
No known key found for this signature in database
GPG key ID: 0435A1D4BBD34D64
149 changed files with 7702 additions and 5253 deletions

View file

@ -6,7 +6,6 @@ src/autocomplete/Autocompleter.js
src/autocomplete/Components.js src/autocomplete/Components.js
src/autocomplete/DuckDuckGoProvider.js src/autocomplete/DuckDuckGoProvider.js
src/autocomplete/EmojiProvider.js src/autocomplete/EmojiProvider.js
src/autocomplete/RoomProvider.js
src/autocomplete/UserProvider.js src/autocomplete/UserProvider.js
src/CallHandler.js src/CallHandler.js
src/component-index.js src/component-index.js
@ -35,7 +34,6 @@ src/components/views/create_room/RoomAlias.js
src/components/views/dialogs/ChatCreateOrReuseDialog.js src/components/views/dialogs/ChatCreateOrReuseDialog.js
src/components/views/dialogs/DeactivateAccountDialog.js src/components/views/dialogs/DeactivateAccountDialog.js
src/components/views/dialogs/InteractiveAuthDialog.js src/components/views/dialogs/InteractiveAuthDialog.js
src/components/views/dialogs/SetMxIdDialog.js
src/components/views/dialogs/UnknownDeviceDialog.js src/components/views/dialogs/UnknownDeviceDialog.js
src/components/views/elements/AccessibleButton.js src/components/views/elements/AccessibleButton.js
src/components/views/elements/ActionButton.js src/components/views/elements/ActionButton.js
@ -56,7 +54,6 @@ src/components/views/elements/RoomDirectoryButton.js
src/components/views/elements/SettingsButton.js src/components/views/elements/SettingsButton.js
src/components/views/elements/StartChatButton.js src/components/views/elements/StartChatButton.js
src/components/views/elements/TintableSvg.js src/components/views/elements/TintableSvg.js
src/components/views/elements/TruncatedList.js
src/components/views/elements/UserSelector.js src/components/views/elements/UserSelector.js
src/components/views/login/CaptchaForm.js src/components/views/login/CaptchaForm.js
src/components/views/login/CasLogin.js src/components/views/login/CasLogin.js
@ -89,7 +86,6 @@ src/components/views/rooms/MemberList.js
src/components/views/rooms/MemberTile.js src/components/views/rooms/MemberTile.js
src/components/views/rooms/MessageComposer.js src/components/views/rooms/MessageComposer.js
src/components/views/rooms/MessageComposerInput.js src/components/views/rooms/MessageComposerInput.js
src/components/views/rooms/MessageComposerInputOld.js
src/components/views/rooms/PresenceLabel.js src/components/views/rooms/PresenceLabel.js
src/components/views/rooms/ReadReceiptMarker.js src/components/views/rooms/ReadReceiptMarker.js
src/components/views/rooms/RoomList.js src/components/views/rooms/RoomList.js
@ -100,7 +96,6 @@ src/components/views/rooms/RoomTile.js
src/components/views/rooms/RoomTopicEditor.js src/components/views/rooms/RoomTopicEditor.js
src/components/views/rooms/SearchableEntityList.js src/components/views/rooms/SearchableEntityList.js
src/components/views/rooms/SearchResultTile.js src/components/views/rooms/SearchResultTile.js
src/components/views/rooms/TabCompleteBar.js
src/components/views/rooms/TopUnreadMessagesBar.js src/components/views/rooms/TopUnreadMessagesBar.js
src/components/views/rooms/UserTile.js src/components/views/rooms/UserTile.js
src/components/views/settings/AddPhoneNumber.js src/components/views/settings/AddPhoneNumber.js
@ -128,9 +123,6 @@ src/Roles.js
src/Rooms.js src/Rooms.js
src/ScalarAuthClient.js src/ScalarAuthClient.js
src/ScalarMessaging.js src/ScalarMessaging.js
src/TabComplete.js
src/TabCompleteEntries.js
src/TextForEvent.js
src/Tinter.js src/Tinter.js
src/UiEffects.js src/UiEffects.js
src/Unread.js src/Unread.js
@ -142,7 +134,7 @@ src/utils/Receipt.js
src/Velociraptor.js src/Velociraptor.js
src/VelocityBounce.js src/VelocityBounce.js
src/WhoIsTyping.js src/WhoIsTyping.js
src/wrappers/WithMatrixClient.js src/wrappers/withMatrixClient.js
test/all-tests.js test/all-tests.js
test/components/structures/login/Registration-test.js test/components/structures/login/Registration-test.js
test/components/structures/MessagePanel-test.js test/components/structures/MessagePanel-test.js

View file

@ -40,6 +40,19 @@ module.exports = {
}], }],
"react/jsx-key": ["error"], "react/jsx-key": ["error"],
// Assert no spacing in JSX curly brackets
// <Element prop={ consideredError} prop={notConsideredError} />
//
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-curly-spacing.md
"react/jsx-curly-spacing": ["error", {"when": "never", "children": {"when": "always"}}],
// Assert spacing before self-closing JSX tags, and no spacing before or
// after the closing slash, and no spacing after the opening bracket of
// the opening tag or closing tag.
//
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-tag-spacing.md
"react/jsx-tag-spacing": ["error"],
/** flowtype **/ /** flowtype **/
"flowtype/require-parameter-type": ["warn", { "flowtype/require-parameter-type": ["warn", {
"excludeArrowFunctions": true, "excludeArrowFunctions": true,

View file

@ -1,3 +1,229 @@
Changes in [0.10.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.6) (2017-09-21)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.5...v0.10.6)
* New version of js-sdk with fixed build
Changes in [0.10.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.5) (2017-09-21)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.4...v0.10.5)
* Fix build error (https://github.com/vector-im/riot-web/issues/5091)
Changes in [0.10.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.4) (2017-09-20)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.4-rc.1...v0.10.4)
* No changes
Changes in [0.10.4-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.4-rc.1) (2017-09-19)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.3...v0.10.4-rc.1)
* Fix RoomView stuck in 'accept invite' state
[\#1396](https://github.com/matrix-org/matrix-react-sdk/pull/1396)
* Only show the integ management button if user is joined
[\#1398](https://github.com/matrix-org/matrix-react-sdk/pull/1398)
* suppressOnHover for member entity tiles which have no onClick
[\#1273](https://github.com/matrix-org/matrix-react-sdk/pull/1273)
* add /devtools command
[\#1268](https://github.com/matrix-org/matrix-react-sdk/pull/1268)
* Fix broken Link
[\#1359](https://github.com/matrix-org/matrix-react-sdk/pull/1359)
* Show who redacted an event on hover
[\#1387](https://github.com/matrix-org/matrix-react-sdk/pull/1387)
* start MELS expanded if it contains a highlighted/permalinked event.
[\#1388](https://github.com/matrix-org/matrix-react-sdk/pull/1388)
* Add ignore user API support
[\#1389](https://github.com/matrix-org/matrix-react-sdk/pull/1389)
* Add option to disable Emoji suggestions
[\#1392](https://github.com/matrix-org/matrix-react-sdk/pull/1392)
* sanitize the i18n for fn:textForHistoryVisibilityEvent
[\#1397](https://github.com/matrix-org/matrix-react-sdk/pull/1397)
* Don't check for only-emoji if there were none
[\#1394](https://github.com/matrix-org/matrix-react-sdk/pull/1394)
* Fix emojification of symbol characters
[\#1393](https://github.com/matrix-org/matrix-react-sdk/pull/1393)
* Update from Weblate.
[\#1395](https://github.com/matrix-org/matrix-react-sdk/pull/1395)
* Make /join join again
[\#1391](https://github.com/matrix-org/matrix-react-sdk/pull/1391)
* Display spinner not room preview after room create
[\#1390](https://github.com/matrix-org/matrix-react-sdk/pull/1390)
* Fix the avatar / room name in room preview
[\#1384](https://github.com/matrix-org/matrix-react-sdk/pull/1384)
* Remove spurious cancel button
[\#1381](https://github.com/matrix-org/matrix-react-sdk/pull/1381)
* Fix starting a chat by email address
[\#1386](https://github.com/matrix-org/matrix-react-sdk/pull/1386)
* respond on copy code block
[\#1363](https://github.com/matrix-org/matrix-react-sdk/pull/1363)
* fix DateUtils inconsistency with 12/24h
[\#1383](https://github.com/matrix-org/matrix-react-sdk/pull/1383)
* allow sending sub,sup and whitelist them on receive
[\#1382](https://github.com/matrix-org/matrix-react-sdk/pull/1382)
* Update roomlist when an event is decrypted
[\#1380](https://github.com/matrix-org/matrix-react-sdk/pull/1380)
* Update from Weblate.
[\#1379](https://github.com/matrix-org/matrix-react-sdk/pull/1379)
* fix radio for theme selection
[\#1368](https://github.com/matrix-org/matrix-react-sdk/pull/1368)
* fix some more zh_Hans - remove entirely broken lines
[\#1378](https://github.com/matrix-org/matrix-react-sdk/pull/1378)
* fix placeholder causing app to break when using zh
[\#1377](https://github.com/matrix-org/matrix-react-sdk/pull/1377)
* Avoid re-rendering RoomList on room switch
[\#1375](https://github.com/matrix-org/matrix-react-sdk/pull/1375)
* Fix 'Failed to load timeline position' regression
[\#1376](https://github.com/matrix-org/matrix-react-sdk/pull/1376)
* Fast path for emojifying strings
[\#1372](https://github.com/matrix-org/matrix-react-sdk/pull/1372)
* Consolidate the code copy button
[\#1374](https://github.com/matrix-org/matrix-react-sdk/pull/1374)
* Only add the code copy button for HTML messages
[\#1373](https://github.com/matrix-org/matrix-react-sdk/pull/1373)
* Don't re-render matrixchat unnecessarily
[\#1371](https://github.com/matrix-org/matrix-react-sdk/pull/1371)
* Don't wait for setState to run onHaveRoom
[\#1370](https://github.com/matrix-org/matrix-react-sdk/pull/1370)
* Introduce a RoomScrollStateStore
[\#1367](https://github.com/matrix-org/matrix-react-sdk/pull/1367)
* Don't always paginate when mounting a ScrollPanel
[\#1369](https://github.com/matrix-org/matrix-react-sdk/pull/1369)
* Remove unused scrollStateMap from LoggedinView
[\#1366](https://github.com/matrix-org/matrix-react-sdk/pull/1366)
* Revert "Implement sticky date separators"
[\#1365](https://github.com/matrix-org/matrix-react-sdk/pull/1365)
* Remove unused string "changing room on a RoomView is not supported"
[\#1361](https://github.com/matrix-org/matrix-react-sdk/pull/1361)
* Remove unused translation code translations
[\#1360](https://github.com/matrix-org/matrix-react-sdk/pull/1360)
* Implement sticky date separators
[\#1353](https://github.com/matrix-org/matrix-react-sdk/pull/1353)
Changes in [0.10.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.3) (2017-09-06)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.3-rc.2...v0.10.3)
* No changes
Changes in [0.10.3-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.3-rc.2) (2017-09-05)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.3-rc.1...v0.10.3-rc.2)
* Fix plurals in translations
[\#1358](https://github.com/matrix-org/matrix-react-sdk/pull/1358)
* Fix typo
[\#1357](https://github.com/matrix-org/matrix-react-sdk/pull/1357)
* Update from Weblate.
[\#1356](https://github.com/matrix-org/matrix-react-sdk/pull/1356)
Changes in [0.10.3-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.3-rc.1) (2017-09-01)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.2...v0.10.3-rc.1)
* Fix room change sometimes being very slow
[\#1354](https://github.com/matrix-org/matrix-react-sdk/pull/1354)
* apply shouldHideEvent fn to onRoomTimeline for RoomStatusBar
[\#1346](https://github.com/matrix-org/matrix-react-sdk/pull/1346)
* text4event widget modified, used to show widget added each time.
[\#1345](https://github.com/matrix-org/matrix-react-sdk/pull/1345)
* separate concepts of showing and managing RRs to fix regression
[\#1352](https://github.com/matrix-org/matrix-react-sdk/pull/1352)
* Make staging widgets work with live and vice versa.
[\#1350](https://github.com/matrix-org/matrix-react-sdk/pull/1350)
* Avoid breaking /sync with uncaught exceptions
[\#1349](https://github.com/matrix-org/matrix-react-sdk/pull/1349)
* we need to pass whether it is an invite RoomSubList explicitly (i18n)
[\#1343](https://github.com/matrix-org/matrix-react-sdk/pull/1343)
* Percent encoding isn't a valid thing within _t
[\#1348](https://github.com/matrix-org/matrix-react-sdk/pull/1348)
* Fix spurious notifications
[\#1339](https://github.com/matrix-org/matrix-react-sdk/pull/1339)
* Unbreak password reset with a non-default HS
[\#1347](https://github.com/matrix-org/matrix-react-sdk/pull/1347)
* Remove unnecessary 'load' on notif audio element
[\#1341](https://github.com/matrix-org/matrix-react-sdk/pull/1341)
* _tJsx returns a React Object, the sub fn must return a React Object
[\#1340](https://github.com/matrix-org/matrix-react-sdk/pull/1340)
* Fix deprecation warning about promise.defer()
[\#1292](https://github.com/matrix-org/matrix-react-sdk/pull/1292)
* Fix click to insert completion
[\#1331](https://github.com/matrix-org/matrix-react-sdk/pull/1331)
Changes in [0.10.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.2) (2017-08-24)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.1...v0.10.2)
* Force update on timelinepanel when event decrypted
[\#1334](https://github.com/matrix-org/matrix-react-sdk/pull/1334)
* Dispatch incoming_call synchronously
[\#1337](https://github.com/matrix-org/matrix-react-sdk/pull/1337)
* Fix React crying on machines without internet due to return undefined
[\#1335](https://github.com/matrix-org/matrix-react-sdk/pull/1335)
* Catch the promise rejection if scalar fails
[\#1333](https://github.com/matrix-org/matrix-react-sdk/pull/1333)
* Update from Weblate.
[\#1329](https://github.com/matrix-org/matrix-react-sdk/pull/1329)
Changes in [0.10.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.1) (2017-08-23)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.1-rc.1...v0.10.1)
* [No changes]
Changes in [0.10.1-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.1-rc.1) (2017-08-22)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.0-rc.2...v0.10.1-rc.1)
* Matthew/multiple widgets
[\#1327](https://github.com/matrix-org/matrix-react-sdk/pull/1327)
* Fix proptypes on UserPickerDialog
[\#1326](https://github.com/matrix-org/matrix-react-sdk/pull/1326)
* AppsDrawer: Remove unnecessary bind
[\#1325](https://github.com/matrix-org/matrix-react-sdk/pull/1325)
* Position add app widget link
[\#1322](https://github.com/matrix-org/matrix-react-sdk/pull/1322)
* Remove app tile beta tag.
[\#1323](https://github.com/matrix-org/matrix-react-sdk/pull/1323)
* Add missing translation.
[\#1324](https://github.com/matrix-org/matrix-react-sdk/pull/1324)
* Note that apps are not E2EE
[\#1319](https://github.com/matrix-org/matrix-react-sdk/pull/1319)
* Only render appTile body (including warnings) if drawer shown.
[\#1321](https://github.com/matrix-org/matrix-react-sdk/pull/1321)
* Timeline improvements
[\#1320](https://github.com/matrix-org/matrix-react-sdk/pull/1320)
* Add a space between widget name and "widget" in widget event tiles
[\#1318](https://github.com/matrix-org/matrix-react-sdk/pull/1318)
* Move manage integrations button from settings page to room header as a
stand-alone component
[\#1286](https://github.com/matrix-org/matrix-react-sdk/pull/1286)
* Don't apply case logic to app names
[\#1316](https://github.com/matrix-org/matrix-react-sdk/pull/1316)
* Stop integ manager opening on every room switch
[\#1315](https://github.com/matrix-org/matrix-react-sdk/pull/1315)
* Add behaviour to toggle app draw on app tile header click
[\#1313](https://github.com/matrix-org/matrix-react-sdk/pull/1313)
* Change OOO so that MELS generation will continue over hidden events
[\#1308](https://github.com/matrix-org/matrix-react-sdk/pull/1308)
* Implement TextualEvent tiles for im.vector.modular.widgets
[\#1312](https://github.com/matrix-org/matrix-react-sdk/pull/1312)
* Don't show widget security warning to the person that added it to the room
[\#1314](https://github.com/matrix-org/matrix-react-sdk/pull/1314)
* remove unused strings introduced by string change
[\#1311](https://github.com/matrix-org/matrix-react-sdk/pull/1311)
* hotfix bad fn signature regression
[\#1310](https://github.com/matrix-org/matrix-react-sdk/pull/1310)
* Show a dialog if the maximum number of widgets allowed has been reached.
[\#1291](https://github.com/matrix-org/matrix-react-sdk/pull/1291)
* Fix Robot translation
[\#1309](https://github.com/matrix-org/matrix-react-sdk/pull/1309)
* Refactor ChatInviteDialog to be UserPickerDialog
[\#1300](https://github.com/matrix-org/matrix-react-sdk/pull/1300)
* Update Link to Translation status
[\#1302](https://github.com/matrix-org/matrix-react-sdk/pull/1302)
Changes in [0.9.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.7) (2017-06-22) Changes in [0.9.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.7) (2017-06-22)
=================================================================================================== ===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.6...v0.9.7) [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.6...v0.9.7)

View file

@ -46,7 +46,7 @@ Please follow the standard Matrix contributor's guide:
https://github.com/matrix-org/synapse/tree/master/CONTRIBUTING.rst https://github.com/matrix-org/synapse/tree/master/CONTRIBUTING.rst
Please follow the Matrix JS/React code style as per: Please follow the Matrix JS/React code style as per:
https://github.com/matrix-org/matrix-react-sdk/tree/master/code_style.rst https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md
Whilst the layering separation between matrix-react-sdk and Riot is broken Whilst the layering separation between matrix-react-sdk and Riot is broken
(as of July 2016), code should be committed as follows: (as of July 2016), code should be committed as follows:

View file

@ -21,9 +21,7 @@ npm run test -- --no-colors
npm run lintall -- -f checkstyle -o eslint.xml || true npm run lintall -- -f checkstyle -o eslint.xml || true
# re-run the linter, excluding any files known to have errors or warnings. # re-run the linter, excluding any files known to have errors or warnings.
./node_modules/.bin/eslint --max-warnings 0 \ npm run lintwithexclusions
--ignore-path .eslintignore.errorfiles \
src test
# delete the old tarball, if it exists # delete the old tarball, if it exists
rm -f matrix-react-sdk-*.tgz rm -f matrix-react-sdk-*.tgz

View file

@ -1,6 +1,6 @@
{ {
"name": "matrix-react-sdk", "name": "matrix-react-sdk",
"version": "0.9.7", "version": "0.10.6",
"description": "SDK for matrix.org using React", "description": "SDK for matrix.org using React",
"author": "matrix.org", "author": "matrix.org",
"repository": { "repository": {
@ -39,8 +39,9 @@
"start": "parallelshell \"npm run build:watch\" \"npm run reskindex:watch\"", "start": "parallelshell \"npm run build:watch\" \"npm run reskindex:watch\"",
"lint": "eslint src/", "lint": "eslint src/",
"lintall": "eslint src/ test/", "lintall": "eslint src/ test/",
"lintwithexclusions": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test",
"clean": "rimraf lib", "clean": "rimraf lib",
"prepublish": "npm run build && git rev-parse HEAD > git-revision.txt", "prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt",
"test": "karma start --single-run=true --browsers ChromeHeadless", "test": "karma start --single-run=true --browsers ChromeHeadless",
"test-multi": "karma start" "test-multi": "karma start"
}, },
@ -66,7 +67,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": "matrix-org/matrix-js-sdk#develop", "matrix-js-sdk": "0.8.4",
"optimist": "^0.6.1", "optimist": "^0.6.1",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
"react": "^15.4.0", "react": "^15.4.0",
@ -99,7 +100,7 @@
"eslint-config-google": "^0.7.1", "eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^4.0.1", "eslint-plugin-babel": "^4.0.1",
"eslint-plugin-flowtype": "^2.30.0", "eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-react": "^6.9.0", "eslint-plugin-react": "^7.4.0",
"expect": "^1.16.0", "expect": "^1.16.0",
"json-loader": "^0.5.3", "json-loader": "^0.5.3",
"karma": "^1.7.0", "karma": "^1.7.0",

View file

@ -6,6 +6,4 @@ npm run test
./.travis-test-riot.sh ./.travis-test-riot.sh
# run the linter, but exclude any files known to have errors or warnings. # run the linter, but exclude any files known to have errors or warnings.
./node_modules/.bin/eslint --max-warnings 0 \ npm run lintwithexclusions
--ignore-path .eslintignore.errorfiles \
src test

77
src/ActiveRoomObserver.js Normal file
View file

@ -0,0 +1,77 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import RoomViewStore from './stores/RoomViewStore';
/**
* Consumes changes from the RoomViewStore and notifies specific things
* about when the active room changes. Unlike listening for RoomViewStore
* changes, you can subscribe to only changes relevant to a particular
* room.
*
* TODO: If we introduce an observer for something else, factor out
* the adding / removing of listeners & emitting into a common class.
*/
class ActiveRoomObserver {
constructor() {
this._listeners = {};
this._activeRoomId = RoomViewStore.getRoomId();
// TODO: We could self-destruct when the last listener goes away, or at least
// stop listening.
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this));
}
addListener(roomId, listener) {
if (!this._listeners[roomId]) this._listeners[roomId] = [];
this._listeners[roomId].push(listener);
}
removeListener(roomId, listener) {
if (this._listeners[roomId]) {
const i = this._listeners[roomId].indexOf(listener);
if (i > -1) {
this._listeners[roomId].splice(i, 1);
}
} else {
console.warn("Unregistering unrecognised listener (roomId=" + roomId + ")");
}
}
_emit(roomId) {
if (!this._listeners[roomId]) return;
for (const l of this._listeners[roomId]) {
l.call();
}
}
_onRoomViewStoreUpdate() {
// emit for the old room ID
if (this._activeRoomId) this._emit(this._activeRoomId);
// update our cache
this._activeRoomId = RoomViewStore.getRoomId();
// and emit for the new one
if (this._activeRoomId) this._emit(this._activeRoomId);
}
}
if (global.mx_ActiveRoomObserver === undefined) {
global.mx_ActiveRoomObserver = new ActiveRoomObserver();
}
export default global.mx_ActiveRoomObserver;

View file

@ -107,6 +107,9 @@ export default class BasePlatform {
isElectron(): boolean { return false; } isElectron(): boolean { return false; }
setupScreenSharingForIframe() {
}
/** /**
* Restarts the application, without neccessarily reloading * Restarts the application, without neccessarily reloading
* any application code * any application code

View file

@ -65,7 +65,7 @@ module.exports = {
const days = getDaysArray(); const days = getDaysArray();
const months = getMonthsArray(); const months = getMonthsArray();
if (date.toDateString() === now.toDateString()) { if (date.toDateString() === now.toDateString()) {
return this.formatTime(date); return this.formatTime(date, showTwelveHour);
} else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { } else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
// TODO: use standard date localize function provided in counterpart // TODO: use standard date localize function provided in counterpart
return _t('%(weekDayName)s %(time)s', { return _t('%(weekDayName)s %(time)s', {
@ -78,7 +78,7 @@ module.exports = {
weekDayName: days[date.getDay()], weekDayName: days[date.getDay()],
monthName: months[date.getMonth()], monthName: months[date.getMonth()],
day: date.getDate(), day: date.getDate(),
time: this.formatTime(date), time: this.formatTime(date, showTwelveHour),
}); });
} }
return this.formatFullDate(date, showTwelveHour); return this.formatFullDate(date, showTwelveHour);
@ -92,7 +92,7 @@ module.exports = {
monthName: months[date.getMonth()], monthName: months[date.getMonth()],
day: date.getDate(), day: date.getDate(),
fullYear: date.getFullYear(), fullYear: date.getFullYear(),
time: showTwelveHour ? twelveHourTime(date) : this.formatTime(date), time: this.formatTime(date, showTwelveHour),
}); });
}, },

113
src/GroupAddressPicker.js Normal file
View file

@ -0,0 +1,113 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Modal from './Modal';
import sdk from './';
import MultiInviter from './utils/MultiInviter';
import { _t } from './languageHandler';
import MatrixClientPeg from './MatrixClientPeg';
import GroupStoreCache from './stores/GroupStoreCache';
export function showGroupInviteDialog(groupId) {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, {
title: _t("Invite new group members"),
description: _t("Who would you like to add to this group?"),
placeholder: _t("Name or matrix ID"),
button: _t("Invite to Group"),
validAddressTypes: ['mx-user-id'],
onFinished: (success, addrs) => {
if (!success) return;
_onGroupInviteFinished(groupId, addrs);
},
});
}
export function showGroupAddRoomDialog(groupId) {
return new Promise((resolve, reject) => {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Rooms to Group', '', AddressPickerDialog, {
title: _t("Add rooms to the group"),
description: _t("Which rooms would you like to add to this group?"),
placeholder: _t("Room name or alias"),
button: _t("Add to group"),
pickerType: 'room',
validAddressTypes: ['mx-room-id'],
onFinished: (success, addrs) => {
if (!success) return;
_onGroupAddRoomFinished(groupId, addrs).then(resolve, reject);
},
});
});
}
function _onGroupInviteFinished(groupId, addrs) {
const multiInviter = new MultiInviter(groupId);
const addrTexts = addrs.map((addr) => addr.address);
multiInviter.invite(addrTexts).then((completionStates) => {
// Show user any errors
const errorList = [];
for (const addr of Object.keys(completionStates)) {
if (addrs[addr] === "error") {
errorList.push(addr);
}
}
if (errorList.length > 0) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite the following users to the group', '', ErrorDialog, {
title: _t("Failed to invite the following users to %(groupId)s:", {groupId: groupId}),
description: errorList.join(", "),
});
}
}).catch((err) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite users to group', '', ErrorDialog, {
title: _t("Failed to invite users group"),
description: _t("Failed to invite users to %(groupId)s", {groupId: groupId}),
});
});
}
function _onGroupAddRoomFinished(groupId, addrs) {
const groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
const errorList = [];
return Promise.all(addrs.map((addr) => {
return groupStore
.addRoomToGroup(addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to %(groupId)s:",
{groupId},
),
description: errorList.join(", "),
});
});
}

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector 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.
@ -31,13 +32,33 @@ emojione.imagePathPNG = 'emojione/png/';
// Use SVGs for emojis // Use SVGs for emojis
emojione.imageType = 'svg'; emojione.imageType = 'svg';
// Anything outside the basic multilingual plane will be a surrogate pair
const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/;
// And there a bunch more symbol characters that emojione has within the
// BMP, so this includes the ranges from 'letterlike symbols' to
// 'miscellaneous symbols and arrows' which should catch all of them
// (with plenty of false positives, but that's OK)
const SYMBOL_PATTERN = /([\u2100-\u2bff])/;
// And this is emojione's complete regex
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi"); const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
/*
* Return true if the given string contains emoji
* Uses a much, much simpler regex than emojione's so will give false
* positives, but useful for fast-path testing strings to see if they
* need emojification.
* unicodeToImage uses this function.
*/
export function containsEmoji(str) {
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
}
/* modified from https://github.com/Ranks/emojione/blob/master/lib/js/emojione.js /* modified from https://github.com/Ranks/emojione/blob/master/lib/js/emojione.js
* because we want to include emoji shortnames in title text * because we want to include emoji shortnames in title text
*/ */
export function unicodeToImage(str) { function unicodeToImage(str) {
let replaceWith, unicode, alt, short, fname; let replaceWith, unicode, alt, short, fname;
const mappedUnicode = emojione.mapUnicodeToShort(); const mappedUnicode = emojione.mapUnicodeToShort();
@ -136,7 +157,7 @@ const sanitizeHtmlParams = {
allowedTags: [ allowedTags: [
'font', // custom to matrix for IRC-style font coloring 'font', // custom to matrix for IRC-style font coloring
'del', // for markdown 'del', // for markdown
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
], ],
@ -375,6 +396,8 @@ export function bodyToHtml(content, highlights, opts) {
var isHtml = (content.format === "org.matrix.custom.html"); var isHtml = (content.format === "org.matrix.custom.html");
let body = isHtml ? content.formatted_body : escape(content.body); let body = isHtml ? content.formatted_body : escape(content.body);
let bodyHasEmoji = false;
var safeBody; var safeBody;
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying // XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which // to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
@ -392,17 +415,20 @@ export function bodyToHtml(content, highlights, opts) {
}; };
} }
safeBody = sanitizeHtml(body, sanitizeHtmlParams); safeBody = sanitizeHtml(body, sanitizeHtmlParams);
safeBody = unicodeToImage(safeBody); bodyHasEmoji = containsEmoji(body);
safeBody = addCodeCopyButton(safeBody); if (bodyHasEmoji) safeBody = unicodeToImage(safeBody);
} }
finally { finally {
delete sanitizeHtmlParams.textFilter; delete sanitizeHtmlParams.textFilter;
} }
let emojiBody = false;
if (bodyHasEmoji) {
EMOJI_REGEX.lastIndex = 0; EMOJI_REGEX.lastIndex = 0;
let contentBodyTrimmed = content.body !== undefined ? content.body.trim() : ''; let contentBodyTrimmed = content.body !== undefined ? content.body.trim() : '';
let match = EMOJI_REGEX.exec(contentBodyTrimmed); let match = EMOJI_REGEX.exec(contentBodyTrimmed);
let emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length; emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length;
}
const className = classNames({ const className = classNames({
'mx_EventTile_body': true, 'mx_EventTile_body': true,
@ -412,23 +438,6 @@ export function bodyToHtml(content, highlights, opts) {
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />; return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />;
} }
function addCodeCopyButton(safeBody) {
// Adds 'copy' buttons to pre blocks
// Note that this only manipulates the markup to add the buttons:
// we need to add the event handlers once the nodes are in the DOM
// since we can't save functions in the markup.
// This is done in TextualBody
const el = document.createElement("div");
el.innerHTML = safeBody;
const codeBlocks = Array.from(el.getElementsByTagName("pre"));
codeBlocks.forEach(p => {
const button = document.createElement("span");
button.className = "mx_EventTile_copyButton";
p.appendChild(button);
});
return el.innerHTML;
}
export function emojifyText(text) { export function emojifyText(text) {
return { return {
__html: unicodeToImage(escape(text)), __html: unicodeToImage(escape(text)),

View file

@ -17,7 +17,7 @@ limitations under the License.
import commonmark from 'commonmark'; import commonmark from 'commonmark';
import escape from 'lodash/escape'; import escape from 'lodash/escape';
const ALLOWED_HTML_TAGS = ['del', 'u']; const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u'];
// These types of node are definitely text // These types of node are definitely text
const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document'];

View file

@ -1,6 +1,7 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector 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.
@ -33,9 +34,16 @@ import Modal from './Modal';
* } * }
*/ */
const MAX_PENDING_ENCRYPTED = 20;
const Notifier = { const Notifier = {
notifsByRoom: {}, notifsByRoom: {},
// A list of event IDs that we've received but need to wait until
// they're decrypted until we decide whether to notify for them
// or not
pendingEncryptedEventIds: [],
notificationMessageForEvent: function(ev) { notificationMessageForEvent: function(ev) {
return TextForEvent.textForEvent(ev); return TextForEvent.textForEvent(ev);
}, },
@ -89,17 +97,18 @@ const Notifier = {
_playAudioNotification: function(ev, room) { _playAudioNotification: function(ev, room) {
const e = document.getElementById("messageAudio"); const e = document.getElementById("messageAudio");
if (e) { if (e) {
e.load();
e.play(); e.play();
} }
}, },
start: function() { start: function() {
this.boundOnRoomTimeline = this.onRoomTimeline.bind(this); this.boundOnEvent = this.onEvent.bind(this);
this.boundOnSyncStateChange = this.onSyncStateChange.bind(this); this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
this.boundOnRoomReceipt = this.onRoomReceipt.bind(this); this.boundOnRoomReceipt = this.onRoomReceipt.bind(this);
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline); this.boundOnEventDecrypted = this.onEventDecrypted.bind(this);
MatrixClientPeg.get().on('event', this.boundOnEvent);
MatrixClientPeg.get().on('Room.receipt', this.boundOnRoomReceipt); MatrixClientPeg.get().on('Room.receipt', this.boundOnRoomReceipt);
MatrixClientPeg.get().on('Event.decrypted', this.boundOnEventDecrypted);
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange); MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
this.toolbarHidden = false; this.toolbarHidden = false;
this.isSyncing = false; this.isSyncing = false;
@ -107,8 +116,9 @@ const Notifier = {
stop: function() { stop: function() {
if (MatrixClientPeg.get() && this.boundOnRoomTimeline) { if (MatrixClientPeg.get() && this.boundOnRoomTimeline) {
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline); MatrixClientPeg.get().removeListener('Event', this.boundOnEvent);
MatrixClientPeg.get().removeListener('Room.receipt', this.boundOnRoomReceipt); MatrixClientPeg.get().removeListener('Room.receipt', this.boundOnRoomReceipt);
MatrixClientPeg.get().removeListener('Event.decrypted', this.boundOnEventDecrypted);
MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange); MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
} }
this.isSyncing = false; this.isSyncing = false;
@ -237,23 +247,30 @@ const Notifier = {
} }
}, },
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) { onEvent: function(ev) {
if (toStartOfTimeline) return;
if (!room) return;
if (!this.isSyncing) return; // don't alert for any messages initially if (!this.isSyncing) return; // don't alert for any messages initially
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return; if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev); // If it's an encrypted event and the type is still 'm.room.encrypted',
if (actions && actions.notify) { // it hasn't yet been decrypted, so wait until it is.
if (this.isEnabled()) { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
this._displayPopupNotification(ev, room); this.pendingEncryptedEventIds.push(ev.getId());
} // don't let the list fill up indefinitely
if (actions.tweaks.sound && this.isAudioEnabled()) { while (this.pendingEncryptedEventIds.length > MAX_PENDING_ENCRYPTED) {
PlatformPeg.get().loudNotification(ev, room); this.pendingEncryptedEventIds.shift();
this._playAudioNotification(ev, room);
} }
return;
} }
this._evaluateEvent(ev);
},
onEventDecrypted: function(ev) {
const idx = this.pendingEncryptedEventIds.indexOf(ev.getId());
if (idx === -1) return;
this.pendingEncryptedEventIds.splice(idx, 1);
this._evaluateEvent(ev);
}, },
onRoomReceipt: function(ev, room) { onRoomReceipt: function(ev, room) {
@ -273,6 +290,20 @@ const Notifier = {
delete this.notifsByRoom[room.roomId]; delete this.notifsByRoom[room.roomId];
} }
}, },
_evaluateEvent: function(ev) {
const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions && actions.notify) {
if (this.isEnabled()) {
this._displayPopupNotification(ev, room);
}
if (actions.tweaks.sound && this.isAudioEnabled()) {
PlatformPeg.get().loudNotification(ev, room);
this._playAudioNotification(ev, room);
}
}
}
}; };
if (!global.mxNotifier) { if (!global.mxNotifier) {

View file

@ -28,7 +28,7 @@ export function inviteToRoom(roomId, addr) {
if (addrType == 'email') { if (addrType == 'email') {
return MatrixClientPeg.get().inviteByEmail(roomId, addr); return MatrixClientPeg.get().inviteByEmail(roomId, addr);
} else if (addrType == 'mx') { } else if (addrType == 'mx-user-id') {
return MatrixClientPeg.get().invite(roomId, addr); return MatrixClientPeg.get().invite(roomId, addr);
} else { } else {
throw new Error('Unsupported address'); throw new Error('Unsupported address');
@ -50,8 +50,8 @@ export function inviteMultipleToRoom(roomId, addrs) {
} }
export function showStartChatInviteDialog() { export function showStartChatInviteDialog() {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Start a chat', '', UserPickerDialog, { Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
title: _t('Start a chat'), title: _t('Start a chat'),
description: _t("Who would you like to communicate with?"), description: _t("Who would you like to communicate with?"),
placeholder: _t("Email, name or matrix ID"), placeholder: _t("Email, name or matrix ID"),
@ -61,8 +61,8 @@ export function showStartChatInviteDialog() {
} }
export function showRoomInviteDialog(roomId) { export function showRoomInviteDialog(roomId) {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Chat Invite', '', UserPickerDialog, { Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {
title: _t('Invite new room members'), title: _t('Invite new room members'),
description: _t('Who would you like to add to this room?'), description: _t('Who would you like to add to this room?'),
button: _t('Send Invites'), button: _t('Send Invites'),
@ -127,7 +127,7 @@ function _onRoomInviteFinished(roomId, shouldInvite, addrs) {
} }
function _isDmChat(addrTexts) { function _isDmChat(addrTexts) {
if (addrTexts.length === 1 && getAddressType(addrTexts[0])) { if (addrTexts.length === 1 && getAddressType(addrTexts[0]) === 'mx') {
return true; return true;
} else { } else {
return false; return false;

View file

@ -76,10 +76,13 @@ class ScalarAuthClient {
return defer.promise; return defer.promise;
} }
getScalarInterfaceUrlForRoom(roomId, screen) { getScalarInterfaceUrlForRoom(roomId, screen, id) {
var url = SdkConfig.get().integrations_ui_url; var url = SdkConfig.get().integrations_ui_url;
url += "?scalar_token=" + encodeURIComponent(this.scalarToken); url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
url += "&room_id=" + encodeURIComponent(roomId); url += "&room_id=" + encodeURIComponent(roomId);
if (id) {
url += '&integ_id=' + encodeURIComponent(id);
}
if (screen) { if (screen) {
url += '&screen=' + encodeURIComponent(screen); url += '&screen=' + encodeURIComponent(screen);
} }

View file

@ -84,6 +84,9 @@ class Skinner {
// behaviour with multiple copies of files etc. is erratic at best. // behaviour with multiple copies of files etc. is erratic at best.
// XXX: We can still end up with the same file twice in the resulting // XXX: We can still end up with the same file twice in the resulting
// JS bundle which is nonideal. // JS bundle which is nonideal.
// See https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/
// or https://nodejs.org/api/modules.html#modules_module_caching_caveats
// ("Modules are cached based on their resolved filename")
if (global.mxSkinner === undefined) { if (global.mxSkinner === undefined) {
global.mxSkinner = new Skinner(); global.mxSkinner = new Skinner();
} }

View file

@ -240,6 +240,59 @@ const commands = {
return reject(this.getUsage()); return reject(this.getUsage());
}), }),
ignore: new Command("ignore", "<userId>", function(roomId, args) {
if (args) {
const matches = args.match(/^(\S+)$/);
if (matches) {
const userId = matches[1];
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
ignoredUsers.push(userId); // de-duped internally in the js-sdk
return success(
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Slash Commands', 'User ignored', QuestionDialog, {
title: _t("Ignored user"),
description: (
<div>
<p>{ _t("You are now ignoring %(userId)s", {userId: userId}) }</p>
</div>
),
hasCancelButton: false,
});
}),
);
}
}
return reject(this.getUsage());
}),
unignore: new Command("unignore", "<userId>", function(roomId, args) {
if (args) {
const matches = args.match(/^(\S+)$/);
if (matches) {
const userId = matches[1];
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
const index = ignoredUsers.indexOf(userId);
if (index !== -1) ignoredUsers.splice(index, 1);
return success(
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Slash Commands', 'User unignored', QuestionDialog, {
title: _t("Unignored user"),
description: (
<div>
<p>{ _t("You are no longer ignoring %(userId)s", {userId: userId}) }</p>
</div>
),
hasCancelButton: false,
});
}),
);
}
}
return reject(this.getUsage());
}),
// Define the power level of a user // Define the power level of a user
op: new Command("op", "<userId> [<power level>]", function(roomId, args) { op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
if (args) { if (args) {
@ -292,6 +345,13 @@ const commands = {
return reject(this.getUsage()); return reject(this.getUsage());
}), }),
// Open developer tools
devtools: new Command("devtools", "", function(roomId) {
const DevtoolsDialog = sdk.getComponent("dialogs.DevtoolsDialog");
Modal.createDialog(DevtoolsDialog, { roomId });
return success();
}),
// Verify a user, device, and pubkey tuple // Verify a user, device, and pubkey tuple
verify: new Command("verify", "<userId> <deviceId> <deviceSigningKey>", function(roomId, args) { verify: new Command("verify", "<userId> <deviceId> <deviceSigningKey>", function(roomId, args) {
if (args) { if (args) {

View file

@ -13,56 +13,67 @@ 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.
*/ */
import MatrixClientPeg from "./MatrixClientPeg"; import MatrixClientPeg from './MatrixClientPeg';
import CallHandler from "./CallHandler"; import CallHandler from './CallHandler';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import * as Roles from './Roles'; import * as Roles from './Roles';
function textForMemberEvent(ev) { function textForMemberEvent(ev) {
// XXX: SYJS-16 "sender is sometimes null for join messages" // XXX: SYJS-16 "sender is sometimes null for join messages"
var senderName = ev.sender ? ev.sender.name : ev.getSender(); const senderName = ev.sender ? ev.sender.name : ev.getSender();
var targetName = ev.target ? ev.target.name : ev.getStateKey(); const targetName = ev.target ? ev.target.name : ev.getStateKey();
var ConferenceHandler = CallHandler.getConferenceHandler(); const prevContent = ev.getPrevContent();
var reason = ev.getContent().reason ? ( const content = ev.getContent();
_t('Reason') + ': ' + ev.getContent().reason
) : ""; const ConferenceHandler = CallHandler.getConferenceHandler();
switch (ev.getContent().membership) { const reason = content.reason ? (_t('Reason') + ': ' + content.reason) : '';
case 'invite': switch (content.membership) {
var threePidContent = ev.getContent().third_party_invite; case 'invite': {
const threePidContent = content.third_party_invite;
if (threePidContent) { if (threePidContent) {
if (threePidContent.display_name) { if (threePidContent.display_name) {
return _t('%(targetName)s accepted the invitation for %(displayName)s.', {targetName: targetName, displayName: threePidContent.display_name}); return _t('%(targetName)s accepted the invitation for %(displayName)s.', {
targetName,
displayName: threePidContent.display_name,
});
} else { } else {
return _t('%(targetName)s accepted an invitation.', {targetName: targetName}); return _t('%(targetName)s accepted an invitation.', {targetName});
} }
} } else {
else {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return _t('%(senderName)s requested a VoIP conference.', {senderName: senderName}); return _t('%(senderName)s requested a VoIP conference.', {senderName});
} else {
return _t('%(senderName)s invited %(targetName)s.', {senderName, targetName});
} }
else {
return _t('%(senderName)s invited %(targetName)s.', {senderName: senderName, targetName: targetName});
} }
} }
case 'ban': case 'ban':
return _t( return _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + reason;
'%(senderName)s banned %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
case 'join': case 'join':
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') { if (prevContent && prevContent.membership === 'join') {
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) { if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname, displayName: ev.getContent().displayname}); return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) { senderName,
return _t('%(senderName)s set their display name to %(displayName)s.', {senderName: ev.getSender(), displayName: ev.getContent().displayname}); oldDisplayName: prevContent.displayname,
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) { displayName: content.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 (!prevContent.displayname && content.displayname) {
return _t('%(senderName)s removed their profile picture.', {senderName: senderName}); return _t('%(senderName)s set their display name to %(displayName)s.', {
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) { senderName,
return _t('%(senderName)s changed their profile picture.', {senderName: senderName}); displayName: content.displayname,
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) { });
return _t('%(senderName)s set a profile picture.', {senderName: senderName}); } else if (prevContent.displayname && !content.displayname) {
return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {
senderName,
oldDisplayName: prevContent.displayname,
});
} else if (prevContent.avatar_url && !content.avatar_url) {
return _t('%(senderName)s removed their profile picture.', {senderName});
} else if (prevContent.avatar_url && content.avatar_url &&
prevContent.avatar_url !== content.avatar_url) {
return _t('%(senderName)s changed their profile picture.', {senderName});
} else if (!prevContent.avatar_url && content.avatar_url) {
return _t('%(senderName)s set a profile picture.', {senderName});
} else { } else {
// suppress null rejoins // suppress null rejoins
return ''; return '';
@ -71,73 +82,69 @@ function textForMemberEvent(ev) {
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 _t('VoIP conference started.'); return _t('VoIP conference started.');
} } else {
else { return _t('%(targetName)s joined the room.', {targetName});
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 _t('VoIP conference finished.'); return _t('VoIP conference finished.');
} else if (prevContent.membership === "invite") {
return _t('%(targetName)s rejected the invitation.', {targetName});
} else {
return _t('%(targetName)s left the room.', {targetName});
} }
else if (ev.getPrevContent().membership === "invite") { } else if (prevContent.membership === "ban") {
return _t('%(targetName)s rejected the invitation.', {targetName: targetName}); return _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName});
} } else if (prevContent.membership === "join") {
else { return _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + reason;
return _t('%(targetName)s left the room.', {targetName: targetName}); } else if (prevContent.membership === "invite") {
} return _t('%(senderName)s withdrew %(targetName)s\'s invitation.', {
} senderName,
else if (ev.getPrevContent().membership === "ban") { targetName,
return _t('%(senderName)s unbanned %(targetName)s.', {senderName: senderName, targetName: targetName}); }) + ' ' + reason;
} } else {
else if (ev.getPrevContent().membership === "join") { return _t('%(targetName)s left the room.', {targetName});
return _t(
'%(senderName)s kicked %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else if (ev.getPrevContent().membership === "invite") {
return _t(
'%(senderName)s withdrew %(targetName)s\'s invitation.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else {
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(); const 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 _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {
senderDisplayName,
topic: ev.getContent().topic,
});
} }
function textForRoomNameEvent(ev) { function textForRoomNameEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName}); return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName});
} }
return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name}); return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {
senderDisplayName,
roomName: ev.getContent().name,
});
} }
function textForMessageEvent(ev) { function textForMessageEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
var message = senderDisplayName + ': ' + ev.getContent().body; let message = senderDisplayName + ': ' + ev.getContent().body;
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 = _t('%(senderDisplayName)s sent an image.', {senderDisplayName: senderDisplayName}); message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName});
} }
return message; return message;
} }
function textForCallAnswerEvent(event) { function textForCallAnswerEvent(event) {
var senderName = event.sender ? event.sender.name : _t('Someone'); const senderName = event.sender ? event.sender.name : _t('Someone');
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)');
return _t('%(senderName)s answered the call.', {senderName: senderName}) + ' ' + supported; return _t('%(senderName)s answered the call.', {senderName}) + ' ' + supported;
} }
function textForCallHangupEvent(event) { function textForCallHangupEvent(event) {
@ -159,48 +166,52 @@ function textForCallHangupEvent(event) {
} }
function textForCallInviteEvent(event) { function textForCallInviteEvent(event) {
var senderName = event.sender ? event.sender.name : _t('Someone'); const 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"; let callType = "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"; callType = "video";
} }
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); const supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s placed a %(callType)s call.', {senderName: senderName, callType: type}) + ' ' + supported; return _t('%(senderName)s placed a %(callType)s call.', {senderName, callType}) + ' ' + supported;
} }
function textForThreePidInviteEvent(event) { function textForThreePidInviteEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); const senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {senderName: senderName, targetDisplayName: event.getContent().display_name}); return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {
senderName,
targetDisplayName: event.getContent().display_name,
});
} }
function textForHistoryVisibilityEvent(event) { function textForHistoryVisibilityEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); const senderName = event.sender ? event.sender.name : event.getSender();
var vis = event.getContent().history_visibility; switch (event.getContent().history_visibility) {
// XXX: This i18n just isn't going to work for languages with different sentence structure. case 'invited':
var text = _t('%(senderName)s made future room history visible to', {senderName: senderName}) + ' '; return _t('%(senderName)s made future room history visible to all room members, '
if (vis === "invited") { + 'from the point they are invited.', {senderName});
text += _t('all room members, from the point they are invited') + '.'; case 'joined':
return _t('%(senderName)s made future room history visible to all room members, '
+ 'from the point they joined.', {senderName});
case 'shared':
return _t('%(senderName)s made future room history visible to all room members.', {senderName});
case 'world_readable':
return _t('%(senderName)s made future room history visible to anyone.', {senderName});
default:
return _t('%(senderName)s made future room history visible to unknown (%(visibility)s).', {
senderName,
visibility: event.getContent().history_visibility,
});
} }
else if (vis === "joined") {
text += _t('all room members, from the point they joined') + '.';
}
else if (vis === "shared") {
text += _t('all room members') + '.';
}
else if (vis === "world_readable") {
text += _t('anyone') + '.';
}
else {
text += ' ' + _t('unknown') + ' (' + vis + ').';
}
return text;
} }
function textForEncryptionEvent(event) { function textForEncryptionEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); const senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {senderName: senderName, algorithm: event.getContent().algorithm}); return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {
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
@ -211,18 +222,18 @@ function textForPowerEvent(event) {
} }
const userDefault = event.getContent().users_default || 0; const userDefault = event.getContent().users_default || 0;
// Construct set of userIds // Construct set of userIds
let users = []; const users = [];
Object.keys(event.getContent().users).forEach( Object.keys(event.getContent().users).forEach(
(userId) => { (userId) => {
if (users.indexOf(userId) === -1) users.push(userId); if (users.indexOf(userId) === -1) users.push(userId);
} },
); );
Object.keys(event.getPrevContent().users).forEach( Object.keys(event.getPrevContent().users).forEach(
(userId) => { (userId) => {
if (users.indexOf(userId) === -1) users.push(userId); if (users.indexOf(userId) === -1) users.push(userId);
} },
); );
let diff = []; const diff = [];
// XXX: This is also surely broken for i18n // XXX: This is also surely broken for i18n
users.forEach((userId) => { users.forEach((userId) => {
// Previous power level // Previous power level
@ -234,8 +245,8 @@ function textForPowerEvent(event) {
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', { _t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
userId: userId, userId: userId,
fromPowerLevel: Roles.textualPowerLevel(from, userDefault), fromPowerLevel: Roles.textualPowerLevel(from, userDefault),
toPowerLevel: Roles.textualPowerLevel(to, userDefault) toPowerLevel: Roles.textualPowerLevel(to, userDefault),
}) }),
); );
} }
}); });
@ -244,11 +255,41 @@ function textForPowerEvent(event) {
} }
return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', { return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', {
senderName: senderName, senderName: senderName,
powerLevelDiffText: diff.join(", ") powerLevelDiffText: diff.join(", "),
}); });
} }
var handlers = { function textForWidgetEvent(event) {
const senderName = event.getSender();
const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent();
const {name, type, url} = event.getContent() || {};
let widgetName = name || prevName || type || prevType || '';
// Apply sentence case to widget name
if (widgetName && widgetName.length > 0) {
widgetName = widgetName[0].toUpperCase() + widgetName.slice(1) + ' ';
}
// If the widget was removed, its content should be {}, but this is sufficiently
// equivalent to that condition.
if (url) {
if (prevUrl) {
return _t('%(widgetName)s widget modified by %(senderName)s', {
widgetName, senderName,
});
} else {
return _t('%(widgetName)s widget added by %(senderName)s', {
widgetName, senderName,
});
}
} else {
return _t('%(widgetName)s widget removed by %(senderName)s', {
widgetName, senderName,
});
}
}
const handlers = {
'm.room.message': textForMessageEvent, 'm.room.message': textForMessageEvent,
'm.room.name': textForRoomNameEvent, 'm.room.name': textForRoomNameEvent,
'm.room.topic': textForTopicEvent, 'm.room.topic': textForTopicEvent,
@ -260,12 +301,14 @@ var handlers = {
'm.room.history_visibility': textForHistoryVisibilityEvent, 'm.room.history_visibility': textForHistoryVisibilityEvent,
'm.room.encryption': textForEncryptionEvent, 'm.room.encryption': textForEncryptionEvent,
'm.room.power_levels': textForPowerEvent, 'm.room.power_levels': textForPowerEvent,
'im.vector.modular.widgets': textForWidgetEvent,
}; };
module.exports = { module.exports = {
textForEvent: function(ev) { textForEvent: function(ev) {
var hdlr = handlers[ev.getType()]; const hdlr = handlers[ev.getType()];
if (!hdlr) return ""; if (!hdlr) return '';
return hdlr(ev); return hdlr(ev);
} },
}; };

View file

@ -16,11 +16,12 @@ limitations under the License.
const emailRegex = /^\S+@\S+\.\S+$/; const emailRegex = /^\S+@\S+\.\S+$/;
const mxidRegex = /^@\S+:\S+$/; const mxUserIdRegex = /^@\S+:\S+$/;
const mxRoomIdRegex = /^!\S+:\S+$/;
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
export const addressTypes = [ export const addressTypes = [
'mx', 'email', 'mx-user-id', 'mx-room-id', 'email',
]; ];
// PropType definition for an object describing // PropType definition for an object describing
@ -41,13 +42,16 @@ export const UserAddressType = PropTypes.shape({
export function getAddressType(inputText) { export function getAddressType(inputText) {
const isEmailAddress = emailRegex.test(inputText); const isEmailAddress = emailRegex.test(inputText);
const isMatrixId = mxidRegex.test(inputText); const isUserId = mxUserIdRegex.test(inputText);
const isRoomId = mxRoomIdRegex.test(inputText);
// sanity check the input for user IDs // sanity check the input for user IDs
if (isEmailAddress) { if (isEmailAddress) {
return 'email'; return 'email';
} else if (isMatrixId) { } else if (isUserId) {
return 'mx'; return 'mx-user-id';
} else if (isRoomId) {
return 'mx-room-id';
} else { } else {
return null; return null;
} }

View file

@ -33,11 +33,17 @@ export default {
// XXX: Always use default, ignore localStorage and remove from labs // XXX: Always use default, ignore localStorage and remove from labs
override: true, override: true,
}, },
{
name: "-",
id: 'feature_groups',
default: false,
},
], ],
// horrible but it works. The locality makes this somewhat more palatable. // horrible but it works. The locality makes this somewhat more palatable.
doTranslations: function() { doTranslations: function() {
this.LABS_FEATURES[0].name = _t("Matrix Apps"); this.LABS_FEATURES[0].name = _t("Matrix Apps");
this.LABS_FEATURES[1].name = _t("Groups");
}, },
loadProfileInfo: function() { loadProfileInfo: function() {

View file

@ -18,6 +18,12 @@ var MatrixClientPeg = require("./MatrixClientPeg");
import { _t } from './languageHandler'; import { _t } from './languageHandler';
module.exports = { module.exports = {
usersTypingApartFromMeAndIgnored: function(room) {
return this.usersTyping(
room, [MatrixClientPeg.get().credentials.userId].concat(MatrixClientPeg.get().getIgnoredUsers())
);
},
usersTypingApartFromMe: function(room) { usersTypingApartFromMe: function(room) {
return this.usersTyping( return this.usersTyping(
room, [MatrixClientPeg.get().credentials.userId] room, [MatrixClientPeg.get().credentials.userId]

View file

@ -16,7 +16,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import { _t } from '../languageHandler'; import { _t, _td } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import FuzzyMatcher from './FuzzyMatcher'; import FuzzyMatcher from './FuzzyMatcher';
import {TextualCompletion} from './Components'; import {TextualCompletion} from './Components';
@ -27,72 +27,82 @@ const COMMANDS = [
{ {
command: '/me', command: '/me',
args: '<message>', args: '<message>',
description: 'Displays action', description: _td('Displays action'),
}, },
{ {
command: '/ban', command: '/ban',
args: '<user-id> [reason]', args: '<user-id> [reason]',
description: 'Bans user with given id', description: _td('Bans user with given id'),
}, },
{ {
command: '/unban', command: '/unban',
args: '<user-id>', args: '<user-id>',
description: 'Unbans user with given id', description: _td('Unbans user with given id'),
}, },
{ {
command: '/op', command: '/op',
args: '<user-id> [<power-level>]', args: '<user-id> [<power-level>]',
description: 'Define the power level of a user', description: _td('Define the power level of a user'),
}, },
{ {
command: '/deop', command: '/deop',
args: '<user-id>', args: '<user-id>',
description: 'Deops user with given id', description: _td('Deops user with given id'),
}, },
{ {
command: '/invite', command: '/invite',
args: '<user-id>', args: '<user-id>',
description: 'Invites user with given id to current room', description: _td('Invites user with given id to current room'),
}, },
{ {
command: '/join', command: '/join',
args: '<room-alias>', args: '<room-alias>',
description: 'Joins room with given alias', description: _td('Joins room with given alias'),
}, },
{ {
command: '/part', command: '/part',
args: '[<room-alias>]', args: '[<room-alias>]',
description: 'Leave room', description: _td('Leave room'),
}, },
{ {
command: '/topic', command: '/topic',
args: '<topic>', args: '<topic>',
description: 'Sets the room topic', description: _td('Sets the room topic'),
}, },
{ {
command: '/kick', command: '/kick',
args: '<user-id> [reason]', args: '<user-id> [reason]',
description: 'Kicks user with given id', description: _td('Kicks user with given id'),
}, },
{ {
command: '/nick', command: '/nick',
args: '<display-name>', args: '<display-name>',
description: 'Changes your display nickname', description: _td('Changes your display nickname'),
}, },
{ {
command: '/ddg', command: '/ddg',
args: '<query>', args: '<query>',
description: 'Searches DuckDuckGo for results', description: _td('Searches DuckDuckGo for results'),
}, },
{ {
command: '/tint', command: '/tint',
args: '<color1> [<color2>]', args: '<color1> [<color2>]',
description: 'Changes colour scheme of current room', description: _td('Changes colour scheme of current room'),
}, },
{ {
command: '/verify', command: '/verify',
args: '<user-id> <device-id> <device-signing-key>', args: '<user-id> <device-id> <device-signing-key>',
description: 'Verifies a user, device, and pubkey tuple', description: _td('Verifies a user, device, and pubkey tuple'),
},
{
command: '/ignore',
args: '<user-id>',
description: _td('Ignores a user, hiding their messages from you'),
},
{
command: '/unignore',
args: '<user-id>',
description: _td('Stops ignoring a user, showing their messages going forward'),
}, },
// Omitting `/markdown` as it only seems to apply to OldComposer // Omitting `/markdown` as it only seems to apply to OldComposer
]; ];

View file

@ -25,6 +25,7 @@ import {PillCompletion} from './Components';
import type {SelectionRange, Completion} from './Autocompleter'; import type {SelectionRange, Completion} from './Autocompleter';
import _uniq from 'lodash/uniq'; import _uniq from 'lodash/uniq';
import _sortBy from 'lodash/sortBy'; import _sortBy from 'lodash/sortBy';
import UserSettingsStore from '../UserSettingsStore';
import EmojiData from '../stripped-emoji.json'; import EmojiData from '../stripped-emoji.json';
@ -96,6 +97,10 @@ export default class EmojiProvider extends AutocompleteProvider {
} }
async getCompletions(query: string, selection: SelectionRange) { async getCompletions(query: string, selection: SelectionRange) {
if (UserSettingsStore.getSyncedSetting("MessageComposerInput.dontSuggestEmoji")) {
return []; // don't give any suggestions if the user doesn't want them
}
const EmojiText = sdk.getComponent('views.elements.EmojiText'); const EmojiText = sdk.getComponent('views.elements.EmojiText');
let completions = []; let completions = [];

View file

@ -33,7 +33,8 @@ const USER_REGEX = /@\S*/g;
let instance = null; let instance = null;
export default class UserProvider extends AutocompleteProvider { export default class UserProvider extends AutocompleteProvider {
users: Array<RoomMember> = []; users: Array<RoomMember> = null;
room: Room = null;
constructor() { constructor() {
super(USER_REGEX, { super(USER_REGEX, {
@ -54,6 +55,9 @@ export default class UserProvider extends AutocompleteProvider {
return []; return [];
} }
// lazy-load user list into matcher
if (this.users === null) this._makeUsers();
let completions = []; let completions = [];
let {command, range} = this.getCurrentCommand(query, selection, force); let {command, range} = this.getCurrentCommand(query, selection, force);
if (command) { if (command) {
@ -83,7 +87,12 @@ export default class UserProvider extends AutocompleteProvider {
} }
setUserListFromRoom(room: Room) { setUserListFromRoom(room: Room) {
const events = room.getLiveTimeline().getEvents(); this.room = room;
this.users = null;
}
_makeUsers() {
const events = this.room.getLiveTimeline().getEvents();
const lastSpoken = {}; const lastSpoken = {};
for(const event of events) { for(const event of events) {
@ -91,7 +100,7 @@ export default class UserProvider extends AutocompleteProvider {
} }
const currentUserId = MatrixClientPeg.get().credentials.userId; const currentUserId = MatrixClientPeg.get().credentials.userId;
this.users = room.getJoinedMembers().filter((member) => { this.users = this.room.getJoinedMembers().filter((member) => {
if (member.userId !== currentUserId) return true; if (member.userId !== currentUserId) return true;
}); });
@ -103,6 +112,7 @@ export default class UserProvider extends AutocompleteProvider {
} }
onUserSpoke(user: RoomMember) { onUserSpoke(user: RoomMember) {
if (this.users === null) return;
if (user.userId === MatrixClientPeg.get().credentials.userId) return; if (user.userId === MatrixClientPeg.get().credentials.userId) return;
// Move the user that spoke to the front of the array // Move the user that spoke to the front of the array

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Vector Creations Ltd. Copyright 2017 Vector Creations Ltd.
Copyright 2017 New Vector 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.
@ -16,6 +17,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Promise from 'bluebird';
import MatrixClientPeg from '../../MatrixClientPeg'; import MatrixClientPeg from '../../MatrixClientPeg';
import sdk from '../../index'; import sdk from '../../index';
import dis from '../../dispatcher'; import dis from '../../dispatcher';
@ -25,6 +27,9 @@ import AccessibleButton from '../views/elements/AccessibleButton';
import Modal from '../../Modal'; import Modal from '../../Modal';
import classnames from 'classnames'; import classnames from 'classnames';
import GroupStoreCache from '../../stores/GroupStoreCache';
import GroupStore from '../../stores/GroupStore';
const RoomSummaryType = PropTypes.shape({ const RoomSummaryType = PropTypes.shape({
room_id: PropTypes.string.isRequired, room_id: PropTypes.string.isRequired,
profile: PropTypes.shape({ profile: PropTypes.shape({
@ -37,6 +42,9 @@ const RoomSummaryType = PropTypes.shape({
const UserSummaryType = PropTypes.shape({ const UserSummaryType = PropTypes.shape({
summaryInfo: PropTypes.shape({ summaryInfo: PropTypes.shape({
user_id: PropTypes.string.isRequired, user_id: PropTypes.string.isRequired,
role_id: PropTypes.string,
avatar_url: PropTypes.string,
displayname: PropTypes.string,
}).isRequired, }).isRequired,
}); });
@ -50,19 +58,79 @@ const CategoryRoomList = React.createClass({
name: PropTypes.string, name: PropTypes.string,
}).isRequired, }).isRequired,
}), }),
groupId: PropTypes.string.isRequired,
// Whether the list should be editable
editing: PropTypes.bool.isRequired,
},
onAddRoomsClicked: function(ev) {
ev.preventDefault();
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
title: _t('Add rooms to the group summary'),
description: _t("Which rooms would you like to add to this summary?"),
placeholder: _t("Room name or alias"),
button: _t("Add to summary"),
pickerType: 'room',
validAddressTypes: ['mx-room-id'],
groupId: this.props.groupId,
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
Promise.all(addrs.map((addr) => {
return this.context.groupStore
.addRoomToGroupSummary(addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
});
},
});
}, },
render: function() { render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddRoomsClicked}>
<TintableSvg src="img/icons-create-room.svg" width="64" height="64" />
<div className="mx_GroupView_featuredThings_addButton_label">
{ _t('Add a Room') }
</div>
</AccessibleButton>) : <div />;
const roomNodes = this.props.rooms.map((r) => { const roomNodes = this.props.rooms.map((r) => {
return <FeaturedRoom key={r.room_id} summaryInfo={r} />; return <FeaturedRoom
key={r.room_id}
groupId={this.props.groupId}
editing={this.props.editing}
summaryInfo={r} />;
}); });
let catHeader = null;
let catHeader = <div />;
if (this.props.category && this.props.category.profile) { if (this.props.category && this.props.category.profile) {
catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>; catHeader = <div className="mx_GroupView_featuredThings_category">
{ this.props.category.profile.name }
</div>;
} }
return <div> return <div className="mx_GroupView_featuredThings_container">
{ catHeader } { catHeader }
{ roomNodes } { roomNodes }
{ addButton }
</div>; </div>;
}, },
}); });
@ -72,6 +140,8 @@ const FeaturedRoom = React.createClass({
props: { props: {
summaryInfo: RoomSummaryType.isRequired, summaryInfo: RoomSummaryType.isRequired,
editing: PropTypes.bool.isRequired,
groupId: PropTypes.string.isRequired,
}, },
onClick: function(e) { onClick: function(e) {
@ -85,28 +155,69 @@ const FeaturedRoom = React.createClass({
}); });
}, },
onDeleteClicked: function(e) {
e.preventDefault();
e.stopPropagation();
this.context.groupStore.removeRoomFromGroupSummary(
this.props.summaryInfo.room_id,
).catch((err) => {
console.error('Error whilst removing room from group summary', err);
const roomName = this.props.summaryInfo.name ||
this.props.summaryInfo.canonical_alias ||
this.props.summaryInfo.room_id;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to remove room from group summary',
'', ErrorDialog,
{
title: _t(
"Failed to remove the room from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
});
});
},
render: function() { render: function() {
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
const roomName = this.props.summaryInfo.profile.name ||
this.props.summaryInfo.profile.canonical_alias ||
_t("Unnamed Room");
const oobData = { const oobData = {
roomId: this.props.summaryInfo.room_id, roomId: this.props.summaryInfo.room_id,
avatarUrl: this.props.summaryInfo.profile.avatar_url, avatarUrl: this.props.summaryInfo.profile.avatar_url,
name: this.props.summaryInfo.profile.name, name: roomName,
}; };
let permalink = null; let permalink = null;
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) { if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias; permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias;
} }
let roomNameNode = null; let roomNameNode = null;
if (permalink) { if (permalink) {
roomNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.profile.name}</a>; roomNameNode = <a href={permalink} onClick={this.onClick} >{ roomName }</a>;
} else { } else {
roomNameNode = <span>{this.props.summaryInfo.profile.name}</span>; roomNameNode = <span>{ roomName }</span>;
} }
const deleteButton = this.props.editing ?
<img
className="mx_GroupView_featuredThing_deleteButton"
src="img/cancel-small.svg"
width="14"
height="14"
alt="Delete"
onClick={this.onDeleteClicked} />
: <div />;
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}> return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
<RoomAvatar oobData={oobData} width={64} height={64} /> <RoomAvatar oobData={oobData} width={64} height={64} />
<div className="mx_GroupView_featuredThing_name">{ roomNameNode }</div> <div className="mx_GroupView_featuredThing_name">{ roomNameNode }</div>
{ deleteButton }
</AccessibleButton>; </AccessibleButton>;
}, },
}); });
@ -121,19 +232,75 @@ const RoleUserList = React.createClass({
name: PropTypes.string, name: PropTypes.string,
}).isRequired, }).isRequired,
}), }),
groupId: PropTypes.string.isRequired,
// Whether the list should be editable
editing: PropTypes.bool.isRequired,
},
onAddUsersClicked: function(ev) {
ev.preventDefault();
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, {
title: _t('Add users to the group summary'),
description: _t("Who would you like to add to this summary?"),
placeholder: _t("Name or matrix ID"),
button: _t("Add to summary"),
validAddressTypes: ['mx-user-id'],
groupId: this.props.groupId,
shouldOmitSelf: false,
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
Promise.all(addrs.map((addr) => {
return this.context.groupStore
.addUserToGroupSummary(addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following users to the group summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following users to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
});
},
});
}, },
render: function() { render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
<TintableSvg src="img/icons-create-room.svg" width="64" height="64" />
<div className="mx_GroupView_featuredThings_addButton_label">
{ _t('Add a User') }
</div>
</AccessibleButton>) : <div />;
const userNodes = this.props.users.map((u) => { const userNodes = this.props.users.map((u) => {
return <FeaturedUser key={u.user_id} summaryInfo={u} />; return <FeaturedUser
key={u.user_id}
summaryInfo={u}
editing={this.props.editing}
groupId={this.props.groupId} />;
}); });
let roleHeader = null; let roleHeader = <div />;
if (this.props.role && this.props.role.profile) { if (this.props.role && this.props.role.profile) {
roleHeader = <div className="mx_GroupView_featuredThings_category">{ this.props.role.profile.name }</div>; roleHeader = <div className="mx_GroupView_featuredThings_category">{ this.props.role.profile.name }</div>;
} }
return <div> return <div className="mx_GroupView_featuredThings_container">
{ roleHeader } { roleHeader }
{ userNodes } { userNodes }
{ addButton }
</div>; </div>;
}, },
}); });
@ -143,6 +310,8 @@ const FeaturedUser = React.createClass({
props: { props: {
summaryInfo: UserSummaryType.isRequired, summaryInfo: UserSummaryType.isRequired,
editing: PropTypes.bool.isRequired,
groupId: PropTypes.string.isRequired,
}, },
onClick: function(e) { onClick: function(e) {
@ -156,19 +325,64 @@ const FeaturedUser = React.createClass({
}); });
}, },
onDeleteClicked: function(e) {
e.preventDefault();
e.stopPropagation();
this.context.groupStore.removeUserFromGroupSummary(
this.props.summaryInfo.user_id,
).catch((err) => {
console.error('Error whilst removing user from group summary', err);
const displayName = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to remove user from group summary',
'', ErrorDialog,
{
title: _t(
"Failed to remove a user from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
});
});
},
render: function() { render: function() {
// Add avatar once we get profile info inline in the summary response const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
//const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id; const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
const userNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.user_id}</a>; const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
const httpUrl = MatrixClientPeg.get()
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
const deleteButton = this.props.editing ?
<img
className="mx_GroupView_featuredThing_deleteButton"
src="img/cancel-small.svg"
width="14"
height="14"
alt="Delete"
onClick={this.onDeleteClicked} />
: <div />;
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}> return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
<BaseAvatar name={name} url={httpUrl} width={64} height={64} />
<div className="mx_GroupView_featuredThing_name">{ userNameNode }</div> <div className="mx_GroupView_featuredThing_name">{ userNameNode }</div>
{ deleteButton }
</AccessibleButton>; </AccessibleButton>;
}, },
}); });
const GroupContext = {
groupStore: React.PropTypes.instanceOf(GroupStore).isRequired,
};
CategoryRoomList.contextTypes = GroupContext;
FeaturedRoom.contextTypes = GroupContext;
RoleUserList.contextTypes = GroupContext;
FeaturedUser.contextTypes = GroupContext;
export default React.createClass({ export default React.createClass({
displayName: 'GroupView', displayName: 'GroupView',
@ -176,6 +390,16 @@ export default React.createClass({
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
}, },
childContextTypes: {
groupStore: React.PropTypes.instanceOf(GroupStore),
},
getChildContext: function() {
return {
groupStore: this._groupStore,
};
},
getInitialState: function() { getInitialState: function() {
return { return {
summary: null, summary: null,
@ -183,12 +407,21 @@ export default React.createClass({
editing: false, editing: false,
saving: false, saving: false,
uploadingAvatar: false, uploadingAvatar: false,
membershipBusy: false,
publicityBusy: false,
}; };
}, },
componentWillMount: function() { componentWillMount: function() {
this._changeAvatarComponent = null; this._changeAvatarComponent = null;
this._loadGroupFromServer(this.props.groupId); this._initGroupStore(this.props.groupId);
MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership);
},
componentWillUnmount: function() {
MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
this._groupStore.removeAllListeners();
}, },
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
@ -197,18 +430,26 @@ export default React.createClass({
summary: null, summary: null,
error: null, error: null,
}, () => { }, () => {
this._loadGroupFromServer(newProps.groupId); this._initGroupStore(newProps.groupId);
}); });
} }
}, },
_loadGroupFromServer: function(groupId) { _onGroupMyMembership: function(group) {
MatrixClientPeg.get().getGroupSummary(groupId).done((res) => { if (group.groupId !== this.props.groupId) return;
this.setState({membershipBusy: false});
},
_initGroupStore: function(groupId) {
this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
this._groupStore.on('update', () => {
this.setState({ this.setState({
summary: res, summary: this._groupStore.getSummary(),
error: null, error: null,
}); });
}, (err) => { });
this._groupStore.on('error', (err) => {
this.setState({ this.setState({
summary: null, summary: null,
error: err, error: err,
@ -216,6 +457,10 @@ export default React.createClass({
}); });
}, },
_onShowRhsClick: function(ev) {
dis.dispatch({ action: 'show_right_panel' });
},
_onEditClick: function() { _onEditClick: function() {
this.setState({ this.setState({
editing: true, editing: true,
@ -281,7 +526,7 @@ export default React.createClass({
editing: false, editing: false,
summary: null, summary: null,
}); });
this._loadGroupFromServer(this.props.groupId); this._initGroupStore(this.props.groupId);
}).catch((e) => { }).catch((e) => {
this.setState({ this.setState({
saving: false, saving: false,
@ -295,10 +540,80 @@ export default React.createClass({
}).done(); }).done();
}, },
_getFeaturedRoomsNode() { _onAcceptInviteClick: function() {
const summary = this.state.summary; this.setState({membershipBusy: true});
MatrixClientPeg.get().acceptGroupInvite(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error accepting invite', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to accept invite"),
});
});
},
if (summary.rooms_section.rooms.length == 0) return null; _onRejectInviteClick: function() {
this.setState({membershipBusy: true});
MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to reject invite"),
});
});
},
_onLeaveClick: function() {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Leave Group', '', QuestionDialog, {
title: _t("Leave Group"),
description: _t("Leave %(groupName)s?", {groupName: this.props.groupId}),
button: _t("Leave"),
danger: true,
onFinished: (confirmed) => {
if (!confirmed) return;
this.setState({membershipBusy: true});
MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error leaving room', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to leave room"),
});
});
},
});
},
_onPubliciseOffClick: function() {
this._setPublicity(false);
},
_onPubliciseOnClick: function() {
this._setPublicity(true);
},
_setPublicity: function(publicity) {
this.setState({
publicityBusy: true,
});
this._groupStore.setGroupPublicity(publicity).then(() => {
this.setState({
publicityBusy: false,
});
});
},
_getFeaturedRoomsNode: function() {
const summary = this.state.summary;
const defaultCategoryRooms = []; const defaultCategoryRooms = [];
const categoryRooms = {}; const categoryRooms = {};
@ -315,13 +630,18 @@ export default React.createClass({
} }
}); });
let defaultCategoryNode = null; const defaultCategoryNode = <CategoryRoomList
if (defaultCategoryRooms.length > 0) { rooms={defaultCategoryRooms}
defaultCategoryNode = <CategoryRoomList rooms={defaultCategoryRooms} />; groupId={this.props.groupId}
} editing={this.state.editing} />;
const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => { const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => {
const cat = summary.rooms_section.categories[catId]; const cat = summary.rooms_section.categories[catId];
return <CategoryRoomList key={catId} rooms={categoryRooms[catId]} category={cat} />; return <CategoryRoomList
key={catId}
rooms={categoryRooms[catId]}
category={cat}
groupId={this.props.groupId}
editing={this.state.editing} />;
}); });
return <div className="mx_GroupView_featuredThings"> return <div className="mx_GroupView_featuredThings">
@ -333,11 +653,9 @@ export default React.createClass({
</div>; </div>;
}, },
_getFeaturedUsersNode() { _getFeaturedUsersNode: function() {
const summary = this.state.summary; const summary = this.state.summary;
if (summary.users_section.users.length == 0) return null;
const noRoleUsers = []; const noRoleUsers = [];
const roleUsers = {}; const roleUsers = {};
summary.users_section.users.forEach((u) => { summary.users_section.users.forEach((u) => {
@ -353,13 +671,18 @@ export default React.createClass({
} }
}); });
let noRoleNode = null; const noRoleNode = <RoleUserList
if (noRoleUsers.length > 0) { users={noRoleUsers}
noRoleNode = <RoleUserList users={noRoleUsers} />; groupId={this.props.groupId}
} editing={this.state.editing} />;
const roleUserNodes = Object.keys(roleUsers).map((roleId) => { const roleUserNodes = Object.keys(roleUsers).map((roleId) => {
const role = summary.users_section.roles[roleId]; const role = summary.users_section.roles[roleId];
return <RoleUserList key={roleId} users={roleUsers[roleId]} role={role} />; return <RoleUserList
key={roleId}
users={roleUsers[roleId]}
role={role}
groupId={this.props.groupId}
editing={this.state.editing} />;
}); });
return <div className="mx_GroupView_featuredThings"> return <div className="mx_GroupView_featuredThings">
@ -371,6 +694,98 @@ export default React.createClass({
</div>; </div>;
}, },
_getMembershipSection: function() {
const Spinner = sdk.getComponent("elements.Spinner");
const group = MatrixClientPeg.get().getGroup(this.props.groupId);
if (!group) return null;
if (group.myMembership === 'invite') {
if (this.state.membershipBusy) {
return <div className="mx_GroupView_membershipSection">
<Spinner />
</div>;
}
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_invited">
<div className="mx_GroupView_membershipSection_description">
{ _t("%(inviter)s has invited you to join this group", {inviter: group.inviter.userId}) }
</div>
<div className="mx_GroupView_membership_buttonContainer">
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onAcceptInviteClick}
>
{ _t("Accept") }
</AccessibleButton>
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onRejectInviteClick}
>
{ _t("Decline") }
</AccessibleButton>
</div>
</div>;
} else if (group.myMembership === 'join') {
let youAreAMemberText = _t("You are a member of this group");
if (this.state.summary.user && this.state.summary.user.is_privileged) {
youAreAMemberText = _t("You are an administrator of this group");
}
let publicisedButton;
if (this.state.publicityBusy) {
publicisedButton = <Spinner />;
}
let publicisedSection;
if (this.state.summary.user && this.state.summary.user.is_publicised) {
if (!this.state.publicityBusy) {
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onPubliciseOffClick}
>
{ _t("Unpublish") }
</AccessibleButton>;
}
publicisedSection = <div className="mx_GroupView_membershipSubSection">
{ _t("This group is published on your profile") }
<div className="mx_GroupView_membership_buttonContainer">
{ publicisedButton }
</div>
</div>;
} else {
if (!this.state.publicityBusy) {
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onPubliciseOnClick}
>
{ _t("Publish") }
</AccessibleButton>;
}
publicisedSection = <div className="mx_GroupView_membershipSubSection">
{ _t("This group is not published on your profile") }
<div className="mx_GroupView_membership_buttonContainer">
{ publicisedButton }
</div>
</div>;
}
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_joined">
<div className="mx_GroupView_membershipSubSection">
<div className="mx_GroupView_membershipSection_description">
{ youAreAMemberText }
</div>
<div className="mx_GroupView_membership_buttonContainer">
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onLeaveClick}
>
{ _t("Leave") }
</AccessibleButton>
</div>
</div>
{ publicisedSection }
</div>;
}
return null;
},
render: function() { render: function() {
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar"); const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
@ -384,8 +799,8 @@ export default React.createClass({
let avatarNode; let avatarNode;
let nameNode; let nameNode;
let shortDescNode; let shortDescNode;
let rightButtons;
let roomBody; let roomBody;
const rightButtons = [];
const headerClasses = { const headerClasses = {
mx_GroupView_header: true, mx_GroupView_header: true,
}; };
@ -428,20 +843,26 @@ export default React.createClass({
placeholder={_t('Description')} placeholder={_t('Description')}
tabIndex="2" tabIndex="2"
/>; />;
rightButtons = <span> rightButtons.push(
<AccessibleButton className="mx_GroupView_saveButton mx_RoomHeader_textButton" onClick={this._onSaveClick}> <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onSaveClick} key="_saveButton"
>
{ _t('Save') } { _t('Save') }
</AccessibleButton> </AccessibleButton>,
<AccessibleButton className='mx_GroupView_cancelButton' onClick={this._onCancelClick}> );
<img src="img/cancel.svg" className='mx_filterFlipColor' rightButtons.push(
<AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this._onCancelClick} key="_cancelButton">
<img src="img/cancel.svg" className="mx_filterFlipColor"
width="18" height="18" alt={_t("Cancel")} /> width="18" height="18" alt={_t("Cancel")} />
</AccessibleButton> </AccessibleButton>,
</span>; );
roomBody = <div> roomBody = <div>
<textarea className="mx_GroupView_editLongDesc" value={this.state.profileForm.long_description} <textarea className="mx_GroupView_editLongDesc" value={this.state.profileForm.long_description}
onChange={this._onLongDescChange} onChange={this._onLongDescChange}
tabIndex="3" tabIndex="3"
/> />
{ this._getFeaturedRoomsNode() }
{ this._getFeaturedUsersNode() }
</div>; </div>;
} else { } else {
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null; const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
@ -467,16 +888,29 @@ export default React.createClass({
description = sanitizedHtmlNode(summary.profile.long_description); description = sanitizedHtmlNode(summary.profile.long_description);
} }
roomBody = <div> roomBody = <div>
{ this._getMembershipSection() }
<div className="mx_GroupView_groupDesc">{ description }</div> <div className="mx_GroupView_groupDesc">{ description }</div>
{ this._getFeaturedRoomsNode() } { this._getFeaturedRoomsNode() }
{ this._getFeaturedUsersNode() } { this._getFeaturedUsersNode() }
</div>; </div>;
// disabled until editing works if (summary.user && summary.user.is_privileged) {
rightButtons = <AccessibleButton className="mx_GroupHeader_button" rightButtons.push(
onClick={this._onEditClick} title={_t("Edit Group")} <AccessibleButton className="mx_GroupHeader_button"
onClick={this._onEditClick} title={_t("Edit Group")} key="_editButton"
> >
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16" /> <TintableSvg src="img/icons-settings-room.svg" width="16" height="16" />
</AccessibleButton>; </AccessibleButton>,
);
}
if (this.props.collapsedRhs) {
rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button"
onClick={this._onShowRhsClick} title={_t('Show panel')} key="_maximiseButton"
>
<TintableSvg src="img/maximise.svg" width="10" height="16" />
</AccessibleButton>,
);
}
headerClasses.mx_GroupView_header_view = true; headerClasses.mx_GroupView_header_view = true;
} }

View file

@ -81,10 +81,6 @@ export default React.createClass({
// stash the MatrixClient in case we log out before we are unmounted // stash the MatrixClient in case we log out before we are unmounted
this._matrixClient = this.props.matrixClient; this._matrixClient = this.props.matrixClient;
// _scrollStateMap is a map from room id to the scroll state returned by
// RoomView.getScrollState()
this._scrollStateMap = {};
CallMediaHandler.loadDevices(); CallMediaHandler.loadDevices();
document.addEventListener('keydown', this._onKeyDown); document.addEventListener('keydown', this._onKeyDown);
@ -116,10 +112,6 @@ export default React.createClass({
return Boolean(MatrixClientPeg.get()); return Boolean(MatrixClientPeg.get());
}, },
getScrollStateForRoom: function(roomId) {
return this._scrollStateMap[roomId];
},
canResetTimelineInRoom: function(roomId) { canResetTimelineInRoom: function(roomId) {
if (!this.refs.roomView) { if (!this.refs.roomView) {
return true; return true;
@ -139,6 +131,9 @@ export default React.createClass({
useCompactLayout: event.getContent().useCompactLayout, useCompactLayout: event.getContent().useCompactLayout,
}); });
} }
if (event.getType() === "m.ignored_user_list") {
dis.dispatch({action: "ignore_state_changed"});
}
}, },
_onKeyDown: function(ev) { _onKeyDown: function(ev) {
@ -246,11 +241,10 @@ export default React.createClass({
eventPixelOffset={this.props.initialEventPixelOffset} eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomId || 'roomview'} key={this.props.currentRoomId || 'roomview'}
opacity={this.props.middleOpacity} opacity={this.props.middleOpacity}
collapsedRhs={this.props.collapse_rhs} collapsedRhs={this.props.collapseRhs}
ConferenceHandler={this.props.ConferenceHandler} ConferenceHandler={this.props.ConferenceHandler}
scrollStateMap={this._scrollStateMap}
/>; />;
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.rightOpacity} />; if (!this.props.collapseRhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.rightOpacity} />;
break; break;
case PageTypes.UserSettings: case PageTypes.UserSettings:
@ -261,7 +255,7 @@ export default React.createClass({
referralBaseUrl={this.props.config.referralBaseUrl} referralBaseUrl={this.props.config.referralBaseUrl}
teamToken={this.props.teamToken} teamToken={this.props.teamToken}
/>; />;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>; if (!this.props.collapseRhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
break; break;
case PageTypes.MyGroups: case PageTypes.MyGroups:
@ -271,9 +265,9 @@ export default React.createClass({
case PageTypes.CreateRoom: case PageTypes.CreateRoom:
page_element = <CreateRoom page_element = <CreateRoom
onRoomCreated={this.props.onRoomCreated} onRoomCreated={this.props.onRoomCreated}
collapsedRhs={this.props.collapse_rhs} collapsedRhs={this.props.collapseRhs}
/>; />;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>; if (!this.props.collapseRhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
break; break;
case PageTypes.RoomDirectory: case PageTypes.RoomDirectory:
@ -306,8 +300,9 @@ export default React.createClass({
case PageTypes.GroupView: case PageTypes.GroupView:
page_element = <GroupView page_element = <GroupView
groupId={this.props.currentGroupId} groupId={this.props.currentGroupId}
collapsedRhs={this.props.collapseRhs}
/>; />;
//right_panel = <RightPanel opacity={this.props.rightOpacity} />; if (!this.props.collapseRhs) right_panel = <RightPanel groupId={this.props.currentGroupId} opacity={this.props.rightOpacity} />;
break; break;
} }
@ -339,7 +334,7 @@ export default React.createClass({
<div className={bodyClasses}> <div className={bodyClasses}>
<LeftPanel <LeftPanel
selectedRoom={this.props.currentRoomId} selectedRoom={this.props.currentRoomId}
collapsed={this.props.collapse_lhs || false} collapsed={this.props.collapseLhs || false}
opacity={this.props.leftOpacity} opacity={this.props.leftOpacity}
/> />
<main className='mx_MatrixChat_middlePanel'> <main className='mx_MatrixChat_middlePanel'>

View file

@ -32,13 +32,12 @@ import dis from "../../dispatcher";
import Modal from "../../Modal"; import Modal from "../../Modal";
import Tinter from "../../Tinter"; import Tinter from "../../Tinter";
import sdk from '../../index'; import sdk from '../../index';
import { showStartChatInviteDialog, showRoomInviteDialog } from '../../Invite'; import { showStartChatInviteDialog, showRoomInviteDialog } from '../../RoomInvite';
import * as Rooms from '../../Rooms'; import * as Rooms from '../../Rooms';
import linkifyMatrix from "../../linkify-matrix"; import linkifyMatrix from "../../linkify-matrix";
import * as Lifecycle from '../../Lifecycle'; import * as Lifecycle from '../../Lifecycle';
// LifecycleStore is not used but does listen to and dispatch actions // LifecycleStore is not used but does listen to and dispatch actions
require('../../stores/LifecycleStore'); require('../../stores/LifecycleStore');
import RoomViewStore from '../../stores/RoomViewStore';
import PageTypes from '../../PageTypes'; import PageTypes from '../../PageTypes';
import createRoom from "../../createRoom"; import createRoom from "../../createRoom";
@ -144,8 +143,8 @@ module.exports = React.createClass({
// If we're trying to just view a user ID (i.e. /user URL), this is it // If we're trying to just view a user ID (i.e. /user URL), this is it
viewUserId: null, viewUserId: null,
collapse_lhs: false, collapseLhs: false,
collapse_rhs: false, collapseRhs: false,
leftOpacity: 1.0, leftOpacity: 1.0,
middleOpacity: 1.0, middleOpacity: 1.0,
rightOpacity: 1.0, rightOpacity: 1.0,
@ -214,9 +213,6 @@ module.exports = React.createClass({
componentWillMount: function() { componentWillMount: function() {
SdkConfig.put(this.props.config); SdkConfig.put(this.props.config);
this._roomViewStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdated);
this._onRoomViewStoreUpdated();
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable(); if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
// Used by _viewRoom before getting state from sync // Used by _viewRoom before getting state from sync
@ -353,7 +349,6 @@ module.exports = React.createClass({
UDEHandler.stopListening(); UDEHandler.stopListening();
window.removeEventListener("focus", this.onFocus); window.removeEventListener("focus", this.onFocus);
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
this._roomViewStoreToken.remove();
}, },
componentDidUpdate: function() { componentDidUpdate: function() {
@ -439,7 +434,7 @@ module.exports = React.createClass({
break; break;
case 'view_user': case 'view_user':
// FIXME: ugly hack to expand the RightPanel and then re-dispatch. // FIXME: ugly hack to expand the RightPanel and then re-dispatch.
if (this.state.collapse_rhs) { if (this.state.collapseRhs) {
setTimeout(()=>{ setTimeout(()=>{
dis.dispatch({ dis.dispatch({
action: 'show_right_panel', action: 'show_right_panel',
@ -521,22 +516,22 @@ module.exports = React.createClass({
break; break;
case 'hide_left_panel': case 'hide_left_panel':
this.setState({ this.setState({
collapse_lhs: true, collapseLhs: true,
}); });
break; break;
case 'show_left_panel': case 'show_left_panel':
this.setState({ this.setState({
collapse_lhs: false, collapseLhs: false,
}); });
break; break;
case 'hide_right_panel': case 'hide_right_panel':
this.setState({ this.setState({
collapse_rhs: true, collapseRhs: true,
}); });
break; break;
case 'show_right_panel': case 'show_right_panel':
this.setState({ this.setState({
collapse_rhs: false, collapseRhs: false,
}); });
break; break;
case 'ui_opacity': { case 'ui_opacity': {
@ -587,10 +582,6 @@ module.exports = React.createClass({
} }
}, },
_onRoomViewStoreUpdated: function() {
this.setState({ currentRoomId: RoomViewStore.getRoomId() });
},
_setPage: function(pageType) { _setPage: function(pageType) {
this.setState({ this.setState({
page_type: pageType, page_type: pageType,
@ -677,10 +668,10 @@ module.exports = React.createClass({
this.focusComposer = true; this.focusComposer = true;
const newState = { const newState = {
currentRoomId: roomInfo.room_id || null,
page_type: PageTypes.RoomView, page_type: PageTypes.RoomView,
thirdPartyInvite: roomInfo.third_party_invite, thirdPartyInvite: roomInfo.third_party_invite,
roomOobData: roomInfo.oob_data, roomOobData: roomInfo.oob_data,
autoJoin: roomInfo.auto_join,
}; };
if (roomInfo.room_alias) { if (roomInfo.room_alias) {
@ -1000,8 +991,8 @@ module.exports = React.createClass({
this.setStateForNewView({ this.setStateForNewView({
view: VIEWS.LOGIN, view: VIEWS.LOGIN,
ready: false, ready: false,
collapse_lhs: false, collapseLhs: false,
collapse_rhs: false, collapseRhs: false,
currentRoomId: null, currentRoomId: null,
page_type: PageTypes.RoomDirectory, page_type: PageTypes.RoomDirectory,
}); });
@ -1066,10 +1057,13 @@ module.exports = React.createClass({
self.setState({ready: true}); self.setState({ready: true});
}); });
cli.on('Call.incoming', function(call) { cli.on('Call.incoming', function(call) {
// we dispatch this synchronously to make sure that the event
// handlers on the call are set up immediately (so that if
// we get an immediate hangup, we don't get a stuck call)
dis.dispatch({ dis.dispatch({
action: 'incoming_call', action: 'incoming_call',
call: call, call: call,
}); }, true);
}); });
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");

View file

@ -65,7 +65,7 @@ module.exports = React.createClass({
suppressFirstDateSeparator: React.PropTypes.bool, suppressFirstDateSeparator: React.PropTypes.bool,
// whether to show read receipts // whether to show read receipts
manageReadReceipts: React.PropTypes.bool, showReadReceipts: React.PropTypes.bool,
// true if updates to the event list should cause the scroll panel to // true if updates to the event list should cause the scroll panel to
// scroll down when we are at the bottom of the window. See ScrollPanel // scroll down when we are at the bottom of the window. See ScrollPanel
@ -241,6 +241,10 @@ module.exports = React.createClass({
// TODO: Implement granular (per-room) hide options // TODO: Implement granular (per-room) hide options
_shouldShowEvent: function(mxEv) { _shouldShowEvent: function(mxEv) {
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
return false; // ignored = no show (only happens if the ignore happens after an event was received)
}
const EventTile = sdk.getComponent('rooms.EventTile'); const EventTile = sdk.getComponent('rooms.EventTile');
if (!EventTile.haveTileForEvent(mxEv)) { if (!EventTile.haveTileForEvent(mxEv)) {
return false; // no tile = no show return false; // no tile = no show
@ -339,6 +343,15 @@ module.exports = React.createClass({
for (;i + 1 < this.props.events.length; i++) { for (;i + 1 < this.props.events.length; i++) {
const collapsedMxEv = this.props.events[i + 1]; const collapsedMxEv = this.props.events[i + 1];
// Ignore redacted/hidden member events
if (!this._shouldShowEvent(collapsedMxEv)) {
// If this hidden event is the RM and in or at end of a MELS put RM after MELS.
if (collapsedMxEv.getId() === this.props.readMarkerEventId) {
readMarkerInMels = true;
}
continue;
}
if (!isMembershipChange(collapsedMxEv) || if (!isMembershipChange(collapsedMxEv) ||
this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) { this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) {
break; break;
@ -349,16 +362,16 @@ module.exports = React.createClass({
readMarkerInMels = true; readMarkerInMels = true;
} }
// Ignore redacted/hidden member events
if (!this._shouldShowEvent(collapsedMxEv)) {
continue;
}
summarisedEvents.push(collapsedMxEv); summarisedEvents.push(collapsedMxEv);
} }
let highlightInMels = false;
// At this point, i = the index of the last event in the summary sequence // At this point, i = the index of the last event in the summary sequence
let eventTiles = summarisedEvents.map((e) => { let eventTiles = summarisedEvents.map((e) => {
if (e.getId() === this.props.highlightedEventId) {
highlightInMels = true;
}
// In order to prevent DateSeparators from appearing in the expanded form // In order to prevent DateSeparators from appearing in the expanded form
// of MemberEventListSummary, render each member event as if the previous // of MemberEventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the // one was itself. This way, the timestamp of the previous event === the
@ -372,15 +385,13 @@ module.exports = React.createClass({
eventTiles = null; eventTiles = null;
} }
ret.push( ret.push(<MemberEventListSummary key={key}
<MemberEventListSummary
key={key}
events={summarisedEvents} events={summarisedEvents}
onToggle={this._onWidgetLoad} // Update scroll state onToggle={this._onWidgetLoad} // Update scroll state
startExpanded={highlightInMels}
> >
{eventTiles} {eventTiles}
</MemberEventListSummary> </MemberEventListSummary>);
);
if (readMarkerInMels) { if (readMarkerInMels) {
ret.push(this._getReadMarkerTile(visible)); ret.push(this._getReadMarkerTile(visible));
@ -487,7 +498,7 @@ module.exports = React.createClass({
var scrollToken = mxEv.status ? undefined : eventId; var scrollToken = mxEv.status ? undefined : eventId;
var readReceipts; var readReceipts;
if (this.props.manageReadReceipts) { if (this.props.showReadReceipts) {
readReceipts = this._getReadReceiptsForEvent(mxEv); readReceipts = this._getReadReceiptsForEvent(mxEv);
} }
ret.push( ret.push(
@ -545,6 +556,9 @@ module.exports = React.createClass({
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) { if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
return; // ignore non-read receipts and receipts from self. return; // ignore non-read receipts and receipts from self.
} }
if (MatrixClientPeg.get().isUserIgnored(r.userId)) {
return; // ignore ignored users
}
let member = room.getMember(r.userId); let member = room.getMember(r.userId);
if (!member) { if (!member) {
return; // ignore unknown user IDs return; // ignore unknown user IDs

View file

@ -102,7 +102,7 @@ export default withMatrixClient(React.createClass({
} }
return <div className="mx_MyGroups"> return <div className="mx_MyGroups">
<SimpleRoomHeader title={ _t("Groups") } /> <SimpleRoomHeader title={_t("Groups")} icon="img/icons-groups.svg" />
<div className='mx_MyGroups_joinCreateBox'> <div className='mx_MyGroups_joinCreateBox'>
<div className="mx_MyGroups_createBox"> <div className="mx_MyGroups_createBox">
<div className="mx_MyGroups_joinCreateHeader"> <div className="mx_MyGroups_joinCreateHeader">
@ -125,7 +125,7 @@ export default withMatrixClient(React.createClass({
<TintableSvg src="img/icons-create-room.svg" width="50" height="50" /> <TintableSvg src="img/icons-create-room.svg" width="50" height="50" />
</AccessibleButton> </AccessibleButton>
{ _tJsx( { _tJsx(
'To join an exisitng group you\'ll have to '+ 'To join an existing group you\'ll have to '+
'know its group identifier; this will look '+ 'know its group identifier; this will look '+
'something like <i>+example:matrix.org</i>.', 'something like <i>+example:matrix.org</i>.',
/<i>(.*)<\/i>/, /<i>(.*)<\/i>/,

View file

@ -121,7 +121,7 @@ module.exports = React.createClass({
onRoomMemberTyping: function(ev, member) { onRoomMemberTyping: function(ev, member) {
this.setState({ this.setState({
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
}); });
}, },

View file

@ -20,6 +20,8 @@ limitations under the License.
// - Drag and drop // - Drag and drop
// - File uploading - uploadFile() // - File uploading - uploadFile()
import shouldHideEvent from "../../shouldHideEvent";
var React = require("react"); var React = require("react");
var ReactDOM = require("react-dom"); var ReactDOM = require("react-dom");
import Promise from 'bluebird'; import Promise from 'bluebird';
@ -45,6 +47,7 @@ import KeyCode from '../../KeyCode';
import UserProvider from '../../autocomplete/UserProvider'; import UserProvider from '../../autocomplete/UserProvider';
import RoomViewStore from '../../stores/RoomViewStore'; import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
let DEBUG = false; let DEBUG = false;
let debuglog = function() {}; let debuglog = function() {};
@ -120,6 +123,9 @@ module.exports = React.createClass({
// store the error here. // store the error here.
roomLoadError: null, roomLoadError: null,
// Have we sent a request to join the room that we're waiting to complete?
joining: false,
// this is true if we are fully scrolled-down, and are looking at // this is true if we are fully scrolled-down, and are looking at
// the end of the live timeline. It has the effect of hiding the // the end of the live timeline. It has the effect of hiding the
// 'scroll to bottom' knob, among a couple of other things. // 'scroll to bottom' knob, among a couple of other things.
@ -143,6 +149,8 @@ module.exports = React.createClass({
MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership); MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership);
MatrixClientPeg.get().on("accountData", this.onAccountData); MatrixClientPeg.get().on("accountData", this.onAccountData);
this._syncedSettings = UserSettingsStore.getSyncedSettings();
// Start listening for RoomViewStore updates // Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true); this._onRoomViewStoreUpdate(true);
@ -152,6 +160,22 @@ module.exports = React.createClass({
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
if (!initial && this.state.roomId !== RoomViewStore.getRoomId()) {
// RoomView explicitly does not support changing what room
// is being viewed: instead it should just be re-mounted when
// switching rooms. Therefore, if the room ID changes, we
// ignore this. We either need to do this or add code to handle
// saving the scroll position (otherwise we end up saving the
// scroll position against the wrong room).
// Given that doing the setState here would cause a bunch of
// unnecessary work, we just ignore the change since we know
// that if the current room ID has changed from what we thought
// it was, it means we're about to be unmounted.
return;
}
const newState = { const newState = {
roomId: RoomViewStore.getRoomId(), roomId: RoomViewStore.getRoomId(),
roomAlias: RoomViewStore.getRoomAlias(), roomAlias: RoomViewStore.getRoomAlias(),
@ -159,16 +183,11 @@ module.exports = React.createClass({
roomLoadError: RoomViewStore.getRoomLoadError(), roomLoadError: RoomViewStore.getRoomLoadError(),
joining: RoomViewStore.isJoining(), joining: RoomViewStore.isJoining(),
initialEventId: RoomViewStore.getInitialEventId(), initialEventId: RoomViewStore.getInitialEventId(),
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(), isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
forwardingEvent: RoomViewStore.getForwardingEvent(), forwardingEvent: RoomViewStore.getForwardingEvent(),
shouldPeek: RoomViewStore.shouldPeek(), shouldPeek: RoomViewStore.shouldPeek(),
}; };
// finished joining, start waiting for a room and show a spinner. See onRoom.
newState.waitingForRoom = this.state.joining && !newState.joining &&
!RoomViewStore.getJoinError();
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307 // Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
console.log( console.log(
'RVS update:', 'RVS update:',
@ -177,7 +196,6 @@ module.exports = React.createClass({
'loading?', newState.roomLoading, 'loading?', newState.roomLoading,
'joining?', newState.joining, 'joining?', newState.joining,
'initial?', initial, 'initial?', initial,
'waiting?', newState.waitingForRoom,
'shouldPeek?', newState.shouldPeek, 'shouldPeek?', newState.shouldPeek,
); );
@ -185,6 +203,25 @@ module.exports = React.createClass({
// the RoomView instance // the RoomView instance
if (initial) { if (initial) {
newState.room = MatrixClientPeg.get().getRoom(newState.roomId); newState.room = MatrixClientPeg.get().getRoom(newState.roomId);
if (newState.room) {
newState.unsentMessageError = this._getUnsentMessageError(newState.room);
newState.showApps = this._shouldShowApps(newState.room);
this._onRoomLoaded(newState.room);
}
}
if (this.state.roomId === null && newState.roomId !== null) {
// Get the scroll state for the new room
// If an event ID wasn't specified, default to the one saved for this room
// in the scroll state store. Assume initialEventPixelOffset should be set.
if (!newState.initialEventId) {
const roomScrollState = RoomScrollStateStore.getScrollState(newState.roomId);
if (roomScrollState) {
newState.initialEventId = roomScrollState.focussedEvent;
newState.initialEventPixelOffset = roomScrollState.pixelOffset;
}
}
} }
// Clear the search results when clicking a search result (which changes the // Clear the search results when clicking a search result (which changes the
@ -193,22 +230,20 @@ module.exports = React.createClass({
newState.searchResults = null; newState.searchResults = null;
} }
// Store the scroll state for the previous room so that we can return to this this.setState(newState);
// position when viewing this room in future. // At this point, newState.roomId could be null (e.g. the alias might not
if (this.state.roomId !== newState.roomId) {
this._updateScrollMap(this.state.roomId);
}
this.setState(newState, () => {
// At this point, this.state.roomId could be null (e.g. the alias might not
// have been resolved yet) so anything called here must handle this case. // have been resolved yet) so anything called here must handle this case.
// We pass the new state into this function for it to read: it needs to
// observe the new state but we don't want to put it in the setState
// callback because this would prevent the setStates from being batched,
// ie. cause it to render RoomView twice rather than the once that is necessary.
if (initial) { if (initial) {
this._onHaveRoom(); this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek);
} }
});
}, },
_onHaveRoom: function() { _setupRoom: function(room, roomId, joining, shouldPeek) {
// if this is an unknown room then we're in one of three states: // if this is an unknown room then we're in one of three states:
// - This is a room we can peek into (search engine) (we can /peek) // - This is a room we can peek into (search engine) (we can /peek)
// - This is a room we can publicly join or were invited to. (we can /join) // - This is a room we can publicly join or were invited to. (we can /join)
@ -224,23 +259,15 @@ module.exports = React.createClass({
// about it). We don't peek in the historical case where we were joined but are // about it). We don't peek in the historical case where we were joined but are
// now not joined because the js-sdk peeking API will clobber our historical room, // now not joined because the js-sdk peeking API will clobber our historical room,
// making it impossible to indicate a newly joined room. // making it impossible to indicate a newly joined room.
const room = this.state.room; if (!joining && roomId) {
if (room) {
this.setState({
unsentMessageError: this._getUnsentMessageError(room),
showApps: this._shouldShowApps(room),
});
this._onRoomLoaded(room);
}
if (!this.state.joining && this.state.roomId) {
if (this.props.autoJoin) { if (this.props.autoJoin) {
this.onJoinButtonClicked(); this.onJoinButtonClicked();
} else if (!room && this.state.shouldPeek) { } else if (!room && shouldPeek) {
console.log("Attempting to peek into room %s", this.state.roomId); console.log("Attempting to peek into room %s", roomId);
this.setState({ this.setState({
peekLoading: true, peekLoading: true,
}); });
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => { MatrixClientPeg.get().peekInRoom(roomId).then((room) => {
this.setState({ this.setState({
room: room, room: room,
peekLoading: false, peekLoading: false,
@ -336,7 +363,9 @@ module.exports = React.createClass({
this.unmounted = true; this.unmounted = true;
// update the scroll map before we get unmounted // update the scroll map before we get unmounted
this._updateScrollMap(this.state.roomId); if (this.state.roomId) {
RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState());
}
if (this.refs.roomView) { if (this.refs.roomView) {
// disconnect the D&D event listeners from the room view. This // disconnect the D&D event listeners from the room view. This
@ -497,8 +526,7 @@ module.exports = React.createClass({
// update unread count when scrolled up // update unread count when scrolled up
if (!this.state.searchResults && this.state.atEndOfLiveTimeline) { if (!this.state.searchResults && this.state.atEndOfLiveTimeline) {
// no change // no change
} } else if (!shouldHideEvent(ev, this._syncedSettings)) {
else {
this.setState((state, props) => { this.setState((state, props) => {
return {numUnreadMessages: state.numUnreadMessages + 1}; return {numUnreadMessages: state.numUnreadMessages + 1};
}); });
@ -614,25 +642,12 @@ module.exports = React.createClass({
}); });
}, },
_updateScrollMap(roomId) {
// No point updating scroll state if the room ID hasn't been resolved yet
if (!roomId) {
return;
}
dis.dispatch({
action: 'update_scroll_state',
room_id: roomId,
scroll_state: this._getScrollState(),
});
},
onRoom: function(room) { onRoom: function(room) {
if (!room || room.roomId !== this.state.roomId) { if (!room || room.roomId !== this.state.roomId) {
return; return;
} }
this.setState({ this.setState({
room: room, room: room,
waitingForRoom: false,
}, () => { }, () => {
this._onRoomLoaded(room); this._onRoomLoaded(room);
}); });
@ -688,15 +703,8 @@ module.exports = React.createClass({
onRoomMemberMembership: function(ev, member, oldMembership) { onRoomMemberMembership: function(ev, member, oldMembership) {
if (member.userId == MatrixClientPeg.get().credentials.userId) { if (member.userId == MatrixClientPeg.get().credentials.userId) {
if (member.membership === 'join') {
this.setState({
waitingForRoom: false,
});
} else {
this.forceUpdate(); this.forceUpdate();
} }
}
}, },
// rate limited because a power level change will emit an event for every // rate limited because a power level change will emit an event for every
@ -1445,10 +1453,6 @@ module.exports = React.createClass({
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
// Whether the preview bar spinner should be shown. We do this when joining or
// when waiting for a room to be returned by js-sdk when joining
const previewBarSpinner = this.state.joining || this.state.waitingForRoom;
if (!this.state.room) { if (!this.state.room) {
if (this.state.roomLoading || this.state.peekLoading) { if (this.state.roomLoading || this.state.peekLoading) {
return ( return (
@ -1482,7 +1486,7 @@ module.exports = React.createClass({
onRejectClick={ this.onRejectThreepidInviteButtonClicked } onRejectClick={ this.onRejectThreepidInviteButtonClicked }
canPreview={ false } error={ this.state.roomLoadError } canPreview={ false } error={ this.state.roomLoadError }
roomAlias={roomAlias} roomAlias={roomAlias}
spinner={previewBarSpinner} spinner={this.state.joining}
inviterName={inviterName} inviterName={inviterName}
invitedEmail={invitedEmail} invitedEmail={invitedEmail}
room={this.state.room} room={this.state.room}
@ -1525,7 +1529,7 @@ module.exports = React.createClass({
onRejectClick={ this.onRejectButtonClicked } onRejectClick={ this.onRejectButtonClicked }
inviterName={ inviterName } inviterName={ inviterName }
canPreview={ false } canPreview={ false }
spinner={previewBarSpinner} spinner={this.state.joining}
room={this.state.room} room={this.state.room}
/> />
</div> </div>
@ -1600,7 +1604,7 @@ module.exports = React.createClass({
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} <RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={ this.onForgetClick } onForgetClick={ this.onForgetClick }
onRejectClick={this.onRejectThreepidInviteButtonClicked} onRejectClick={this.onRejectThreepidInviteButtonClicked}
spinner={previewBarSpinner} spinner={this.state.joining}
inviterName={inviterName} inviterName={inviterName}
invitedEmail={invitedEmail} invitedEmail={invitedEmail}
canPreview={this.state.canPeek} canPreview={this.state.canPeek}
@ -1716,7 +1720,8 @@ module.exports = React.createClass({
var messagePanel = ( var messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef} <TimelinePanel ref={this._gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()} timelineSet={this.state.room.getUnfilteredTimelineSet()}
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)} showReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
manageReadReceipts={true}
manageReadMarkers={true} manageReadMarkers={true}
hidden={hideMessagePanel} hidden={hideMessagePanel}
highlightedEventId={highlightedEventId} highlightedEventId={highlightedEventId}

View file

@ -157,7 +157,7 @@ module.exports = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
this.checkFillState(); this.checkScroll();
}, },
componentDidUpdate: function() { componentDidUpdate: function() {

View file

@ -59,6 +59,7 @@ var TimelinePanel = React.createClass({
// that room. // that room.
timelineSet: React.PropTypes.object.isRequired, timelineSet: React.PropTypes.object.isRequired,
showReadReceipts: React.PropTypes.bool,
// Enable managing RRs and RMs. These require the timelineSet to have a room. // Enable managing RRs and RMs. These require the timelineSet to have a room.
manageReadReceipts: React.PropTypes.bool, manageReadReceipts: React.PropTypes.bool,
manageReadMarkers: React.PropTypes.bool, manageReadMarkers: React.PropTypes.bool,
@ -197,6 +198,7 @@ var TimelinePanel = React.createClass({
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt); MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated); MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
MatrixClientPeg.get().on("Room.accountData", this.onAccountData); MatrixClientPeg.get().on("Room.accountData", this.onAccountData);
MatrixClientPeg.get().on("Event.decrypted", this.onEventDecrypted);
MatrixClientPeg.get().on("sync", this.onSync); MatrixClientPeg.get().on("sync", this.onSync);
this._initTimeline(this.props); this._initTimeline(this.props);
@ -266,6 +268,7 @@ var TimelinePanel = React.createClass({
client.removeListener("Room.receipt", this.onRoomReceipt); client.removeListener("Room.receipt", this.onRoomReceipt);
client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated); client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
client.removeListener("Room.accountData", this.onAccountData); client.removeListener("Room.accountData", this.onAccountData);
client.removeListener("Event.decrypted", this.onEventDecrypted);
client.removeListener("sync", this.onSync); client.removeListener("sync", this.onSync);
} }
}, },
@ -341,9 +344,16 @@ var TimelinePanel = React.createClass({
newState[canPaginateOtherWayKey] = true; newState[canPaginateOtherWayKey] = true;
} }
this.setState(newState); // Don't resolve until the setState has completed: we need to let
// the component update before we consider the pagination completed,
return r; // otherwise we'll end up paginating in all the history the js-sdk
// has in memory because we never gave the component a chance to scroll
// itself into the right place
return new Promise((resolve) => {
this.setState(newState, () => {
resolve(r);
});
});
}); });
}, },
@ -374,6 +384,9 @@ var TimelinePanel = React.createClass({
this.sendReadReceipt(); this.sendReadReceipt();
this.updateReadMarker(); this.updateReadMarker();
break; break;
case 'ignore_state_changed':
this.forceUpdate();
break;
} }
}, },
@ -503,6 +516,18 @@ var TimelinePanel = React.createClass({
}, this.props.onReadMarkerUpdated); }, this.props.onReadMarkerUpdated);
}, },
onEventDecrypted: function(ev) {
// Need to update as we don't display event tiles for events that
// haven't yet been decrypted. The event will have just been updated
// in place so we just need to re-render.
// TODO: We should restrict this to only events in our timeline,
// but possibly the event tile itself should just update when this
// happens to save us re-rendering the whole timeline.
if (ev.getRoomId() === this.props.timelineSet.room.roomId) {
this.forceUpdate();
}
},
onSync: function(state, prevState, data) { onSync: function(state, prevState, data) {
this.setState({clientSyncState: state}); this.setState({clientSyncState: state});
}, },
@ -1127,7 +1152,7 @@ var TimelinePanel = React.createClass({
readMarkerVisible={ this.state.readMarkerVisible } readMarkerVisible={ this.state.readMarkerVisible }
suppressFirstDateSeparator={ this.state.canBackPaginate } suppressFirstDateSeparator={ this.state.canBackPaginate }
showUrlPreview={ this.props.showUrlPreview } showUrlPreview={ this.props.showUrlPreview }
manageReadReceipts = { this.props.manageReadReceipts } showReadReceipts={ this.props.showReadReceipts }
ourUserId={ MatrixClientPeg.get().credentials.userId } ourUserId={ MatrixClientPeg.get().credentials.userId }
stickyBottom={ stickyBottom } stickyBottom={ stickyBottom }
onScroll={ this.onMessageListScroll } onScroll={ this.onMessageListScroll }

View file

@ -32,7 +32,7 @@ const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig'); const SdkConfig = require('../../SdkConfig');
import Analytics from '../../Analytics'; import Analytics from '../../Analytics';
import AccessibleButton from '../views/elements/AccessibleButton'; import AccessibleButton from '../views/elements/AccessibleButton';
import { _t } from '../../languageHandler'; import { _t, _td } from '../../languageHandler';
import * as languageHandler from '../../languageHandler'; import * as languageHandler from '../../languageHandler';
import * as FormattingUtils from '../../utils/FormattingUtils'; import * as FormattingUtils from '../../utils/FormattingUtils';
@ -63,51 +63,55 @@ const gHVersionLabel = function(repo, token='') {
const SETTINGS_LABELS = [ const SETTINGS_LABELS = [
{ {
id: 'autoplayGifsAndVideos', id: 'autoplayGifsAndVideos',
label: 'Autoplay GIFs and videos', label: _td('Autoplay GIFs and videos'),
}, },
{ {
id: 'hideReadReceipts', id: 'hideReadReceipts',
label: 'Hide read receipts', label: _td('Hide read receipts'),
}, },
{ {
id: 'dontSendTypingNotifications', id: 'dontSendTypingNotifications',
label: "Don't send typing notifications", label: _td("Don't send typing notifications"),
}, },
{ {
id: 'alwaysShowTimestamps', id: 'alwaysShowTimestamps',
label: 'Always show message timestamps', label: _td('Always show message timestamps'),
}, },
{ {
id: 'showTwelveHourTimestamps', id: 'showTwelveHourTimestamps',
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)', label: _td('Show timestamps in 12 hour format (e.g. 2:30pm)'),
}, },
{ {
id: 'hideJoinLeaves', id: 'hideJoinLeaves',
label: 'Hide join/leave messages (invites/kicks/bans unaffected)', label: _td('Hide join/leave messages (invites/kicks/bans unaffected)'),
}, },
{ {
id: 'hideAvatarDisplaynameChanges', id: 'hideAvatarDisplaynameChanges',
label: 'Hide avatar and display name changes', label: _td('Hide avatar and display name changes'),
}, },
{ {
id: 'useCompactLayout', id: 'useCompactLayout',
label: 'Use compact timeline layout', label: _td('Use compact timeline layout'),
}, },
{ {
id: 'hideRedactions', id: 'hideRedactions',
label: 'Hide removed messages', label: _td('Hide removed messages'),
}, },
{ {
id: 'enableSyntaxHighlightLanguageDetection', id: 'enableSyntaxHighlightLanguageDetection',
label: 'Enable automatic language detection for syntax highlighting', label: _td('Enable automatic language detection for syntax highlighting'),
}, },
{ {
id: 'MessageComposerInput.autoReplaceEmoji', id: 'MessageComposerInput.autoReplaceEmoji',
label: 'Automatically replace plain text Emoji', label: _td('Automatically replace plain text Emoji'),
},
{
id: 'MessageComposerInput.dontSuggestEmoji',
label: _td('Disable Emoji suggestions while typing'),
}, },
{ {
id: 'Pill.shouldHidePillAvatar', id: 'Pill.shouldHidePillAvatar',
label: 'Hide avatars in user and room mentions', label: _td('Hide avatars in user and room mentions'),
}, },
/* /*
{ {
@ -120,7 +124,7 @@ const SETTINGS_LABELS = [
const ANALYTICS_SETTINGS_LABELS = [ const ANALYTICS_SETTINGS_LABELS = [
{ {
id: 'analyticsOptOut', id: 'analyticsOptOut',
label: 'Opt out of analytics', label: _td('Opt out of analytics'),
fn: function(checked) { fn: function(checked) {
Analytics[checked ? 'disable' : 'enable'](); Analytics[checked ? 'disable' : 'enable']();
}, },
@ -130,7 +134,7 @@ const ANALYTICS_SETTINGS_LABELS = [
const WEBRTC_SETTINGS_LABELS = [ const WEBRTC_SETTINGS_LABELS = [
{ {
id: 'webRtcForceTURN', id: 'webRtcForceTURN',
label: 'Disable Peer-to-Peer for 1:1 calls', label: _td('Disable Peer-to-Peer for 1:1 calls'),
}, },
]; ];
@ -139,7 +143,7 @@ const WEBRTC_SETTINGS_LABELS = [
const CRYPTO_SETTINGS_LABELS = [ const CRYPTO_SETTINGS_LABELS = [
{ {
id: 'blacklistUnverifiedDevices', id: 'blacklistUnverifiedDevices',
label: 'Never send encrypted messages to unverified devices from this device', label: _td('Never send encrypted messages to unverified devices from this device'),
fn: function(checked) { fn: function(checked) {
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked); MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
}, },
@ -162,16 +166,44 @@ const CRYPTO_SETTINGS_LABELS = [
const THEMES = [ const THEMES = [
{ {
id: 'theme', id: 'theme',
label: 'Light theme', label: _td('Light theme'),
value: 'light', value: 'light',
}, },
{ {
id: 'theme', id: 'theme',
label: 'Dark theme', label: _td('Dark theme'),
value: 'dark', value: 'dark',
}, },
]; ];
const IgnoredUser = React.createClass({
propTypes: {
userId: React.PropTypes.string.isRequired,
onUnignored: React.PropTypes.func.isRequired,
},
_onUnignoreClick: function() {
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
const index = ignoredUsers.indexOf(this.props.userId);
if (index !== -1) {
ignoredUsers.splice(index, 1);
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers)
.then(() => this.props.onUnignored(this.props.userId));
} else this.props.onUnignored(this.props.userId);
},
render: function() {
return (
<li>
<AccessibleButton onClick={this._onUnignoreClick} className="mx_UserSettings_button mx_UserSettings_buttonSmall">
{ _t("Unignore") }
</AccessibleButton>
{ this.props.userId }
</li>
);
},
});
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'UserSettings', displayName: 'UserSettings',
@ -207,6 +239,7 @@ module.exports = React.createClass({
vectorVersion: undefined, vectorVersion: undefined,
rejectingInvites: false, rejectingInvites: false,
mediaDevices: null, mediaDevices: null,
ignoredUsers: [],
}; };
}, },
@ -228,6 +261,7 @@ module.exports = React.createClass({
} }
this._refreshMediaDevices(); this._refreshMediaDevices();
this._refreshIgnoredUsers();
// Bulk rejecting invites: // Bulk rejecting invites:
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms() // /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
@ -346,9 +380,22 @@ module.exports = React.createClass({
}); });
}, },
_refreshIgnoredUsers: function(userIdUnignored=null) {
const users = MatrixClientPeg.get().getIgnoredUsers();
if (userIdUnignored) {
const index = users.indexOf(userIdUnignored);
if (index !== -1) users.splice(index, 1);
}
this.setState({
ignoredUsers: users,
});
},
onAction: function(payload) { onAction: function(payload) {
if (payload.action === "notifier_enabled") { if (payload.action === "notifier_enabled") {
this.forceUpdate(); this.forceUpdate();
} else if (payload.action === "ignore_state_changed") {
this._refreshIgnoredUsers();
} }
}, },
@ -729,6 +776,7 @@ module.exports = React.createClass({
// to rebind the onChange each time we render // to rebind the onChange each time we render
const onChange = (e) => { const onChange = (e) => {
if (e.target.checked) { if (e.target.checked) {
this._syncedSettings[setting.id] = setting.value;
UserSettingsStore.setSyncedSetting(setting.id, setting.value); UserSettingsStore.setSyncedSetting(setting.id, setting.value);
} }
dis.dispatch({ dis.dispatch({
@ -741,11 +789,11 @@ module.exports = React.createClass({
type="radio" type="radio"
name={setting.id} name={setting.id}
value={setting.value} value={setting.value}
defaultChecked={ this._syncedSettings[setting.id] === setting.value } checked={this._syncedSettings[setting.id] === setting.value}
onChange={onChange} onChange={onChange}
/> />
<label htmlFor={setting.id + "_" + setting.value}> <label htmlFor={setting.id + "_" + setting.value}>
{ setting.label } { _t(setting.label) }
</label> </label>
</div>; </div>;
}, },
@ -795,6 +843,26 @@ module.exports = React.createClass({
); );
}, },
_renderIgnoredUsers: function() {
if (this.state.ignoredUsers.length > 0) {
const updateHandler = this._refreshIgnoredUsers;
return (
<div>
<h3>{ _t("Ignored Users") }</h3>
<div className="mx_UserSettings_section mx_UserSettings_ignoredUsersSection">
<ul>
{ this.state.ignoredUsers.map(function(userId) {
return (<IgnoredUser key={userId}
userId={userId}
onUnignored={updateHandler}></IgnoredUser>);
}) }
</ul>
</div>
</div>
);
} else return (<div />);
},
_renderLocalSetting: function(setting) { _renderLocalSetting: function(setting) {
// TODO: this ought to be a separate component so that we don't need // TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render // to rebind the onChange each time we render
@ -1301,6 +1369,7 @@ module.exports = React.createClass({
{ this._renderWebRtcSettings() } { this._renderWebRtcSettings() }
{ this._renderDevicesPanel() } { this._renderDevicesPanel() }
{ this._renderCryptoInfo() } { this._renderCryptoInfo() }
{ this._renderIgnoredUsers() }
{ this._renderBulkOptions() } { this._renderBulkOptions() }
{ this._renderBugReport() } { this._renderBugReport() }

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector 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.
@ -136,16 +137,15 @@ module.exports = React.createClass({
}); });
}, },
onHsUrlChanged: function(newHsUrl) { onServerConfigChange: function(config) {
this.setState({ const newState = {};
enteredHomeserverUrl: newHsUrl if (config.hsUrl !== undefined) {
}); newState.enteredHomeserverUrl = config.hsUrl;
}, }
if (config.isUrl !== undefined) {
onIsUrlChanged: function(newIsUrl) { newState.enteredIdentityServerUrl = config.isUrl;
this.setState({ }
enteredIdentityServerUrl: newIsUrl this.setState(newState);
});
}, },
showErrorDialog: function(body, title) { showErrorDialog: function(body, title) {
@ -170,7 +170,7 @@ module.exports = React.createClass({
else if (this.state.progress === "sent_email") { else if (this.state.progress === "sent_email") {
resetPasswordJsx = ( resetPasswordJsx = (
<div> <div>
{ _t('An email has been sent to') } {this.state.email}. { _t('Once you&#39;ve followed the link it contains, click below') }. { _t('An email has been sent to') } {this.state.email}. { _t("Once you've followed 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={ _t('I have verified my email address') } /> value={ _t('I have verified my email address') } />
@ -221,8 +221,7 @@ module.exports = React.createClass({
defaultIsUrl={this.props.defaultIsUrl} defaultIsUrl={this.props.defaultIsUrl}
customHsUrl={this.props.customHsUrl} customHsUrl={this.props.customHsUrl}
customIsUrl={this.props.customIsUrl} customIsUrl={this.props.customIsUrl}
onHsUrlChanged={this.onHsUrlChanged} onServerConfigChange={this.onServerConfigChange}
onIsUrlChanged={this.onIsUrlChanged}
delayTimeMs={0}/> delayTimeMs={0}/>
<div className="mx_Login_error"> <div className="mx_Login_error">
</div> </div>

View file

@ -14,10 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
'use strict'; import React from 'react';
import AvatarLogic from '../../../Avatar';
var React = require('react');
var AvatarLogic = require("../../../Avatar");
import sdk from '../../../index'; import sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';

View file

@ -13,11 +13,10 @@ 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.
*/ */
var React = require('react'); import React from "react";
var ContentRepo = require("matrix-js-sdk").ContentRepo; import {ContentRepo} from "matrix-js-sdk";
var MatrixClientPeg = require('../../../MatrixClientPeg'); import MatrixClientPeg from "../../../MatrixClientPeg";
var Avatar = require('../../../Avatar'); import sdk from "../../../index";
var sdk = require("../../../index");
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomAvatar', displayName: 'RoomAvatar',
@ -30,7 +29,7 @@ module.exports = React.createClass({
oobData: React.PropTypes.object, oobData: React.PropTypes.object,
width: React.PropTypes.number, width: React.PropTypes.number,
height: React.PropTypes.number, height: React.PropTypes.number,
resizeMethod: React.PropTypes.string resizeMethod: React.PropTypes.string,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -44,13 +43,13 @@ module.exports = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
urls: this.getImageUrls(this.props) urls: this.getImageUrls(this.props),
}; };
}, },
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
this.setState({ this.setState({
urls: this.getImageUrls(newProps) urls: this.getImageUrls(newProps),
}); });
}, },
@ -61,11 +60,10 @@ module.exports = React.createClass({
props.oobData.avatarUrl, props.oobData.avatarUrl,
Math.floor(props.width * window.devicePixelRatio), Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio), Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod props.resizeMethod,
), // highest priority ), // highest priority
this.getRoomAvatarUrl(props), this.getRoomAvatarUrl(props),
this.getOneToOneAvatar(props), this.getOneToOneAvatar(props), // lowest priority
this.getFallbackAvatar(props) // lowest priority
].filter(function(url) { ].filter(function(url) {
return (url != null && url != ""); return (url != null && url != "");
}); });
@ -79,17 +77,17 @@ module.exports = React.createClass({
Math.floor(props.width * window.devicePixelRatio), Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio), Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod, props.resizeMethod,
false false,
); );
}, },
getOneToOneAvatar: function(props) { getOneToOneAvatar: function(props) {
if (!props.room) return null; if (!props.room) return null;
var mlist = props.room.currentState.members; const mlist = props.room.currentState.members;
var userIds = []; const userIds = [];
// for .. in optimisation to return early if there are >2 keys // for .. in optimisation to return early if there are >2 keys
for (var uid in mlist) { for (const uid in mlist) {
if (mlist.hasOwnProperty(uid)) { if (mlist.hasOwnProperty(uid)) {
userIds.push(uid); userIds.push(uid);
} }
@ -99,7 +97,7 @@ module.exports = React.createClass({
} }
if (userIds.length == 2) { if (userIds.length == 2) {
var theOtherGuy = null; let theOtherGuy = null;
if (mlist[userIds[0]].userId == MatrixClientPeg.get().credentials.userId) { if (mlist[userIds[0]].userId == MatrixClientPeg.get().credentials.userId) {
theOtherGuy = mlist[userIds[1]]; theOtherGuy = mlist[userIds[1]];
} else { } else {
@ -110,7 +108,7 @@ module.exports = React.createClass({
Math.floor(props.width * window.devicePixelRatio), Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio), Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod, props.resizeMethod,
false false,
); );
} else if (userIds.length == 1) { } else if (userIds.length == 1) {
return mlist[userIds[0]].getAvatarUrl( return mlist[userIds[0]].getAvatarUrl(
@ -118,37 +116,24 @@ module.exports = React.createClass({
Math.floor(props.width * window.devicePixelRatio), Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio), Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod, props.resizeMethod,
false false,
); );
} else { } else {
return null; return null;
} }
}, },
getFallbackAvatar: function(props) {
let roomId = null;
if (props.oobData && props.oobData.roomId) {
roomId = this.props.oobData.roomId;
} else if (props.room) {
roomId = props.room.roomId;
} else {
return null;
}
return Avatar.defaultAvatarUrlForString(roomId);
},
render: function() { render: function() {
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var {room, oobData, ...otherProps} = this.props; const {room, oobData, ...otherProps} = this.props;
var roomName = room ? room.name : oobData.name; const roomName = room ? room.name : oobData.name;
return ( return (
<BaseAvatar {...otherProps} name={roomName} <BaseAvatar {...otherProps} name={roomName}
idName={room ? room.roomId : null} idName={room ? room.roomId : null}
urls={this.state.urls} /> urls={this.state.urls} />
); );
} },
}); });

View file

@ -28,7 +28,7 @@ const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: "UserPickerDialog", displayName: "AddressPickerDialog",
propTypes: { propTypes: {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
@ -38,8 +38,14 @@ module.exports = React.createClass({
roomId: PropTypes.string, roomId: PropTypes.string,
button: PropTypes.string, button: PropTypes.string,
focus: PropTypes.bool, focus: PropTypes.bool,
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOfType(addressTypes)), validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)),
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
groupId: PropTypes.string,
// The type of entity to search for. Default: 'user'.
pickerType: PropTypes.oneOf(['user', 'room']),
// Whether the current user should be included in the addresses returned. Only
// applicable when pickerType is `user`. Default: false.
includeSelf: PropTypes.bool,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -47,6 +53,8 @@ module.exports = React.createClass({
value: "", value: "",
focus: true, focus: true,
validAddressTypes: addressTypes, validAddressTypes: addressTypes,
pickerType: 'user',
includeSelf: false,
}; };
}, },
@ -140,11 +148,23 @@ module.exports = React.createClass({
// Only do search if there is something to search // Only do search if there is something to search
if (query.length > 0 && query != '@' && query.length >= 2) { if (query.length > 0 && query != '@' && query.length >= 2) {
this.queryChangedDebouncer = setTimeout(() => { this.queryChangedDebouncer = setTimeout(() => {
if (this.state.serverSupportsUserDirectory) { if (this.props.pickerType === 'user') {
if (this.props.groupId) {
this._doNaiveGroupSearch(query);
} else if (this.state.serverSupportsUserDirectory) {
this._doUserDirectorySearch(query); this._doUserDirectorySearch(query);
} else { } else {
this._doLocalSearch(query); this._doLocalSearch(query);
} }
} else if (this.props.pickerType === 'room') {
if (this.props.groupId) {
this._doNaiveGroupRoomSearch(query);
} else {
this._doRoomSearch(query);
}
} else {
console.error('Unknown pickerType', this.props.pickerType);
}
}, QUERY_USER_DIRECTORY_DEBOUNCE_MS); }, QUERY_USER_DIRECTORY_DEBOUNCE_MS);
} else { } else {
this.setState({ this.setState({
@ -185,6 +205,101 @@ module.exports = React.createClass({
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
}, },
_doNaiveGroupSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
this.setState({
busy: true,
query,
searchError: null,
});
MatrixClientPeg.get().getGroupUsers(this.props.groupId).then((resp) => {
const results = [];
resp.chunk.forEach((u) => {
const userIdMatch = u.user_id.toLowerCase().includes(lowerCaseQuery);
const displayNameMatch = (u.displayname || '').toLowerCase().includes(lowerCaseQuery);
if (!(userIdMatch || displayNameMatch)) {
return;
}
results.push({
user_id: u.user_id,
avatar_url: u.avatar_url,
display_name: u.displayname,
});
});
this._processResults(results, query);
}).catch((err) => {
console.error('Error whilst searching group rooms: ', err);
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
}).done(() => {
this.setState({
busy: false,
});
});
},
_doNaiveGroupRoomSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
MatrixClientPeg.get().getGroupRooms(this.props.groupId).then((resp) => {
const results = [];
resp.chunk.forEach((r) => {
const nameMatch = (r.name || '').toLowerCase().includes(lowerCaseQuery);
const topicMatch = (r.topic || '').toLowerCase().includes(lowerCaseQuery);
const aliasMatch = (r.canonical_alias || '').toLowerCase().includes(lowerCaseQuery);
if (!(nameMatch || topicMatch || aliasMatch)) {
return;
}
results.push({
room_id: r.room_id,
avatar_url: r.avatar_url,
name: r.name || r.canonical_alias,
});
});
this._processResults(results, query);
}).catch((err) => {
console.error('Error whilst searching group users: ', err);
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
}).done(() => {
this.setState({
busy: false,
});
});
},
_doRoomSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
const rooms = MatrixClientPeg.get().getRooms();
const results = [];
rooms.forEach((room) => {
const nameEvent = room.currentState.getStateEvents('m.room.name', '');
const topicEvent = room.currentState.getStateEvents('m.room.topic', '');
const name = nameEvent ? nameEvent.getContent().name : '';
const canonicalAlias = room.getCanonicalAlias();
const topic = topicEvent ? topicEvent.getContent().topic : '';
const nameMatch = (name || '').toLowerCase().includes(lowerCaseQuery);
const aliasMatch = (canonicalAlias || '').toLowerCase().includes(lowerCaseQuery);
const topicMatch = (topic || '').toLowerCase().includes(lowerCaseQuery);
if (!(nameMatch || topicMatch || aliasMatch)) {
return;
}
const avatarEvent = room.currentState.getStateEvents('m.room.avatar', '');
const avatarUrl = avatarEvent ? avatarEvent.getContent().url : undefined;
results.push({
room_id: room.roomId,
avatar_url: avatarUrl,
name: name || canonicalAlias,
});
});
this._processResults(results, query);
this.setState({
busy: false,
});
},
_doUserDirectorySearch: function(query) { _doUserDirectorySearch: function(query) {
this.setState({ this.setState({
busy: true, busy: true,
@ -245,17 +360,30 @@ module.exports = React.createClass({
_processResults: function(results, query) { _processResults: function(results, query) {
const queryList = []; const queryList = [];
results.forEach((user) => { results.forEach((result) => {
if (user.user_id === MatrixClientPeg.get().credentials.userId) { if (result.room_id) {
queryList.push({
addressType: 'mx-room-id',
address: result.room_id,
displayName: result.name,
avatarMxc: result.avatar_url,
isKnown: true,
});
return; return;
} }
if (!this.props.includeSelf &&
result.user_id === MatrixClientPeg.get().credentials.userId
) {
return;
}
// Return objects, structure of which is defined // Return objects, structure of which is defined
// by UserAddressType // by UserAddressType
queryList.push({ queryList.push({
addressType: 'mx', addressType: 'mx-user-id',
address: user.user_id, address: result.user_id,
displayName: user.display_name, displayName: result.display_name,
avatarMxc: user.avatar_url, avatarMxc: result.avatar_url,
isKnown: true, isKnown: true,
}); });
}); });
@ -291,16 +419,23 @@ module.exports = React.createClass({
address: addressText, address: addressText,
isKnown: false, isKnown: false,
}; };
if (addrType == null) { if (!this.props.validAddressTypes.includes(addrType)) {
this.setState({ error: true }); this.setState({ error: true });
return null; return null;
} else if (addrType == 'mx') { } else if (addrType == 'mx-user-id') {
const user = MatrixClientPeg.get().getUser(addrObj.address); const user = MatrixClientPeg.get().getUser(addrObj.address);
if (user) { if (user) {
addrObj.displayName = user.displayName; addrObj.displayName = user.displayName;
addrObj.avatarMxc = user.avatarUrl; addrObj.avatarMxc = user.avatarUrl;
addrObj.isKnown = true; addrObj.isKnown = true;
} }
} else if (addrType == 'mx-room-id') {
const room = MatrixClientPeg.get().getRoom(addrObj.address);
if (room) {
addrObj.displayName = room.name;
addrObj.avatarMxc = room.avatarUrl;
addrObj.isKnown = true;
}
} }
const userList = this.state.userList.slice(); const userList = this.state.userList.slice();
@ -382,8 +517,21 @@ module.exports = React.createClass({
let error; let error;
let addressSelector; let addressSelector;
if (this.state.error) { if (this.state.error) {
let tryUsing = '';
const validTypeDescriptions = this.props.validAddressTypes.map((t) => {
return {
'mx-user-id': _t("Matrix ID"),
'mx-room-id': _t("Matrix Room ID"),
'email': _t("email address"),
}[t];
});
tryUsing = _t("Try using one of the following valid address types: %(validTypesList)s.", {
validTypesList: validTypeDescriptions.join(", "),
});
error = <div className="mx_ChatInviteDialog_error"> error = <div className="mx_ChatInviteDialog_error">
{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")} { _t("You have entered an invalid address.") }
<br />
{ tryUsing }
</div>; </div>;
} else if (this.state.searchError) { } else if (this.state.searchError) {
error = <div className="mx_ChatInviteDialog_error">{ this.state.searchError }</div>; error = <div className="mx_ChatInviteDialog_error">{ this.state.searchError }</div>;

View file

@ -18,6 +18,7 @@ import React from 'react';
import sdk from '../../../index'; import sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import classnames from 'classnames'; import classnames from 'classnames';
import { GroupMemberType } from '../../../groups';
/* /*
* A dialog for confirming an operation on another user. * A dialog for confirming an operation on another user.
@ -30,7 +31,10 @@ import classnames from 'classnames';
export default React.createClass({ export default React.createClass({
displayName: 'ConfirmUserActionDialog', displayName: 'ConfirmUserActionDialog',
propTypes: { propTypes: {
member: React.PropTypes.object.isRequired, // matrix-js-sdk member object // matrix-js-sdk (room) member object. Supply either this or 'groupMember'
member: React.PropTypes.object,
// group member object. Supply either this or 'member'
groupMember: GroupMemberType,
action: React.PropTypes.string.isRequired, // eg. 'Ban' action: React.PropTypes.string.isRequired, // eg. 'Ban'
// Whether to display a text field for a reason // Whether to display a text field for a reason
@ -69,6 +73,7 @@ export default React.createClass({
render: function() { render: function() {
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 BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action}); const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
const confirmButtonClass = classnames({ const confirmButtonClass = classnames({
@ -91,6 +96,20 @@ export default React.createClass({
); );
} }
let avatar;
let name;
let userId;
if (this.props.member) {
avatar = <MemberAvatar member={this.props.member} width={48} height={48} />;
name = this.props.member.name;
userId = this.props.member.userId;
} else {
// we don't get this info from the API yet
avatar = <BaseAvatar name={this.props.groupMember.userId} width={48} height={48} />;
name = this.props.groupMember.userId;
userId = this.props.groupMember.userId;
}
return ( return (
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished} <BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
onEnterPressed={this.onOk} onEnterPressed={this.onOk}
@ -98,10 +117,10 @@ export default React.createClass({
> >
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
<div className="mx_ConfirmUserActionDialog_avatar"> <div className="mx_ConfirmUserActionDialog_avatar">
<MemberAvatar member={this.props.member} width={48} height={48} /> { avatar }
</div> </div>
<div className="mx_ConfirmUserActionDialog_name">{this.props.member.name}</div> <div className="mx_ConfirmUserActionDialog_name">{ name }</div>
<div className="mx_ConfirmUserActionDialog_userId">{this.props.member.userId}</div> <div className="mx_ConfirmUserActionDialog_userId">{ userId }</div>
</div> </div>
{ reasonBox } { reasonBox }
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">

View file

@ -18,7 +18,7 @@ import Modal from '../../../Modal';
import React from 'react'; import React from 'react';
import sdk from '../../../index'; import sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
/** /**
* Dialog which asks the user whether they want to share their keys with * Dialog which asks the user whether they want to share their keys with
@ -116,11 +116,11 @@ export default React.createClass({
let text; let text;
if (this.state.wasNewDevice) { if (this.state.wasNewDevice) {
text = "You added a new device '%(displayName)s', which is" text = _td("You added a new device '%(displayName)s', which is"
+ " requesting encryption keys."; + " requesting encryption keys.");
} else { } else {
text = "Your unverified device '%(displayName)s' is requesting" text = _td("Your unverified device '%(displayName)s' is requesting"
+ " encryption keys."; + " encryption keys.");
} }
text = _t(text, {displayName: displayName}); text = _t(text, {displayName: displayName});

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector 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.
@ -17,6 +18,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 { _t } from '../../../languageHandler';
import classnames from 'classnames';
export default React.createClass({ export default React.createClass({
displayName: 'QuestionDialog', displayName: 'QuestionDialog',
@ -25,6 +27,7 @@ export default React.createClass({
description: React.PropTypes.node, description: React.PropTypes.node,
extraButtons: React.PropTypes.node, extraButtons: React.PropTypes.node,
button: React.PropTypes.string, button: React.PropTypes.string,
danger: React.PropTypes.bool,
focus: React.PropTypes.bool, focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired, onFinished: React.PropTypes.func.isRequired,
}, },
@ -36,6 +39,7 @@ export default React.createClass({
extraButtons: null, extraButtons: null,
focus: true, focus: true,
hasCancelButton: true, hasCancelButton: true,
danger: false,
}; };
}, },
@ -54,6 +58,10 @@ export default React.createClass({
{ _t("Cancel") } { _t("Cancel") }
</button> </button>
) : null; ) : null;
const buttonClasses = classnames({
mx_Dialog_primary: true,
danger: this.props.danger,
});
return ( return (
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} <BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished}
onEnterPressed={this.onOk} onEnterPressed={this.onOk}
@ -63,7 +71,7 @@ export default React.createClass({
{ this.props.description } { this.props.description }
</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={buttonClasses} onClick={this.onOk} autoFocus={this.props.focus}>
{ this.props.button || _t('OK') } { this.props.button || _t('OK') }
</button> </button>
{ this.props.extraButtons } { this.props.extraButtons }

View file

@ -19,6 +19,7 @@ import PropTypes from 'prop-types';
import AccessibleButton from './AccessibleButton'; import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import sdk from '../../../index'; import sdk from '../../../index';
import Analytics from '../../../Analytics';
export default React.createClass({ export default React.createClass({
displayName: 'RoleButton', displayName: 'RoleButton',
@ -47,6 +48,7 @@ export default React.createClass({
_onClick: function(ev) { _onClick: function(ev) {
ev.stopPropagation(); ev.stopPropagation();
Analytics.trackEvent('Action Button', 'click', this.props.action);
dis.dispatch({action: this.props.action}); dis.dispatch({action: this.props.action});
}, },

View file

@ -45,11 +45,12 @@ export default React.createClass({
const address = this.props.address; const address = this.props.address;
const name = address.displayName || address.address; const name = address.displayName || address.address;
let imgUrls = []; const imgUrls = [];
const isMatrixAddress = ['mx-user-id', 'mx-room-id'].includes(address.addressType);
if (address.addressType === "mx" && address.avatarMxc) { if (isMatrixAddress && address.avatarMxc) {
imgUrls.push(MatrixClientPeg.get().mxcUrlToHttp( imgUrls.push(MatrixClientPeg.get().mxcUrlToHttp(
address.avatarMxc, 25, 25, 'crop' address.avatarMxc, 25, 25, 'crop',
)); ));
} else if (address.addressType === 'email') { } else if (address.addressType === 'email') {
imgUrls.push('img/icon-email-user.svg'); imgUrls.push('img/icon-email-user.svg');
@ -77,7 +78,7 @@ export default React.createClass({
let info; let info;
let error = false; let error = false;
if (address.addressType === "mx" && address.isKnown) { if (isMatrixAddress && address.isKnown) {
const idClasses = classNames({ const idClasses = classNames({
"mx_AddressTile_id": true, "mx_AddressTile_id": true,
"mx_AddressTile_justified": this.props.justified, "mx_AddressTile_justified": this.props.justified,
@ -89,7 +90,7 @@ export default React.createClass({
<div className={idClasses}>{ address.address }</div> <div className={idClasses}>{ address.address }</div>
</div> </div>
); );
} else if (address.addressType === "mx") { } else if (isMatrixAddress) {
const unknownMxClasses = classNames({ const unknownMxClasses = classNames({
"mx_AddressTile_unknownMx": true, "mx_AddressTile_unknownMx": true,
"mx_AddressTile_justified": this.props.justified, "mx_AddressTile_justified": this.props.justified,

View file

@ -47,13 +47,19 @@ export default class AppPermission extends React.Component {
} }
render() { render() {
let e2eWarningText;
if (this.props.isRoomEncrypted) {
e2eWarningText =
<span className='mx_AppPermissionWarningTextLabel'>{ _t('NOTE: Apps are not end-to-end encrypted') }</span>;
}
return ( return (
<div className='mx_AppPermissionWarning'> <div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarningImage'> <div className='mx_AppPermissionWarningImage'>
<img src='img/warning.svg' alt={_t('Warning!')} /> <img src='img/warning.svg' alt={_t('Warning!')} />
</div> </div>
<div className='mx_AppPermissionWarningText'> <div className='mx_AppPermissionWarningText'>
<span className='mx_AppPermissionWarningTextLabel'>Do you want to load widget from URL:</span> <span className='mx_AppPermissionWarningTextURL'>{this.state.curlBase}</span> <span className='mx_AppPermissionWarningTextLabel'>{ _t('Do you want to load widget from URL:') }</span> <span className='mx_AppPermissionWarningTextURL'>{ this.state.curlBase }</span>
{ e2eWarningText }
</div> </div>
<input <input
className='mx_AppPermissionButton' className='mx_AppPermissionButton'
@ -67,9 +73,11 @@ export default class AppPermission extends React.Component {
} }
AppPermission.propTypes = { AppPermission.propTypes = {
isRoomEncrypted: PropTypes.bool,
url: PropTypes.string.isRequired, url: PropTypes.string.isRequired,
onPermissionGranted: PropTypes.func.isRequired, onPermissionGranted: PropTypes.func.isRequired,
}; };
AppPermission.defaultProps = { AppPermission.defaultProps = {
isRoomEncrypted: false,
onPermissionGranted: function() {}, onPermissionGranted: function() {},
}; };

View file

@ -19,18 +19,19 @@ limitations under the License.
import url from 'url'; import url from 'url';
import React from 'react'; import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
import ScalarAuthClient from '../../../ScalarAuthClient'; import ScalarAuthClient from '../../../ScalarAuthClient';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import { _t } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index'; import sdk from '../../../index';
import AppPermission from './AppPermission'; import AppPermission from './AppPermission';
import AppWarning from './AppWarning'; import AppWarning from './AppWarning';
import MessageSpinner from './MessageSpinner'; import MessageSpinner from './MessageSpinner';
import WidgetUtils from '../../../WidgetUtils'; import WidgetUtils from '../../../WidgetUtils';
import dis from '../../../dispatcher';
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const betaHelpMsg = 'This feature is currently experimental and is intended for beta testing only';
export default React.createClass({ export default React.createClass({
displayName: 'AppTile', displayName: 'AppTile',
@ -44,6 +45,10 @@ export default React.createClass({
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer. // Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false. // This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
fullWidth: React.PropTypes.bool, fullWidth: React.PropTypes.bool,
// UserId of the current user
userId: React.PropTypes.string.isRequired,
// UserId of the entity that added / modified the widget
creatorUserId: React.PropTypes.string,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -59,7 +64,8 @@ export default React.createClass({
loading: false, loading: false,
widgetUrl: this.props.url, widgetUrl: this.props.url,
widgetPermissionId: widgetPermissionId, widgetPermissionId: widgetPermissionId,
hasPermissionToLoad: Boolean(hasPermissionToLoad === 'true'), // Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user
hasPermissionToLoad: hasPermissionToLoad === 'true' || this.props.userId === this.props.creatorUserId,
error: null, error: null,
deleting: false, deleting: false,
}; };
@ -67,8 +73,17 @@ export default React.createClass({
// Returns true if props.url is a scalar URL, typically https://scalar.vector.im/api // Returns true if props.url is a scalar URL, typically https://scalar.vector.im/api
isScalarUrl: function() { isScalarUrl: function() {
const scalarUrl = SdkConfig.get().integrations_rest_url; let scalarUrls = SdkConfig.get().integrations_widgets_urls;
return scalarUrl && this.props.url.startsWith(scalarUrl); if (!scalarUrls || scalarUrls.length == 0) {
scalarUrls = [SdkConfig.get().integrations_rest_url];
}
for (let i = 0; i < scalarUrls.length; i++) {
if (this.props.url.startsWith(scalarUrls[i])) {
return true;
}
}
return false;
}, },
isMixedContent: function() { isMixedContent: function() {
@ -113,6 +128,30 @@ export default React.createClass({
loading: false, loading: false,
}); });
}); });
window.addEventListener('message', this._onMessage, false);
},
componentWillUnmount() {
window.removeEventListener('message', this._onMessage);
},
_onMessage(event) {
if (this.props.type !== 'jitsi') {
return;
}
if (!event.origin) {
event.origin = event.originalEvent.origin;
}
if (!this.state.widgetUrl.startsWith(event.origin)) {
return;
}
if (event.data.widgetAction === 'jitsi_iframe_loaded') {
const iframe = this.refs.appFrame.contentWindow
.document.querySelector('iframe[id^="jitsiConferenceFrame"]');
PlatformPeg.get().setupScreenSharingForIframe(iframe);
}
}, },
_canUserModify: function() { _canUserModify: function() {
@ -122,7 +161,8 @@ export default React.createClass({
_onEditClick: function(e) { _onEditClick: function(e) {
console.log("Edit widget ID ", this.props.id); console.log("Edit widget ID ", this.props.id);
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = this._scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'type_' + this.props.type); const src = this._scalarClient.getScalarInterfaceUrlForRoom(
this.props.room.roomId, 'type_' + this.props.type, this.props.id);
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
src: src, src: src,
}, "mx_IntegrationsManager"); }, "mx_IntegrationsManager");
@ -155,9 +195,9 @@ export default React.createClass({
// These strings are translated at the point that they are inserted in to the DOM, in the render method // These strings are translated at the point that they are inserted in to the DOM, in the render method
_deleteWidgetLabel() { _deleteWidgetLabel() {
if (this._canUserModify()) { if (this._canUserModify()) {
return 'Delete widget'; return _td('Delete widget');
} }
return 'Revoke widget access'; return _td('Revoke widget access');
}, },
/* TODO -- Store permission in account data so that it is persisted across multiple devices */ /* TODO -- Store permission in account data so that it is persisted across multiple devices */
@ -177,11 +217,25 @@ export default React.createClass({
let appTileName = "No name"; let appTileName = "No name";
if(this.props.name && this.props.name.trim()) { if(this.props.name && this.props.name.trim()) {
appTileName = this.props.name.trim(); appTileName = this.props.name.trim();
appTileName = appTileName[0].toUpperCase() + appTileName.slice(1).toLowerCase();
} }
return appTileName; return appTileName;
}, },
onClickMenuBar: function(ev) {
ev.preventDefault();
// Ignore clicks on menu bar children
if (ev.target !== this.refs.menu_bar) {
return;
}
// Toggle the view state of the apps drawer
dis.dispatch({
action: 'appsDrawer',
show: !this.props.show,
});
},
render: function() { render: function() {
let appTileBody; let appTileBody;
@ -203,6 +257,7 @@ export default React.createClass({
safeWidgetUrl = url.format(parsedWidgetUrl); safeWidgetUrl = url.format(parsedWidgetUrl);
} }
if (this.props.show) {
if (this.state.loading) { if (this.state.loading) {
appTileBody = ( appTileBody = (
<div className='mx_AppTileBody mx_AppLoading'> <div className='mx_AppTileBody mx_AppLoading'>
@ -231,15 +286,18 @@ export default React.createClass({
); );
} }
} else { } else {
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
appTileBody = ( appTileBody = (
<div className="mx_AppTileBody"> <div className="mx_AppTileBody">
<AppPermission <AppPermission
isRoomEncrypted={isRoomEncrypted}
url={this.state.widgetUrl} url={this.state.widgetUrl}
onPermissionGranted={this._grantWidgetPermission} onPermissionGranted={this._grantWidgetPermission}
/> />
</div> </div>
); );
} }
}
// editing is done in scalar // editing is done in scalar
const showEditButton = Boolean(this._scalarClient && this._canUserModify()); const showEditButton = Boolean(this._scalarClient && this._canUserModify());
@ -253,10 +311,9 @@ export default React.createClass({
return ( return (
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}> <div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
<div className="mx_AppTileMenuBar"> <div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
{ this.formatAppTileName() } { this.formatAppTileName() }
<span className="mx_AppTileMenuBarWidgets"> <span className="mx_AppTileMenuBarWidgets">
<span className="mx_Beta" alt={betaHelpMsg} title={betaHelpMsg}>&#946;</span>
{ /* Edit widget */ } { /* Edit widget */ }
{ showEditButton && <img { showEditButton && <img
src="img/edit.svg" src="img/edit.svg"

View file

@ -0,0 +1,149 @@
/*
Copyright 2017 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import {_t} from '../../../languageHandler.js';
const EditableItem = React.createClass({
displayName: 'EditableItem',
propTypes: {
initialValue: PropTypes.string,
index: PropTypes.number,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onRemove: PropTypes.func,
onAdd: PropTypes.func,
addOnChange: PropTypes.bool,
},
onChange: function(value) {
this.setState({ value });
if (this.props.onChange) this.props.onChange(value, this.props.index);
if (this.props.addOnChange && this.props.onAdd) this.props.onAdd(value);
},
onRemove: function() {
if (this.props.onRemove) this.props.onRemove(this.props.index);
},
onAdd: function() {
if (this.props.onAdd) this.props.onAdd(this.state.value);
},
render: function() {
const EditableText = sdk.getComponent('elements.EditableText');
return <div className="mx_EditableItem">
<EditableText
className="mx_EditableItem_editable"
placeholderClassName="mx_EditableItem_editablePlaceholder"
placeholder={this.props.placeholder}
blurToCancel={false}
editable={true}
initialValue={this.props.initialValue}
onValueChanged={this.onChange} />
{ this.props.onAdd ?
<div className="mx_EditableItem_addButton">
<img className="mx_filterFlipColor"
src="img/plus.svg" width="14" height="14"
alt={_t("Add")} onClick={this.onAdd} />
</div>
:
<div className="mx_EditableItem_removeButton">
<img className="mx_filterFlipColor"
src="img/cancel-small.svg" width="14" height="14"
alt={_t("Delete")} onClick={this.onRemove} />
</div>
}
</div>;
},
});
module.exports = React.createClass({
displayName: 'EditableItemList',
propTypes: {
items: PropTypes.arrayOf(PropTypes.string).isRequired,
onNewItemChanged: PropTypes.func,
onItemAdded: PropTypes.func,
onItemEdited: PropTypes.func,
onItemRemoved: PropTypes. func,
},
getDefaultProps: function() {
return {
onItemAdded: () => {},
onItemEdited: () => {},
onItemRemoved: () => {},
onNewItemChanged: () => {},
};
},
onItemAdded: function(value) {
this.props.onItemAdded(value);
},
onItemEdited: function(value, index) {
if (value.length === 0) {
this.onItemRemoved(index);
} else {
this.props.onItemEdited(value, index);
}
},
onItemRemoved: function(index) {
this.props.onItemRemoved(index);
},
onNewItemChanged: function(value) {
this.props.onNewItemChanged(value);
},
render: function() {
const editableItems = this.props.items.map((item, index) => {
return <EditableItem
key={index}
index={index}
initialValue={item}
onChange={this.onItemEdited}
onRemove={this.onItemRemoved}
placeholder={this.props.placeholder}
/>;
});
const label = this.props.items.length > 0 ?
this.props.itemsLabel : this.props.noItemsLabel;
return (<div className="mx_EditableItemList">
<div className="mx_EditableItemList_label">
{ label }
</div>
{ editableItems }
<EditableItem
key={-1}
initialValue={this.props.newItem}
onAdd={this.onItemAdded}
onChange={this.onNewItemChanged}
addOnChange={true}
placeholder={this.props.placeholder}
/>
</div>);
},
});

View file

@ -65,7 +65,9 @@ module.exports = React.createClass({
}, },
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) { if (nextProps.initialValue !== this.props.initialValue ||
nextProps.initialValue !== this.value
) {
this.value = nextProps.initialValue; this.value = nextProps.initialValue;
if (this.refs.editable_div) { if (this.refs.editable_div) {
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2016 Aviral Dasgupta Copyright 2016 Aviral Dasgupta
Copyright 2017 New Vector 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,12 +16,19 @@
*/ */
import React from 'react'; import React from 'react';
import {emojifyText} from '../../../HtmlUtils'; import {emojifyText, containsEmoji} from '../../../HtmlUtils';
export default function EmojiText(props) { export default function EmojiText(props) {
const {element, children, ...restProps} = props; const {element, children, ...restProps} = props;
// fast path: simple regex to detect strings that don't contain
// emoji and just return them
if (containsEmoji(children)) {
restProps.dangerouslySetInnerHTML = emojifyText(children); restProps.dangerouslySetInnerHTML = emojifyText(children);
return React.createElement(element, restProps); return React.createElement(element, restProps);
} else {
return React.createElement(element, restProps, children);
}
} }
EmojiText.propTypes = { EmojiText.propTypes = {

View file

@ -0,0 +1,290 @@
/*
Copyright 2017 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk';
import UserSettingsStore from '../../../UserSettingsStore';
import dis from '../../../dispatcher';
import Promise from 'bluebird';
const BULK_REQUEST_DEBOUNCE_MS = 200;
// Does the server support groups? Assume yes until we receive M_UNRECOGNIZED.
// If true, flair can function and we should keep sending requests for groups and avatars.
let groupSupport = true;
const USER_GROUPS_CACHE_BUST_MS = 1800000; // 30 mins
const GROUP_PROFILES_CACHE_BUST_MS = 1800000; // 30 mins
// TODO: Cache-busting based on time. (The server won't inform us of membership changes.)
// This applies to userGroups and groupProfiles. We can provide a slightly better UX by
// cache-busting when the current user joins/leaves a group.
const userGroups = {
// $userId: ['+group1:domain', '+group2:domain', ...]
};
const groupProfiles = {
// $groupId: {
// avatar_url: 'mxc://...'
// }
};
// Represents all unsettled promises to retrieve the groups for each userId. When a promise
// is settled, it is deleted from this object.
const usersPending = {
// $userId: {
// prom: Promise
// resolve: () => {}
// reject: () => {}
// }
};
let debounceTimeoutID;
function getPublicisedGroupsCached(matrixClient, userId) {
if (userGroups[userId]) {
return Promise.resolve(userGroups[userId]);
}
// Bulk lookup ongoing, return promise to resolve/reject
if (usersPending[userId]) {
return usersPending[userId].prom;
}
usersPending[userId] = {};
usersPending[userId].prom = new Promise((resolve, reject) => {
usersPending[userId].resolve = resolve;
usersPending[userId].reject = reject;
}).then((groups) => {
userGroups[userId] = groups;
setTimeout(() => {
delete userGroups[userId];
}, USER_GROUPS_CACHE_BUST_MS);
return userGroups[userId];
}).catch((err) => {
throw err;
}).finally(() => {
delete usersPending[userId];
});
// This debounce will allow consecutive requests for the public groups of users that
// are sent in intervals of < BULK_REQUEST_DEBOUNCE_MS to be batched and only requested
// when no more requests are received within the next BULK_REQUEST_DEBOUNCE_MS. The naive
// implementation would do a request that only requested the groups for `userId`, leading
// to a worst and best case of 1 user per request. This implementation's worst is still
// 1 user per request but only if the requests are > BULK_REQUEST_DEBOUNCE_MS apart and the
// best case is N users per request.
//
// This is to reduce the number of requests made whilst trading off latency when viewing
// a Flair component.
if (debounceTimeoutID) clearTimeout(debounceTimeoutID);
debounceTimeoutID = setTimeout(() => {
batchedGetPublicGroups(matrixClient);
}, BULK_REQUEST_DEBOUNCE_MS);
return usersPending[userId].prom;
}
async function batchedGetPublicGroups(matrixClient) {
// Take the userIds from the keys of usersPending
const usersInFlight = Object.keys(usersPending);
let resp = {
users: [],
};
try {
resp = await matrixClient.getPublicisedGroups(usersInFlight);
} catch (err) {
// Propagate the same error to all usersInFlight
usersInFlight.forEach((userId) => {
usersPending[userId].reject(err);
});
return;
}
const updatedUserGroups = resp.users;
usersInFlight.forEach((userId) => {
usersPending[userId].resolve(updatedUserGroups[userId] || []);
});
}
async function getGroupProfileCached(matrixClient, groupId) {
if (groupProfiles[groupId]) {
return groupProfiles[groupId];
}
const profile = await matrixClient.getGroupProfile(groupId);
groupProfiles[groupId] = {
groupId,
avatarUrl: profile.avatar_url,
};
setTimeout(() => {
delete groupProfiles[groupId];
}, GROUP_PROFILES_CACHE_BUST_MS);
return groupProfiles[groupId];
}
class FlairAvatar extends React.Component {
constructor() {
super();
this.onClick = this.onClick.bind(this);
}
onClick(ev) {
ev.preventDefault();
// Don't trigger onClick of parent element
ev.stopPropagation();
dis.dispatch({
action: 'view_group',
group_id: this.props.groupProfile.groupId,
});
}
render() {
const httpUrl = this.context.matrixClient.mxcUrlToHttp(
this.props.groupProfile.avatarUrl, 14, 14, 'scale', false);
return <img
src={httpUrl}
width="14px"
height="14px"
onClick={this.onClick}
title={this.props.groupProfile.groupId} />;
}
}
FlairAvatar.propTypes = {
groupProfile: PropTypes.shape({
groupId: PropTypes.string.isRequired,
avatarUrl: PropTypes.string.isRequired,
}),
};
FlairAvatar.contextTypes = {
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};
export default class Flair extends React.Component {
constructor() {
super();
this.state = {
profiles: [],
};
this.onRoomStateEvents = this.onRoomStateEvents.bind(this);
}
componentWillUnmount() {
this._unmounted = true;
this.context.matrixClient.removeListener('RoomState.events', this.onRoomStateEvents);
}
componentWillMount() {
this._unmounted = false;
if (UserSettingsStore.isFeatureEnabled('feature_groups') && groupSupport) {
this._generateAvatars();
}
this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
}
onRoomStateEvents(event) {
if (event.getType() === 'm.room.related_groups' && groupSupport) {
this._generateAvatars();
}
}
async _getGroupProfiles(groups) {
const profiles = [];
for (const groupId of groups) {
let groupProfile = null;
try {
groupProfile = await getGroupProfileCached(this.context.matrixClient, groupId);
} catch (err) {
console.error('Could not get profile for group', groupId, err);
}
profiles.push(groupProfile);
}
return profiles.filter((p) => p !== null);
}
async _generateAvatars() {
let groups;
try {
groups = await getPublicisedGroupsCached(this.context.matrixClient, this.props.userId);
} catch (err) {
// Indicate whether the homeserver supports groups
if (err.errcode === 'M_UNRECOGNIZED') {
console.warn('Cannot display flair, server does not support groups');
groupSupport = false;
// Return silently to avoid spamming for non-supporting servers
return;
}
console.error('Could not get groups for user', this.props.userId, err);
}
if (this.props.roomId && this.props.showRelated) {
const relatedGroupsEvent = this.context.matrixClient
.getRoom(this.props.roomId)
.currentState
.getStateEvents('m.room.related_groups', '');
const relatedGroups = relatedGroupsEvent ?
relatedGroupsEvent.getContent().groups || [] : [];
if (relatedGroups && relatedGroups.length > 0) {
groups = groups.filter((groupId) => {
return relatedGroups.includes(groupId);
});
} else {
groups = [];
}
}
if (!groups || groups.length === 0) {
return;
}
const profiles = await this._getGroupProfiles(groups);
if (!this.unmounted) {
this.setState({profiles});
}
}
render() {
if (this.state.profiles.length === 0) {
return <div />;
}
const avatars = this.state.profiles.map((profile, index) => {
return <FlairAvatar key={index} groupProfile={profile} />;
});
return (
<span className="mx_Flair" style={{"marginLeft": "5px", "verticalAlign": "-3px"}}>
{ avatars }
</span>
);
}
}
Flair.propTypes = {
userId: PropTypes.string,
// Whether to show only the flair associated with related groups of the given room,
// or all flair associated with a user.
showRelated: PropTypes.bool,
// The room that this flair will be displayed in. Optional. Only applies when showRelated = true.
roomId: PropTypes.string,
};
// TODO: We've decided that all components should follow this pattern, which means removing withMatrixClient and using
// this.context.matrixClient everywhere instead of this.props.matrixClient.
// See https://github.com/vector-im/riot-web/issues/4951.
Flair.contextTypes = {
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};

View file

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

View file

@ -0,0 +1,107 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import classNames from 'classnames';
import SdkConfig from '../../../SdkConfig';
import ScalarAuthClient from '../../../ScalarAuthClient';
import ScalarMessaging from '../../../ScalarMessaging';
import Modal from "../../../Modal";
import { _t } from '../../../languageHandler';
import AccessibleButton from './AccessibleButton';
import TintableSvg from './TintableSvg';
export default class ManageIntegsButton extends React.Component {
constructor(props) {
super(props);
this.state = {
scalarError: null,
};
this.onManageIntegrations = this.onManageIntegrations.bind(this);
}
componentWillMount() {
ScalarMessaging.startListening();
this.scalarClient = null;
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => {
this.forceUpdate();
}, (err) => {
this.setState({ scalarError: err});
console.error('Error whilst initialising scalarClient for ManageIntegsButton', err);
});
}
}
componentWillUnmount() {
ScalarMessaging.stopListening();
}
onManageIntegrations(ev) {
ev.preventDefault();
if (this.state.scalarError && !this.scalarClient.hasCredentials()) {
return;
}
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
Modal.createDialog(IntegrationsManager, {
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.roomId) :
null,
}, "mx_IntegrationsManager");
}
render() {
let integrationsButton = <div />;
let integrationsWarningTriangle = <div />;
let integrationsErrorPopup = <div />;
if (this.scalarClient !== null) {
const integrationsButtonClasses = classNames({
mx_RoomHeader_button: true,
mx_RoomSettings_integrationsButton_error: !!this.state.scalarError,
});
if (this.state.scalarError && !this.scalarClient.hasCredentials()) {
integrationsWarningTriangle = <img src="img/warning.svg" title={_t('Integrations Error')} width="17" />;
// Popup shown when hovering over integrationsButton_error (via CSS)
integrationsErrorPopup = (
<span className="mx_RoomSettings_integrationsButton_errorPopup">
{ _t('Could not connect to the integration server') }
</span>
);
}
integrationsButton = (
<AccessibleButton className={integrationsButtonClasses} onClick={this.onManageIntegrations} title={_t('Manage Integrations')}>
<TintableSvg src="img/icons-apps.svg" width="35" height="35" />
{ integrationsWarningTriangle }
{ integrationsErrorPopup }
</AccessibleButton>
);
}
return integrationsButton;
}
}
ManageIntegsButton.propTypes = {
roomId: PropTypes.string.isRequired,
};

View file

@ -34,11 +34,13 @@ module.exports = React.createClass({
threshold: React.PropTypes.number, threshold: React.PropTypes.number,
// Called when the MELS expansion is toggled // Called when the MELS expansion is toggled
onToggle: React.PropTypes.func, onToggle: React.PropTypes.func,
// Whether or not to begin with state.expanded=true
startExpanded: React.PropTypes.bool,
}, },
getInitialState: function() { getInitialState: function() {
return { return {
expanded: false, expanded: Boolean(this.props.startExpanded),
}; };
}, },

View file

@ -171,7 +171,7 @@ const Pill = React.createClass({
} }
pillClass = 'mx_UserPill'; pillClass = 'mx_UserPill';
href = null; href = null;
onClick = this.onUserPillClicked.bind(this); onClick = this.onUserPillClicked;
} }
} }
break; break;

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 New Vector 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.
@ -13,7 +14,9 @@ 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.
*/ */
var React = require('react');
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
module.exports = React.createClass({ module.exports = React.createClass({
@ -21,12 +24,21 @@ module.exports = React.createClass({
propTypes: { propTypes: {
// The number of elements to show before truncating. If negative, no truncation is done. // The number of elements to show before truncating. If negative, no truncation is done.
truncateAt: React.PropTypes.number, truncateAt: PropTypes.number,
// The className to apply to the wrapping div // The className to apply to the wrapping div
className: React.PropTypes.string, className: PropTypes.string,
// A function that returns the children to be rendered into the element.
// function getChildren(start: number, end: number): Array<React.Node>
// The start element is included, the end is not (as in `slice`).
// If omitted, the React child elements will be used. This parameter can be used
// to avoid creating unnecessary React elements.
getChildren: PropTypes.func,
// A function that should return the total number of child element available.
// Required if getChildren is supplied.
getChildCount: PropTypes.func,
// A function which will be invoked when an overflow element is required. // A function which will be invoked when an overflow element is required.
// This will be inserted after the children. // This will be inserted after the children.
createOverflowElement: React.PropTypes.func createOverflowElement: PropTypes.func,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -36,38 +48,54 @@ module.exports = React.createClass({
return ( return (
<div>{ _t("And %(count)s more...", {count: overflowCount}) }</div> <div>{ _t("And %(count)s more...", {count: overflowCount}) }</div>
); );
} },
}; };
}, },
render: function() { _getChildren: function(start, end) {
var childsJsx = this.props.children; if (this.props.getChildren && this.props.getChildCount) {
var overflowJsx; return this.props.getChildren(start, end);
var childArray = React.Children.toArray(this.props.children).filter((c) => { } else {
// XXX: I'm not sure why anything would pass null into this, it seems
// like a bizzare case to handle, but I'm preserving the behaviour.
// (see commit 38d5c7d5c5d5a34dc16ef5d46278315f5c57f542)
return React.Children.toArray(this.props.children).filter((c) => {
return c != null; return c != null;
}); }).slice(start, end);
}
},
var childCount = childArray.length; _getChildCount: function() {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount();
} else {
return React.Children.toArray(this.props.children).filter((c) => {
return c != null;
}).length;
}
},
render: function() {
let overflowNode = null;
const totalChildren = this._getChildCount();
let upperBound = totalChildren;
if (this.props.truncateAt >= 0) { if (this.props.truncateAt >= 0) {
var overflowCount = childCount - this.props.truncateAt; const overflowCount = totalChildren - this.props.truncateAt;
if (overflowCount > 1) { if (overflowCount > 1) {
overflowJsx = this.props.createOverflowElement( overflowNode = this.props.createOverflowElement(
overflowCount, childCount overflowCount, totalChildren,
); );
upperBound = this.props.truncateAt;
// cut out the overflow elements
childArray.splice(childCount - overflowCount, overflowCount);
childsJsx = childArray; // use what is left
} }
} }
const childNodes = this._getChildren(0, upperBound);
return ( return (
<div className={this.props.className}> <div className={this.props.className}>
{childsJsx} { childNodes }
{overflowJsx} { overflowNode }
</div> </div>
); );
} },
}); });

View file

@ -0,0 +1,70 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import AccessibleButton from '../elements/AccessibleButton';
export default React.createClass({
displayName: 'GroupInviteTile',
propTypes: {
group: PropTypes.object.isRequired,
},
onClick: function(e) {
dis.dispatch({
action: 'view_group',
group_id: this.props.group.groupId,
});
},
render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EmojiText = sdk.getComponent('elements.EmojiText');
const av = (
<BaseAvatar name={this.props.group.name} width={24} height={24}
url={this.props.group.avatarUrl}
/>
);
const label = <EmojiText
element="div"
title={this.props.group.name}
className="mx_GroupInviteTile_name"
dir="auto"
>
{ this.props.group.name }
</EmojiText>;
const badge = <div className="mx_GroupInviteTile_badge">!</div>;
return (
<AccessibleButton className="mx_GroupInviteTile" onClick={this.onClick}>
<div className="mx_GroupInviteTile_avatarContainer">
{ av }
</div>
<div className="mx_GroupInviteTile_nameContainer">
{ label }
{ badge }
</div>
</AccessibleButton>
);
},
});

View file

@ -0,0 +1,195 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import PropTypes from 'prop-types';
import React from 'react';
import dis from '../../../dispatcher';
import Modal from '../../../Modal';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { GroupMemberType } from '../../../groups';
import { groupMemberFromApiObject } from '../../../groups';
import withMatrixClient from '../../../wrappers/withMatrixClient';
import AccessibleButton from '../elements/AccessibleButton';
import GeminiScrollbar from 'react-gemini-scrollbar';
module.exports = withMatrixClient(React.createClass({
displayName: 'GroupMemberInfo',
propTypes: {
matrixClient: PropTypes.object.isRequired,
groupId: PropTypes.string,
groupMember: GroupMemberType,
},
getInitialState: function() {
return {
fetching: false,
removingUser: false,
groupMembers: null,
};
},
componentWillMount: function() {
this._fetchMembers();
},
_fetchMembers: function() {
this.setState({fetching: true});
this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => {
this.setState({
groupMembers: result.chunk.map((apiMember) => {
return groupMemberFromApiObject(apiMember);
}),
fetching: false,
});
}).catch((e) => {
this.setState({fetching: false});
console.error("Failed to get group groupMember list: ", e);
});
},
_onKick: function() {
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createDialog(ConfirmUserActionDialog, {
groupMember: this.props.groupMember,
action: _t('Remove from group'),
danger: true,
onFinished: (proceed) => {
if (!proceed) return;
this.setState({removingUser: true});
this.props.matrixClient.removeUserFromGroup(
this.props.groupId, this.props.groupMember.userId,
).then(() => {
// return to the user list
dis.dispatch({
action: "view_user",
member: null,
});
}).catch((e) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
title: _t('Error'),
description: _t('Failed to remove user from group'),
});
}).finally(() => {
this.setState({removingUser: false});
});
},
});
},
_onCancel: function(e) {
// Go back to the user list
dis.dispatch({
action: "view_user",
member: null,
});
},
onRoomTileClick(roomId) {
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
},
render: function() {
if (this.state.fetching || this.state.removingUser) {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
}
if (!this.state.groupMembers) return null;
const targetIsInGroup = this.state.groupMembers.some((m) => {
return m.userId === this.props.groupMember.userId;
});
let kickButton;
let adminButton;
if (targetIsInGroup) {
kickButton = (
<AccessibleButton className="mx_MemberInfo_field"
onClick={this._onKick}>
{ _t('Remove from group') }
</AccessibleButton>
);
// No make/revoke admin API yet
/*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator");
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
{giveOpLabel}
</AccessibleButton>;*/
}
let adminTools;
if (kickButton || adminButton) {
adminTools =
<div className="mx_MemberInfo_adminTools">
<h3>{ _t("Admin Tools") }</h3>
<div className="mx_MemberInfo_buttons">
{ kickButton }
{ adminButton }
</div>
</div>;
}
const avatarUrl = this.props.matrixClient.mxcUrlToHttp(
this.props.groupMember.avatarUrl,
36, 36, 'crop',
);
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const avatar = (
<BaseAvatar name={this.props.groupMember.userId} width={36} height={36}
url={avatarUrl}
/>
);
const groupMemberName = (
this.props.groupMember.displayname || this.props.groupMember.userId
);
const EmojiText = sdk.getComponent('elements.EmojiText');
return (
<div className="mx_MemberInfo">
<GeminiScrollbar autoshow={true}>
<AccessibleButton className="mx_MemberInfo_cancel"onClick={this._onCancel}>
<img src="img/cancel.svg" width="18" height="18" />
</AccessibleButton>
<div className="mx_MemberInfo_avatar">
{ avatar }
</div>
<EmojiText element="h2">{ groupMemberName }</EmojiText>
<div className="mx_MemberInfo_profile">
<div className="mx_MemberInfo_profileField">
{ this.props.groupMember.userId }
</div>
</div>
{ adminTools }
</GeminiScrollbar>
</div>
);
},
}));

View file

@ -0,0 +1,155 @@
/*
Copyright 2017 Vector Creations Ltd.
Copyright 2017 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import { groupMemberFromApiObject } from '../../../groups';
import GeminiScrollbar from 'react-gemini-scrollbar';
import PropTypes from 'prop-types';
import withMatrixClient from '../../../wrappers/withMatrixClient';
const INITIAL_LOAD_NUM_MEMBERS = 30;
export default withMatrixClient(React.createClass({
displayName: 'GroupMemberList',
propTypes: {
matrixClient: PropTypes.object.isRequired,
groupId: PropTypes.string.isRequired,
},
getInitialState: function() {
return {
fetching: false,
members: null,
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
};
},
componentWillMount: function() {
this._unmounted = false;
this._fetchMembers();
},
_fetchMembers: function() {
this.setState({fetching: true});
this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => {
this.setState({
members: result.chunk.map((apiMember) => {
return groupMemberFromApiObject(apiMember);
}),
fetching: false,
});
}).catch((e) => {
this.setState({fetching: false});
console.error("Failed to get group member list: " + e);
});
},
_createOverflowTile: function(overflowCount, totalCount) {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullMemberList} />
);
},
_showFullMemberList: function() {
this.setState({
truncateAt: -1,
});
},
onSearchQueryChanged: function(ev) {
this.setState({ searchQuery: ev.target.value });
},
makeGroupMemberTiles: function(query) {
const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile");
query = (query || "").toLowerCase();
let memberList = this.state.members;
if (query) {
memberList = memberList.filter((m) => {
const matchesName = m.displayname.toLowerCase().indexOf(query) !== -1;
const matchesId = m.userId.toLowerCase().includes(query);
if (!matchesName && !matchesId) {
return false;
}
return true;
});
}
memberList = memberList.map((m) => {
return (
<GroupMemberTile key={m.userId} groupId={this.props.groupId} member={m} />
);
});
memberList.sort((a, b) => {
// TODO: should put admins at the top: we don't yet have that info
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return memberList;
},
render: function() {
if (this.state.fetching) {
const Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_MemberList">
<Spinner />
</div>);
} else if (this.state.members === null) {
return null;
}
const inputBox = (
<form autoComplete="off">
<input className="mx_GroupMemberList_query" id="mx_GroupMemberList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={_t('Filter group members')} />
</form>
);
const TruncatedList = sdk.getComponent("elements.TruncatedList");
return (
<div className="mx_MemberList">
{ inputBox }
<GeminiScrollbar autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper">
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}>
{ this.makeGroupMemberTiles(this.state.searchQuery) }
</TruncatedList>
</GeminiScrollbar>
</div>
);
},
}));

View file

@ -0,0 +1,70 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { GroupMemberType } from '../../../groups';
import withMatrixClient from '../../../wrappers/withMatrixClient';
export default withMatrixClient(React.createClass({
displayName: 'GroupMemberTile',
propTypes: {
matrixClient: PropTypes.object,
groupId: PropTypes.string.isRequired,
member: GroupMemberType.isRequired,
},
getInitialState: function() {
return {};
},
onClick: function(e) {
dis.dispatch({
action: 'view_group_user',
member: this.props.member,
groupId: this.props.groupId,
});
},
render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
const name = this.props.member.displayname || this.props.member.userId;
const avatarUrl = this.props.matrixClient.mxcUrlToHttp(
this.props.member.avatarUrl,
36, 36, 'crop',
);
const av = (
<BaseAvatar name={this.props.member.userId}
width={36} height={36}
url={avatarUrl}
/>
);
return (
<EntityTile presenceState="online"
avatarJsx={av} onClick={this.onClick}
name={name} powerLevel={0} suppressOnHover={true}
/>
);
},
}));

View file

@ -0,0 +1,143 @@
/*
Copyright 2017 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import { groupRoomFromApiObject } from '../../../groups';
import GeminiScrollbar from 'react-gemini-scrollbar';
import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk';
const INITIAL_LOAD_NUM_ROOMS = 30;
export default React.createClass({
contextTypes: {
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
},
propTypes: {
groupId: PropTypes.string.isRequired,
},
getInitialState: function() {
return {
fetching: false,
rooms: null,
truncateAt: INITIAL_LOAD_NUM_ROOMS,
searchQuery: "",
};
},
componentWillMount: function() {
this._unmounted = false;
this._fetchRooms();
},
_fetchRooms: function() {
this.setState({fetching: true});
this.context.matrixClient.getGroupRooms(this.props.groupId).then((result) => {
this.setState({
rooms: result.chunk.map((apiRoom) => {
return groupRoomFromApiObject(apiRoom);
}),
fetching: false,
});
}).catch((e) => {
this.setState({fetching: false});
console.error("Failed to get group room list: ", e);
});
},
_createOverflowTile: function(overflowCount, totalCount) {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullRoomList} />
);
},
_showFullRoomList: function() {
this.setState({
truncateAt: -1,
});
},
onSearchQueryChanged: function(ev) {
this.setState({ searchQuery: ev.target.value });
},
makeGroupRoomTiles: function(query) {
const GroupRoomTile = sdk.getComponent("groups.GroupRoomTile");
query = (query || "").toLowerCase();
let roomList = this.state.rooms;
if (query) {
roomList = roomList.filter((room) => {
const matchesName = (room.name || "").toLowerCase().include(query);
const matchesAlias = (room.canonicalAlias || "").toLowerCase().includes(query);
return matchesName || matchesAlias;
});
}
roomList = roomList.map((groupRoom, index) => {
return (
<GroupRoomTile
key={index}
groupId={this.props.groupId}
groupRoom={groupRoom} />
);
});
return roomList;
},
render: function() {
if (this.state.fetching) {
const Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_GroupRoomList">
<Spinner />
</div>);
} else if (this.state.rooms === null) {
return null;
}
const inputBox = (
<form autoComplete="off">
<input className="mx_GroupRoomList_query" id="mx_GroupRoomList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={_t('Filter group rooms')} />
</form>
);
const TruncatedList = sdk.getComponent("elements.TruncatedList");
return (
<div className="mx_GroupRoomList">
{ inputBox }
<GeminiScrollbar autoshow={true} className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}>
{ this.makeGroupRoomTiles(this.state.searchQuery) }
</TruncatedList>
</GeminiScrollbar>
</div>
);
},
});

View file

@ -0,0 +1,135 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import {MatrixClient} from 'matrix-js-sdk';
import { _t } from '../../../languageHandler';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { GroupRoomType } from '../../../groups';
import Modal from '../../../Modal';
const GroupRoomTile = React.createClass({
displayName: 'GroupRoomTile',
propTypes: {
groupId: PropTypes.string.isRequired,
groupRoom: GroupRoomType.isRequired,
},
getInitialState: function() {
return {
name: this.calculateRoomName(this.props.groupRoom),
};
},
componentWillReceiveProps: function(newProps) {
this.setState({
name: this.calculateRoomName(newProps.groupRoom),
});
},
calculateRoomName: function(groupRoom) {
return groupRoom.name || groupRoom.canonicalAlias || _t("Unnamed Room");
},
removeRoomFromGroup: function() {
const groupId = this.props.groupId;
const roomName = this.state.name;
const roomId = this.props.groupRoom.roomId;
this.context.matrixClient
.removeRoomFromGroup(groupId, roomId)
.catch((err) => {
console.error(`Error whilst removing ${roomId} from ${groupId}`, err);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {
title: _t("Failed to remove room from group"),
description: _t("Failed to remove '%(roomName)s' from %(groupId)s", {groupId, roomName}),
});
});
},
onClick: function(e) {
let roomId;
let roomAlias;
if (this.props.groupRoom.canonicalAlias) {
roomAlias = this.props.groupRoom.canonicalAlias;
} else {
roomId = this.props.groupRoom.roomId;
}
dis.dispatch({
action: 'view_room',
room_id: roomId,
room_alias: roomAlias,
});
},
onDeleteClick: function(e) {
const groupId = this.props.groupId;
const roomName = this.state.name;
e.preventDefault();
e.stopPropagation();
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Confirm removal of group from room', '', QuestionDialog, {
title: _t("Are you sure you want to remove '%(roomName)s' from %(groupId)s?", {roomName, groupId}),
description: _t("Removing a room from the group will also remove it from the group page."),
button: _t("Remove"),
onFinished: (success) => {
if (success) {
this.removeRoomFromGroup();
}
},
});
},
render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
this.props.groupRoom.avatarUrl,
36, 36, 'crop',
);
const av = (
<BaseAvatar name={this.state.name}
width={36} height={36}
url={avatarUrl}
/>
);
return (
<AccessibleButton className="mx_GroupRoomTile" onClick={this.onClick}>
<div className="mx_GroupRoomTile_avatar">
{ av }
</div>
<div className="mx_GroupRoomTile_name">
{ this.state.name }
</div>
<AccessibleButton className="mx_GroupRoomTile_delete" onClick={this.onDeleteClick}>
<img src="img/cancel-small.svg" />
</AccessibleButton>
</AccessibleButton>
);
},
});
GroupRoomTile.contextTypes = {
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};
export default GroupRoomTile;

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import { _t, _tJsx } from '../../../languageHandler'; import { _t, _tJsx } from '../../../languageHandler';
var DIV_ID = 'mx_recaptcha'; var DIV_ID = 'mx_recaptcha';
@ -66,11 +67,10 @@ module.exports = React.createClass({
// * jumping straight to a hosted captcha page (but we don't support that yet) // * jumping straight to a hosted captcha page (but we don't support that yet)
// * embedding the captcha in an iframe (if that works) // * embedding the captcha in an iframe (if that works)
// * using a better captcha lib // * using a better captcha lib
warning.innerHTML = _tJsx( ReactDOM.render(_tJsx(
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>", "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/,
(sub) => { return "<a href='https://riot.im/app'>{ sub }</a>"; } (sub) => { return <a href='https://riot.im/app'>{ sub }</a>; }), warning);
);
this.refs.recaptchaContainer.appendChild(warning); this.refs.recaptchaContainer.appendChild(warning);
} }
else { else {

View file

@ -18,6 +18,7 @@
import React from 'react'; import React from 'react';
import sdk from '../../../index'; import sdk from '../../../index';
import Flair from '../elements/Flair.js';
export default function SenderProfile(props) { export default function SenderProfile(props) {
const EmojiText = sdk.getComponent('elements.EmojiText'); const EmojiText = sdk.getComponent('elements.EmojiText');
@ -30,8 +31,17 @@ export default function SenderProfile(props) {
} }
return ( return (
<EmojiText className="mx_SenderProfile" dir="auto" <div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
onClick={props.onClick}>{`${name || ''} ${props.aux || ''}`}</EmojiText> <EmojiText className="mx_SenderProfile_name">{ name || '' }</EmojiText>
{ props.enableFlair ?
<Flair
userId={mxEvent.getSender()}
roomId={mxEvent.getRoomId()}
showRelated={true} />
: null
}
{ props.aux ? <EmojiText className="mx_SenderProfile_aux"> { props.aux }</EmojiText> : null }
</div>
); );
} }

View file

@ -31,6 +31,7 @@ import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import UserSettingsStore from "../../../UserSettingsStore"; import UserSettingsStore from "../../../UserSettingsStore";
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import ContextualMenu from '../../structures/ContextualMenu';
import {RoomMember} from 'matrix-js-sdk'; import {RoomMember} from 'matrix-js-sdk';
import classNames from 'classnames'; import classNames from 'classnames';
@ -72,12 +73,16 @@ module.exports = React.createClass({
textArea.value = text; textArea.value = text;
document.body.appendChild(textArea); document.body.appendChild(textArea);
textArea.select(); textArea.select();
let successful = false;
try { try {
const successful = document.execCommand('copy'); successful = document.execCommand('copy');
} catch (err) { } catch (err) {
console.log('Unable to copy'); console.log('Unable to copy');
} }
document.body.removeChild(textArea); document.body.removeChild(textArea);
return successful;
}, },
componentDidMount: function() { componentDidMount: function() {
@ -113,14 +118,7 @@ module.exports = React.createClass({
} }
}, 10); }, 10);
} }
// add event handlers to the 'copy code' buttons this._addCodeCopyButton();
const buttons = ReactDOM.findDOMNode(this).getElementsByClassName("mx_EventTile_copyButton");
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = (e) => {
const copyCode = buttons[i].parentNode.getElementsByTagName("code")[0];
this.copyToClipboard(copyCode.textContent);
};
}
} }
}, },
@ -257,6 +255,33 @@ module.exports = React.createClass({
} }
}, },
_addCodeCopyButton() {
// Add 'copy' buttons to pre blocks
ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre').forEach((p) => {
const button = document.createElement("span");
button.className = "mx_EventTile_copyButton";
button.onclick = (e) => {
const copyCode = button.parentNode.getElementsByTagName("code")[0];
const successful = this.copyToClipboard(copyCode.textContent);
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const buttonRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const x = buttonRect.right + window.pageXOffset;
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
const {close} = ContextualMenu.createMenu(GenericTextContextMenu, {
chevronOffset: 10,
left: x,
top: y,
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
e.target.onmouseout = close;
};
p.appendChild(button);
});
},
onCancelClick: function(event) { onCancelClick: function(event) {
this.setState({ widgetHidden: true }); this.setState({ widgetHidden: true });
// FIXME: persist this somewhere smarter than local storage // FIXME: persist this somewhere smarter than local storage

View file

@ -23,9 +23,14 @@ module.exports = React.createClass({
displayName: 'UnknownBody', displayName: 'UnknownBody',
render: function() { render: function() {
let tooltip = _t("Removed or unknown message type");
if (this.props.mxEvent.isRedacted()) {
tooltip = _t("Message removed by %(userId)s", {userId: this.props.mxEvent.getSender()});
}
const text = this.props.mxEvent.getContent().body; const text = this.props.mxEvent.getContent().body;
return ( return (
<span className="mx_UnknownBody" title={_t("Removed or unknown message type")}> <span className="mx_UnknownBody" title={tooltip}>
{ text } { text }
</span> </span>
); );

View file

@ -136,24 +136,25 @@ module.exports = React.createClass({
return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases);
}, },
onAliasAdded: function(alias) { onNewAliasChanged: function(value) {
this.setState({newAlias: value});
},
onLocalAliasAdded: function(alias) {
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
if (this.isAliasValid(alias)) { const localDomain = MatrixClientPeg.get().getDomain();
// add this alias to the domain to aliases dict if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
var domain = alias.replace(/^.*?:/, ''); this.state.domainToAliases[localDomain] = this.state.domainToAliases[localDomain] || [];
// XXX: do we need to deep copy aliases before editing it? this.state.domainToAliases[localDomain].push(alias);
this.state.domainToAliases[domain] = this.state.domainToAliases[domain] || [];
this.state.domainToAliases[domain].push(alias);
this.setState({
domainToAliases: this.state.domainToAliases
});
// reset the add field this.setState({
this.refs.add_alias.setValue(''); // FIXME domainToAliases: this.state.domainToAliases,
} // Reset the add field
else { newAlias: "",
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); });
} else {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, { Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, {
title: _t('Invalid alias format'), title: _t('Invalid alias format'),
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }), description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
@ -161,15 +162,13 @@ module.exports = React.createClass({
} }
}, },
onAliasChanged: function(domain, index, alias) { onLocalAliasChanged: function(alias, index) {
if (alias === "") return; // hit the delete button to delete please if (alias === "") return; // hit the delete button to delete please
var oldAlias; const localDomain = MatrixClientPeg.get().getDomain();
if (this.isAliasValid(alias)) { if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
oldAlias = this.state.domainToAliases[domain][index]; this.state.domainToAliases[localDomain][index] = alias;
this.state.domainToAliases[domain][index] = alias; } else {
} const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, { Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, {
title: _t('Invalid address format'), title: _t('Invalid address format'),
description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }), description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }),
@ -177,15 +176,16 @@ module.exports = React.createClass({
} }
}, },
onAliasDeleted: function(domain, index) { onLocalAliasDeleted: function(index) {
const localDomain = MatrixClientPeg.get().getDomain();
// It's a bit naughty to directly manipulate this.state, and React would // It's a bit naughty to directly manipulate this.state, and React would
// normally whine at you, but it can't see us doing the splice. Given we // normally whine at you, but it can't see us doing the splice. Given we
// promptly setState anyway, it's just about acceptable. The alternative // promptly setState anyway, it's just about acceptable. The alternative
// would be to arbitrarily deepcopy to a temp variable and then setState // would be to arbitrarily deepcopy to a temp variable and then setState
// that, but why bother when we can cut this corner. // that, but why bother when we can cut this corner.
var alias = this.state.domainToAliases[domain].splice(index, 1); this.state.domainToAliases[localDomain].splice(index, 1);
this.setState({ this.setState({
domainToAliases: this.state.domainToAliases domainToAliases: this.state.domainToAliases,
}); });
}, },
@ -198,6 +198,7 @@ module.exports = React.createClass({
render: function() { render: function() {
var self = this; var self = this;
var EditableText = sdk.getComponent("elements.EditableText"); var EditableText = sdk.getComponent("elements.EditableText");
var EditableItemList = sdk.getComponent("elements.EditableItemList");
var localDomain = MatrixClientPeg.get().getDomain(); var localDomain = MatrixClientPeg.get().getDomain();
var canonical_alias_section; var canonical_alias_section;
@ -257,58 +258,24 @@ module.exports = React.createClass({
<div className="mx_RoomSettings_aliasLabel"> <div className="mx_RoomSettings_aliasLabel">
{ _t('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"> <EditableItemList
{ (this.state.domainToAliases[localDomain] && className={"mx_RoomSettings_localAliases"}
this.state.domainToAliases[localDomain].length > 0) items={this.state.domainToAliases[localDomain] || []}
? _t('Local addresses for this room:') newItem={this.state.newAlias}
: _t('This room has no local addresses') } onNewItemChanged={this.onNewAliasChanged}
</div> onItemAdded={this.onLocalAliasAdded}
<div className="mx_RoomSettings_aliasesTable"> onItemEdited={this.onLocalAliasChanged}
{ (this.state.domainToAliases[localDomain] || []).map((alias, i) => { onItemRemoved={this.onLocalAliasDeleted}
var deleteButton; itemsLabel={_t('Local addresses for this room:')}
if (this.props.canSetAliases) { noItemsLabel={_t('This room has no local addresses')}
deleteButton = ( placeholder={_t(
<img src="img/cancel-small.svg" width="14" height="14" 'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
alt={ _t('Delete') } onClick={ self.onAliasDeleted.bind(self, localDomain, i) } /> )}
); />
}
return (
<div className="mx_RoomSettings_aliasesTableRow" key={ i }>
<EditableText
className="mx_RoomSettings_alias mx_RoomSettings_editable"
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
blurToCancel={ false }
onValueChanged={ self.onAliasChanged.bind(self, localDomain, i) }
editable={ self.props.canSetAliases }
initialValue={ alias } />
<div className="mx_RoomSettings_deleteAlias mx_filterFlipColor">
{ deleteButton }
</div>
</div>
);
})}
{ this.props.canSetAliases ?
<div className="mx_RoomSettings_aliasesTableRow" key="new">
<EditableText
ref="add_alias"
className="mx_RoomSettings_alias mx_RoomSettings_editable"
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
blurToCancel={ false }
onValueChanged={ self.onAliasAdded } />
<div className="mx_RoomSettings_addAlias mx_filterFlipColor">
<img src="img/plus.svg" width="14" height="14" alt="Add"
onClick={ self.onAliasAdded.bind(self, undefined) }/>
</div>
</div> : ""
}
</div>
{ remote_aliases_section } { remote_aliases_section }
</div> </div>
); );
} },
}); });

View file

@ -0,0 +1,125 @@
/*
Copyright 2017 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
const GROUP_ID_REGEX = /\+\S+\:\S+/;
module.exports = React.createClass({
displayName: 'RelatedGroupSettings',
propTypes: {
roomId: React.PropTypes.string.isRequired,
canSetRelatedRooms: React.PropTypes.bool.isRequired,
relatedGroupsEvent: React.PropTypes.instanceOf(MatrixEvent),
},
contextTypes: {
matrixClient: React.PropTypes.instanceOf(MatrixClient),
},
getDefaultProps: function() {
return {
canSetRelatedRooms: false,
};
},
getInitialState: function() {
return {
newGroupsList: this.props.relatedGroupsEvent ?
(this.props.relatedGroupsEvent.getContent().groups || []) : [],
newGroupId: null,
};
},
saveSettings: function() {
return this.context.matrixClient.sendStateEvent(
this.props.roomId,
'm.room.related_groups',
{
groups: this.state.newGroupsList,
},
'',
);
},
validateGroupId: function(groupId) {
if (!GROUP_ID_REGEX.test(groupId)) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Invalid related group ID', '', ErrorDialog, {
title: _t('Invalid group ID'),
description: _t('\'%(groupId)s\' is not a valid group ID', { groupId }),
});
return false;
}
return true;
},
onNewGroupChanged: function(newGroupId) {
this.setState({ newGroupId });
},
onGroupAdded: function(groupId) {
if (groupId.length === 0 || !this.validateGroupId(groupId)) {
return;
}
this.setState({
newGroupsList: this.state.newGroupsList.concat([groupId]),
newGroupId: '',
});
},
onGroupEdited: function(groupId, index) {
if (groupId.length === 0 || !this.validateGroupId(groupId)) {
return;
}
this.setState({
newGroupsList: Object.assign(this.state.newGroupsList, {[index]: groupId}),
});
},
onGroupDeleted: function(index) {
const newGroupsList = this.state.newGroupsList.slice();
newGroupsList.splice(index, 1),
this.setState({ newGroupsList });
},
render: function() {
const localDomain = this.context.matrixClient.getDomain();
const EditableItemList = sdk.getComponent('elements.EditableItemList');
return (<div>
<h3>{ _t('Related Groups') }</h3>
<EditableItemList
items={this.state.newGroupsList}
className={"mx_RelatedGroupSettings"}
newItem={this.state.newGroupId}
onNewItemChanged={this.onNewGroupChanged}
onItemAdded={this.onGroupAdded}
onItemEdited={this.onGroupEdited}
onItemRemoved={this.onGroupDeleted}
itemsLabel={_t('Related groups for this room:')}
noItemsLabel={_t('This room has no related groups')}
placeholder={_t(
'New group ID (e.g. +foo:%(localDomain)s)', {localDomain},
)}
/>
</div>);
},
});

View file

@ -28,6 +28,8 @@ import ScalarMessaging from '../../../ScalarMessaging';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import WidgetUtils from '../../../WidgetUtils'; import WidgetUtils from '../../../WidgetUtils';
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'AppsDrawer', displayName: 'AppsDrawer',
@ -51,19 +53,18 @@ module.exports = React.createClass({
this.scalarClient = null; this.scalarClient = null;
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
this.scalarClient = new ScalarAuthClient(); this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => { this.scalarClient.connect().then(() => {
this.forceUpdate(); this.forceUpdate();
if (this.state.apps && this.state.apps.length < 1) { }).catch((e) => {
this.onClickAddWidget(); console.log("Failed to connect to integrations server");
}
// TODO -- Handle Scalar errors // TODO -- Handle Scalar errors
// },
// (err) => {
// this.setState({ // this.setState({
// scalar_error: err, // scalar_error: err,
// }); // });
}); });
} }
this.dispatcherRef = dis.register(this.onAction);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
@ -71,6 +72,27 @@ module.exports = React.createClass({
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
} }
dis.unregister(this.dispatcherRef);
},
componentWillReceiveProps(newProps) {
// Room has changed probably, update apps
this._updateApps();
},
onAction: function(action) {
switch (action.action) {
case 'appsDrawer':
// When opening the app draw when there aren't any apps, auto-launch the
// integrations manager to skip the awkward click on "Add widget"
if (action.show) {
const apps = this._getApps();
if (apps.length === 0) {
this._launchManageIntegrations();
}
}
break;
}
}, },
/** /**
@ -93,7 +115,7 @@ module.exports = React.createClass({
return pathTemplate; return pathTemplate;
}, },
_initAppConfig: function(appId, app) { _initAppConfig: function(appId, app, sender) {
const user = MatrixClientPeg.get().getUser(this.props.userId); const user = MatrixClientPeg.get().getUser(this.props.userId);
const params = { const params = {
'$matrix_user_id': this.props.userId, '$matrix_user_id': this.props.userId,
@ -111,6 +133,7 @@ module.exports = React.createClass({
app.id = appId; app.id = appId;
app.name = app.name || app.type; app.name = app.name || app.type;
app.url = this.encodeUri(app.url, params); app.url = this.encodeUri(app.url, params);
app.creatorUserId = (sender && sender.userId) ? sender.userId : null;
return app; return app;
}, },
@ -131,18 +154,12 @@ module.exports = React.createClass({
return appsStateEvents.filter((ev) => { return appsStateEvents.filter((ev) => {
return ev.getContent().type && ev.getContent().url; return ev.getContent().type && ev.getContent().url;
}).map((ev) => { }).map((ev) => {
return this._initAppConfig(ev.getStateKey(), ev.getContent()); return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
}); });
}, },
_updateApps: function() { _updateApps: function() {
const apps = this._getApps(); const apps = this._getApps();
if (apps.length < 1) {
dis.dispatch({
action: 'appsDrawer',
show: false,
});
}
this.setState({ this.setState({
apps: apps, apps: apps,
}); });
@ -157,11 +174,7 @@ module.exports = React.createClass({
} }
}, },
onClickAddWidget: function(e) { _launchManageIntegrations: function() {
if (e) {
e.preventDefault();
}
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') : this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') :
@ -171,6 +184,23 @@ module.exports = React.createClass({
}, "mx_IntegrationsManager"); }, "mx_IntegrationsManager");
}, },
onClickAddWidget: function(e) {
e.preventDefault();
// Display a warning dialog if the max number of widgets have already been added to the room
const apps = this._getApps();
if (apps && apps.length >= MAX_WIDGETS) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`;
console.error(errorMsg);
Modal.createDialog(ErrorDialog, {
title: _t("Cannot add any more widgets"),
description: _t("The maximum permitted number of widgets have already been added to this room."),
});
return;
}
this._launchManageIntegrations();
},
render: function() { render: function() {
const apps = this.state.apps.map( const apps = this.state.apps.map(
(app, index, arr) => { (app, index, arr) => {
@ -183,24 +213,34 @@ module.exports = React.createClass({
fullWidth={arr.length<2 ? true : false} fullWidth={arr.length<2 ? true : false}
room={this.props.room} room={this.props.room}
userId={this.props.userId} userId={this.props.userId}
show={this.props.showApps}
creatorUserId={app.creatorUserId}
/>); />);
}); });
const addWidget = this.state.apps && this.state.apps.length < 2 && this._canUserModify() && let addWidget;
(<div onClick={this.onClickAddWidget} if (this.props.showApps &&
this._canUserModify()
) {
addWidget = <div
onClick={this.onClickAddWidget}
role="button" role="button"
tabIndex="0" tabIndex="0"
className="mx_AddWidget_button" className={this.state.apps.length<2 ?
"mx_AddWidget_button mx_AddWidget_button_full_width" :
"mx_AddWidget_button"
}
title={_t('Add a widget')}> title={_t('Add a widget')}>
[+] { _t('Add a widget') } [+] { _t('Add a widget') }
</div>); </div>;
}
return ( return (
<div className="mx_AppsDrawer"> <div className="mx_AppsDrawer">
<div id="apps" className="mx_AppsContainer"> <div id="apps" className="mx_AppsContainer">
{ apps } { apps }
</div> </div>
{addWidget} { this._canUserModify() && addWidget }
</div> </div>
); );
}, },

View file

@ -143,7 +143,6 @@ export default class Autocomplete extends React.Component {
return null; return null;
} }
this.setSelection(selectionOffset); this.setSelection(selectionOffset);
return selectionOffset === COMPOSER_SELECTED ? null : this.state.completionList[selectionOffset - 1];
} }
// called from MessageComposerInput // called from MessageComposerInput
@ -155,7 +154,6 @@ export default class Autocomplete extends React.Component {
return null; return null;
} }
this.setSelection(selectionOffset); this.setSelection(selectionOffset);
return selectionOffset === COMPOSER_SELECTED ? null : this.state.completionList[selectionOffset - 1];
} }
onEscape(e): boolean { onEscape(e): boolean {
@ -201,6 +199,9 @@ export default class Autocomplete extends React.Component {
setSelection(selectionOffset: number) { setSelection(selectionOffset: number) {
this.setState({selectionOffset, hide: false}); this.setState({selectionOffset, hide: false});
if (this.props.onSelectionChange) {
this.props.onSelectionChange(this.state.completionList[selectionOffset - 1]);
}
} }
componentDidUpdate() { componentDidUpdate() {

View file

@ -129,11 +129,13 @@ module.exports = React.createClass({
); );
let appsDrawer = null; let appsDrawer = null;
if(UserSettingsStore.isFeatureEnabled('matrix_apps') && this.props.showApps) { if(UserSettingsStore.isFeatureEnabled('matrix_apps')) {
appsDrawer = <AppsDrawer ref="appsDrawer" appsDrawer = <AppsDrawer ref="appsDrawer"
room={this.props.room} room={this.props.room}
userId={this.props.userId} userId={this.props.userId}
maxHeight={this.props.maxHeight}/>; maxHeight={this.props.maxHeight}
showApps={this.props.showApps}
/>;
} }
return ( return (

View file

@ -44,6 +44,8 @@ var eventTileTypes = {
'm.room.history_visibility' : 'messages.TextualEvent', 'm.room.history_visibility' : 'messages.TextualEvent',
'm.room.encryption' : 'messages.TextualEvent', 'm.room.encryption' : 'messages.TextualEvent',
'm.room.power_levels' : 'messages.TextualEvent', 'm.room.power_levels' : 'messages.TextualEvent',
'im.vector.modular.widgets': 'messages.TextualEvent',
}; };
var MAX_READ_AVATARS = 5; var MAX_READ_AVATARS = 5;
@ -506,10 +508,10 @@ module.exports = withMatrixClient(React.createClass({
if (msgtype === 'm.image') aux = _t('sent an image'); if (msgtype === 'm.image') aux = _t('sent an image');
else if (msgtype === 'm.video') aux = _t('sent a video'); else if (msgtype === 'm.video') aux = _t('sent a video');
else if (msgtype === 'm.file') aux = _t('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} enableFlair={!aux} aux={aux} />;
} }
else { else {
sender = <SenderProfile mxEvent={this.props.mxEvent} />; sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
} }
} }

View file

@ -62,6 +62,7 @@ module.exports = withMatrixClient(React.createClass({
updating: 0, updating: 0,
devicesLoading: true, devicesLoading: true,
devices: null, devices: null,
isIgnoring: false,
}; };
}, },
@ -81,6 +82,8 @@ module.exports = withMatrixClient(React.createClass({
cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomState.events", this.onRoomStateEvents);
cli.on("RoomMember.name", this.onRoomMemberName); cli.on("RoomMember.name", this.onRoomMemberName);
cli.on("accountData", this.onAccountData); cli.on("accountData", this.onAccountData);
this._checkIgnoreState();
}, },
componentDidMount: function() { componentDidMount: function() {
@ -111,6 +114,11 @@ module.exports = withMatrixClient(React.createClass({
} }
}, },
_checkIgnoreState: function() {
const isIgnoring = this.props.matrixClient.isUserIgnored(this.props.member.userId);
this.setState({isIgnoring: isIgnoring});
},
_disambiguateDevices: function(devices) { _disambiguateDevices: function(devices) {
var names = Object.create(null); var names = Object.create(null);
for (var i = 0; i < devices.length; i++) { for (var i = 0; i < devices.length; i++) {
@ -225,6 +233,18 @@ module.exports = withMatrixClient(React.createClass({
}); });
}, },
onIgnoreToggle: function() {
const ignoredUsers = this.props.matrixClient.getIgnoredUsers();
if (this.state.isIgnoring) {
const index = ignoredUsers.indexOf(this.props.member.userId);
if (index !== -1) ignoredUsers.splice(index, 1);
} else {
ignoredUsers.push(this.props.member.userId);
}
this.props.matrixClient.setIgnoredUsers(ignoredUsers).then(() => this.setState({isIgnoring: !this.state.isIgnoring}));
},
onKick: function() { onKick: function() {
const membership = this.props.member.membership; const membership = this.props.member.membership;
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick"); const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
@ -607,6 +627,29 @@ module.exports = withMatrixClient(React.createClass({
); );
}, },
_renderUserOptions: function() {
// Only allow the user to ignore the user if its not ourselves
let ignoreButton = null;
if (this.props.member.userId !== this.props.matrixClient.getUserId()) {
ignoreButton = (
<AccessibleButton onClick={this.onIgnoreToggle} className="mx_MemberInfo_field">
{this.state.isIgnoring ? _t("Unignore") : _t("Ignore")}
</AccessibleButton>
);
}
if (!ignoreButton) return null;
return (
<div>
<h3>{ _t("User Options") }</h3>
<div className="mx_MemberInfo_buttons">
{ignoreButton}
</div>
</div>
);
},
render: function() { render: function() {
var startChat, kickButton, banButton, muteButton, giveModButton, spinner; var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
if (this.props.member.userId !== this.props.matrixClient.credentials.userId) { if (this.props.member.userId !== this.props.matrixClient.credentials.userId) {
@ -708,7 +751,7 @@ module.exports = withMatrixClient(React.createClass({
if (kickButton || banButton || muteButton || giveModButton) { if (kickButton || banButton || muteButton || giveModButton) {
adminTools = adminTools =
<div> <div>
<h3>{_t("Admin tools")}</h3> <h3>{_t("Admin Tools")}</h3>
<div className="mx_MemberInfo_buttons"> <div className="mx_MemberInfo_buttons">
{muteButton} {muteButton}
@ -756,6 +799,8 @@ module.exports = withMatrixClient(React.createClass({
</div> </div>
</div> </div>
{ this._renderUserOptions() }
{ adminTools } { adminTools }
{ startChat } { startChat }

View file

@ -1,6 +1,7 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector 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.
@ -14,42 +15,37 @@ 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.
*/ */
var React = require('react');
import React from 'react';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
var classNames = require('classnames');
var Matrix = require("matrix-js-sdk");
import Promise from 'bluebird';
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var Entities = require("../../../Entities");
var sdk = require('../../../index'); var sdk = require('../../../index');
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
var rate_limited_func = require('../../../ratelimitedfunc'); var rate_limited_func = require('../../../ratelimitedfunc');
var CallHandler = require("../../../CallHandler"); var CallHandler = require("../../../CallHandler");
var Invite = require("../../../Invite");
var INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
const SHOW_MORE_INCREMENT = 100;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MemberList', displayName: 'MemberList',
getInitialState: function() { getInitialState: function() {
var state = { this.memberDict = this.getMemberDict();
members: [], const members = this.roomMembers();
return {
members: members,
filteredJoinedMembers: this._filterMembers(members, 'join'),
filteredInvitedMembers: this._filterMembers(members, 'invite'),
// ideally we'd size this to the page height, but // ideally we'd size this to the page height, but
// in practice I find that a little constraining // in practice I find that a little constraining
truncateAt: INITIAL_LOAD_NUM_MEMBERS, truncateAtJoined: INITIAL_LOAD_NUM_MEMBERS,
truncateAtInvited: INITIAL_LOAD_NUM_INVITED,
searchQuery: "", searchQuery: "",
}; };
if (!this.props.roomId) return state;
var cli = MatrixClientPeg.get();
var room = cli.getRoom(this.props.roomId);
if (!room) return state;
this.memberDict = this.getMemberDict();
state.members = this.roomMembers();
return state;
}, },
componentWillMount: function() { componentWillMount: function() {
@ -147,10 +143,12 @@ module.exports = React.createClass({
// console.log("Updating memberlist"); // console.log("Updating memberlist");
this.memberDict = this.getMemberDict(); this.memberDict = this.getMemberDict();
var self = this; const newState = {
this.setState({ members: this.roomMembers(),
members: self.roomMembers() };
}); newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join');
newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite');
this.setState(newState);
}, 500), }, 500),
getMemberDict: function() { getMemberDict: function() {
@ -199,7 +197,15 @@ module.exports = React.createClass({
return to_display; return to_display;
}, },
_createOverflowTile: function(overflowCount, totalCount) { _createOverflowTileJoined: function(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList);
},
_createOverflowTileInvited: function(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList);
},
_createOverflowTile: function(overflowCount, totalCount, onClick) {
// 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.
const EntityTile = sdk.getComponent("rooms.EntityTile"); const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -208,13 +214,19 @@ module.exports = React.createClass({
<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} />
} name={text} presenceState="online" suppressOnHover={true} } name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullMemberList} /> onClick={onClick} />
); );
}, },
_showFullMemberList: function() { _showMoreJoinedMemberList: function() {
this.setState({ this.setState({
truncateAt: -1 truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT,
});
},
_showMoreInvitedMemberList: function() {
this.setState({
truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT,
}); });
}, },
@ -280,17 +292,17 @@ module.exports = React.createClass({
}, },
onSearchQueryChanged: function(ev) { onSearchQueryChanged: function(ev) {
this.setState({ searchQuery: ev.target.value }); const q = ev.target.value;
this.setState({
searchQuery: q,
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', q),
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', q),
});
}, },
makeMemberTiles: function(membership, query) { _filterMembers: function(members, membership, query) {
var MemberTile = sdk.getComponent("rooms.MemberTile"); return members.filter((userId) => {
query = (query || "").toLowerCase(); const m = this.memberDict[userId];
var self = this;
var memberList = self.state.members.filter(function(userId) {
var m = self.memberDict[userId];
if (query) { if (query) {
const matchesName = m.name.toLowerCase().indexOf(query) !== -1; const matchesName = m.name.toLowerCase().indexOf(query) !== -1;
@ -302,14 +314,23 @@ module.exports = React.createClass({
} }
return m.membership == membership; return m.membership == membership;
}).map(function(userId) { });
var m = self.memberDict[userId]; },
_makeMemberTiles: function(members, membership) {
const MemberTile = sdk.getComponent("rooms.MemberTile");
const memberList = members.map((userId) => {
const m = this.memberDict[userId];
return ( return (
<MemberTile key={userId} member={m} ref={userId} /> <MemberTile key={userId} member={m} ref={userId} />
); );
}); });
// XXX: surely this is not the right home for this logic. // XXX: surely this is not the right home for this logic.
// Double XXX: Now it's really, really not the right home for this logic:
// we shouldn't even be passing in the 'membership' param to this function.
// Ew, ew, and ew.
if (membership === "invite") { if (membership === "invite") {
// include 3pid invites (m.room.third_party_invite) state events. // include 3pid invites (m.room.third_party_invite) state events.
// The HS may have already converted these into m.room.member invites so // The HS may have already converted these into m.room.member invites so
@ -333,7 +354,7 @@ module.exports = React.createClass({
return; return;
} }
memberList.push( memberList.push(
<EntityTile key={e.getStateKey()} name={e.getContent().display_name} /> <EntityTile key={e.getStateKey()} name={e.getContent().display_name} suppressOnHover={true} />
); );
}); });
} }
@ -342,21 +363,42 @@ module.exports = React.createClass({
return memberList; return memberList;
}, },
_getChildrenJoined: function(start, end) {
return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
},
_getChildCountJoined: function() {
return this.state.filteredJoinedMembers.length;
},
_getChildrenInvited: function(start, end) {
return this._makeMemberTiles(this.state.filteredInvitedMembers.slice(start, end), 'invite');
},
_getChildCountInvited: function() {
return this.state.filteredInvitedMembers.length;
},
render: function() { render: function() {
var invitedSection = null; const TruncatedList = sdk.getComponent("elements.TruncatedList");
var invitedMemberTiles = this.makeMemberTiles('invite', this.state.searchQuery);
if (invitedMemberTiles.length > 0) { let invitedSection = null;
if (this._getChildCountInvited() > 0) {
invitedSection = ( invitedSection = (
<div className="mx_MemberList_invited"> <div className="mx_MemberList_invited">
<h2>{ _t("Invited") }</h2> <h2>{ _t("Invited") }</h2>
<div className="mx_MemberList_wrapper"> <div className="mx_MemberList_wrapper">
{invitedMemberTiles} <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAtInvited}
createOverflowElement={this._createOverflowTileInvited}
getChildren={this._getChildrenInvited}
getChildCount={this._getChildCountInvited}
/>
</div> </div>
</div> </div>
); );
} }
var inputBox = ( const inputBox = (
<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}
@ -364,15 +406,15 @@ module.exports = React.createClass({
</form> </form>
); );
var TruncatedList = sdk.getComponent("elements.TruncatedList");
return ( return (
<div className="mx_MemberList"> <div className="mx_MemberList">
{ inputBox } { inputBox }
<GeminiScrollbar autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper"> <GeminiScrollbar autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper">
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt} <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAtJoined}
createOverflowElement={this._createOverflowTile}> createOverflowElement={this._createOverflowTileJoined}
{this.makeMemberTiles('join', this.state.searchQuery)} getChildren={this._getChildrenJoined}
</TruncatedList> getChildCount={this._getChildCountJoined}
/>
{invitedSection} {invitedSection}
</GeminiScrollbar> </GeminiScrollbar>
</div> </div>

View file

@ -289,12 +289,12 @@ export default class MessageComposer extends React.Component {
if (this.props.showApps) { if (this.props.showApps) {
hideAppsButton = hideAppsButton =
<div key="controls_hide_apps" className="mx_MessageComposer_apps" onClick={this.onHideAppsClick} title={_t("Hide Apps")}> <div key="controls_hide_apps" className="mx_MessageComposer_apps" onClick={this.onHideAppsClick} title={_t("Hide Apps")}>
<TintableSvg src="img/icons-apps-active.svg" width="35" height="35"/> <TintableSvg src="img/icons-hide-apps.svg" width="35" height="35"/>
</div>; </div>;
} else { } else {
showAppsButton = showAppsButton =
<div key="show_apps" className="mx_MessageComposer_apps" onClick={this.onShowAppsClick} title={_t("Show Apps")}> <div key="show_apps" className="mx_MessageComposer_apps" onClick={this.onShowAppsClick} title={_t("Show Apps")}>
<TintableSvg src="img/icons-apps.svg" width="35" height="35"/> <TintableSvg src="img/icons-show-apps.svg" width="35" height="35"/>
</div>; </div>;
} }
} }

View file

@ -30,7 +30,7 @@ import SlashCommands from '../../../SlashCommands';
import KeyCode from '../../../KeyCode'; import KeyCode from '../../../KeyCode';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import sdk from '../../../index'; import sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import Analytics from '../../../Analytics'; import Analytics from '../../../Analytics';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
@ -949,8 +949,7 @@ export default class MessageComposerInput extends React.Component {
}; };
moveAutocompleteSelection = (up) => { moveAutocompleteSelection = (up) => {
const completion = up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow(); up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow();
return this.setDisplayedCompletion(completion);
}; };
onEscape = async (e) => { onEscape = async (e) => {
@ -1033,10 +1032,10 @@ export default class MessageComposerInput extends React.Component {
buttons. */ buttons. */
getSelectionInfo(editorState: EditorState) { getSelectionInfo(editorState: EditorState) {
const styleName = { const styleName = {
BOLD: 'bold', BOLD: _td('bold'),
ITALIC: 'italic', ITALIC: _td('italic'),
STRIKETHROUGH: 'strike', STRIKETHROUGH: _td('strike'),
UNDERLINE: 'underline', UNDERLINE: _td('underline'),
}; };
const originalStyle = editorState.getCurrentInlineStyle().toArray(); const originalStyle = editorState.getCurrentInlineStyle().toArray();
@ -1045,10 +1044,10 @@ export default class MessageComposerInput extends React.Component {
.filter((styleName) => !!styleName); .filter((styleName) => !!styleName);
const blockName = { const blockName = {
'code-block': 'code', 'code-block': _td('code'),
'blockquote': 'quote', 'blockquote': _td('quote'),
'unordered-list-item': 'bullet', 'unordered-list-item': _td('bullet'),
'ordered-list-item': 'numbullet', 'ordered-list-item': _td('numbullet'),
}; };
const originalBlockType = editorState.getCurrentContent() const originalBlockType = editorState.getCurrentContent()
.getBlockForKey(editorState.getSelection().getStartKey()) .getBlockForKey(editorState.getSelection().getStartKey())
@ -1133,6 +1132,7 @@ export default class MessageComposerInput extends React.Component {
<Autocomplete <Autocomplete
ref={(e) => this.autocomplete = e} ref={(e) => this.autocomplete = e}
onConfirm={this.setDisplayedCompletion} onConfirm={this.setDisplayedCompletion}
onSelectionChange={this.setDisplayedCompletion}
query={this.getAutocompleteQuery(content)} query={this.getAutocompleteQuery(content)}
selection={selection}/> selection={selection}/>
</div> </div>

View file

@ -70,7 +70,7 @@ module.exports = React.createClass({
if (presence === "online") return _t("Online"); if (presence === "online") return _t("Online");
if (presence === "unavailable") return _t("Idle"); // XXX: is this actually right? if (presence === "unavailable") return _t("Idle"); // XXX: is this actually right?
if (presence === "offline") return _t("Offline"); if (presence === "offline") return _t("Offline");
return "Unknown"; return _t("Unknown");
}, },
render: function() { render: function() {

View file

@ -123,7 +123,19 @@ module.exports = React.createClass({
} }
var newElement = ReactDOM.findDOMNode(this); var newElement = ReactDOM.findDOMNode(this);
var startTopOffset = oldTop - newElement.offsetParent.getBoundingClientRect().top; let startTopOffset;
if (!newElement.offsetParent) {
// this seems to happen sometimes for reasons I don't understand
// the docs for `offsetParent` say it may be null if `display` is
// `none`, but I can't see why that would happen.
console.warn(
`ReadReceiptMarker for ${this.props.member.userId} in ` +
`${this.props.member.roomId} has no offsetParent`,
);
startTopOffset = 0;
} else {
startTopOffset = oldTop - newElement.offsetParent.getBoundingClientRect().top;
}
var startStyles = []; var startStyles = [];
var enterTransitionOpts = []; var enterTransitionOpts = [];
@ -131,13 +143,12 @@ module.exports = React.createClass({
if (oldInfo && oldInfo.left) { if (oldInfo && oldInfo.left) {
// start at the old height and in the old h pos // start at the old height and in the old h pos
var leftOffset = oldInfo.left;
startStyles.push({ top: startTopOffset+"px", startStyles.push({ top: startTopOffset+"px",
left: oldInfo.left+"px" }); left: oldInfo.left+"px" });
var reorderTransitionOpts = { var reorderTransitionOpts = {
duration: 100, duration: 100,
easing: 'easeOut' easing: 'easeOut',
}; };
enterTransitionOpts.push(reorderTransitionOpts); enterTransitionOpts.push(reorderTransitionOpts);
@ -175,7 +186,7 @@ module.exports = React.createClass({
if (this.props.timestamp) { if (this.props.timestamp) {
title = _t( title = _t(
"Seen by %(userName)s at %(dateTime)s", "Seen by %(userName)s at %(dateTime)s",
{userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)} {userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)},
); );
} }

View file

@ -29,6 +29,7 @@ import * as linkify from 'linkifyjs';
import linkifyElement from 'linkifyjs/element'; import linkifyElement from 'linkifyjs/element';
import linkifyMatrix from '../../../linkify-matrix'; import linkifyMatrix from '../../../linkify-matrix';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader'; import {CancelButton} from './SimpleRoomHeader';
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -47,6 +48,7 @@ module.exports = React.createClass({
onSaveClick: React.PropTypes.func, onSaveClick: React.PropTypes.func,
onSearchClick: React.PropTypes.func, onSearchClick: React.PropTypes.func,
onLeaveClick: React.PropTypes.func, onLeaveClick: React.PropTypes.func,
onCancelClick: React.PropTypes.func,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -54,6 +56,7 @@ module.exports = React.createClass({
editing: false, editing: false,
inRoom: false, inRoom: false,
onSaveClick: function() {}, onSaveClick: function() {},
onCancelClick: null,
}; };
}, },
@ -320,10 +323,18 @@ module.exports = React.createClass({
} }
let rightRow; let rightRow;
let manageIntegsButton;
if(this.props.room && this.props.room.roomId && this.props.inRoom) {
manageIntegsButton = <ManageIntegsButton
roomId={this.props.room.roomId}
/>;
}
if (!this.props.editing) { if (!this.props.editing) {
rightRow = rightRow =
<div className="mx_RoomHeader_rightRow"> <div className="mx_RoomHeader_rightRow">
{ settingsButton } { settingsButton }
{ manageIntegsButton }
{ forgetButton } { forgetButton }
{ searchButton } { searchButton }
{ rightPanelButtons } { rightPanelButtons }

View file

@ -63,7 +63,6 @@ module.exports = React.createClass({
propTypes: { propTypes: {
ConferenceHandler: React.PropTypes.any, ConferenceHandler: React.PropTypes.any,
collapsed: React.PropTypes.bool.isRequired, collapsed: React.PropTypes.bool.isRequired,
currentRoom: React.PropTypes.string,
searchFilter: React.PropTypes.string, searchFilter: React.PropTypes.string,
}, },
@ -88,7 +87,9 @@ module.exports = React.createClass({
cli.on("Room.receipt", this.onRoomReceipt); cli.on("Room.receipt", this.onRoomReceipt);
cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomState.events", this.onRoomStateEvents);
cli.on("RoomMember.name", this.onRoomMemberName); cli.on("RoomMember.name", this.onRoomMemberName);
cli.on("Event.decrypted", this.onEventDecrypted);
cli.on("accountData", this.onAccountData); cli.on("accountData", this.onAccountData);
cli.on("Group.myMembership", this._onGroupMyMembership);
this.refreshRoomList(); this.refreshRoomList();
@ -155,7 +156,9 @@ module.exports = React.createClass({
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt); MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName); MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
MatrixClientPeg.get().removeListener("Event.decrypted", this.onEventDecrypted);
MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
} }
// cancel any pending calls to the rate_limited_funcs // cancel any pending calls to the rate_limited_funcs
this._delayedRefreshRoomList.cancelPendingCall(); this._delayedRefreshRoomList.cancelPendingCall();
@ -224,12 +227,21 @@ module.exports = React.createClass({
this._delayedRefreshRoomList(); this._delayedRefreshRoomList();
}, },
onEventDecrypted: function(ev) {
// An event being decrypted may mean we need to re-order the room list
this._delayedRefreshRoomList();
},
onAccountData: function(ev) { onAccountData: function(ev) {
if (ev.getType() == 'm.direct') { if (ev.getType() == 'm.direct') {
this._delayedRefreshRoomList(); this._delayedRefreshRoomList();
} }
}, },
_onGroupMyMembership: function(group) {
this.forceUpdate();
},
_delayedRefreshRoomList: new rate_limited_func(function() { _delayedRefreshRoomList: new rate_limited_func(function() {
this.refreshRoomList(); this.refreshRoomList();
}, 500), }, 500),
@ -544,8 +556,24 @@ module.exports = React.createClass({
} }
}, },
_makeGroupInviteTiles() {
const ret = [];
const GroupInviteTile = sdk.getComponent('groups.GroupInviteTile');
for (const group of MatrixClientPeg.get().getGroups()) {
if (group.myMembership !== 'invite') continue;
ret.push(<GroupInviteTile key={group.groupId} group={group} />);
}
return ret;
},
render: function() { render: function() {
var RoomSubList = sdk.getComponent('structures.RoomSubList'); const RoomSubList = sdk.getComponent('structures.RoomSubList');
const inviteSectionExtraTiles = this._makeGroupInviteTiles();
var self = this; var self = this;
return ( return (
<GeminiScrollbar className="mx_RoomList_scrollbar" <GeminiScrollbar className="mx_RoomList_scrollbar"
@ -555,12 +583,15 @@ module.exports = React.createClass({
label={ _t('Invites') } label={ _t('Invites') }
editable={ false } editable={ false }
order="recent" order="recent"
isInvite={true}
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } collapsed={ self.props.collapsed }
searchFilter={ self.props.searchFilter } searchFilter={ self.props.searchFilter }
onHeaderClick={ self.onSubListHeaderClick } onHeaderClick={ self.onSubListHeaderClick }
onShowMoreRooms={ self.onShowMoreRooms } /> onShowMoreRooms={ self.onShowMoreRooms }
extraTiles={ inviteSectionExtraTiles }
/>
<RoomSubList list={ self.state.lists['m.favourite'] } <RoomSubList list={ self.state.lists['m.favourite'] }
label={ _t('Favourites') } label={ _t('Favourites') }

View file

@ -24,8 +24,6 @@ import sdk from '../../../index';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import ObjectUtils from '../../../ObjectUtils'; import ObjectUtils from '../../../ObjectUtils';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import ScalarAuthClient from '../../../ScalarAuthClient';
import ScalarMessaging from '../../../ScalarMessaging';
import UserSettingsStore from '../../../UserSettingsStore'; import UserSettingsStore from '../../../UserSettingsStore';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
@ -92,7 +90,6 @@ module.exports = React.createClass({
propTypes: { propTypes: {
room: React.PropTypes.object.isRequired, room: React.PropTypes.object.isRequired,
onSaveClick: React.PropTypes.func, onSaveClick: React.PropTypes.func,
onCancelClick: React.PropTypes.func,
}, },
getInitialState: function() { getInitialState: function() {
@ -118,14 +115,10 @@ module.exports = React.createClass({
// Default to false if it's undefined, otherwise react complains about changing // Default to false if it's undefined, otherwise react complains about changing
// components from uncontrolled to controlled // components from uncontrolled to controlled
isRoomPublished: this._originalIsRoomPublished || false, isRoomPublished: this._originalIsRoomPublished || false,
scalar_error: null,
showIntegrationsError: false,
}; };
}, },
componentWillMount: function() { componentWillMount: function() {
ScalarMessaging.startListening();
MatrixClientPeg.get().on("RoomMember.membership", this._onRoomMemberMembership); MatrixClientPeg.get().on("RoomMember.membership", this._onRoomMemberMembership);
MatrixClientPeg.get().getRoomDirectoryVisibility( MatrixClientPeg.get().getRoomDirectoryVisibility(
@ -137,18 +130,6 @@ module.exports = React.createClass({
console.error("Failed to get room visibility: " + err); console.error("Failed to get room visibility: " + err);
}); });
this.scalarClient = null;
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => {
this.forceUpdate();
}, (err) => {
this.setState({
scalar_error: err
});
});
}
dis.dispatch({ dis.dispatch({
action: 'ui_opacity', action: 'ui_opacity',
sideOpacity: 0.3, sideOpacity: 0.3,
@ -157,8 +138,6 @@ module.exports = React.createClass({
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
ScalarMessaging.stopListening();
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli) { if (cli) {
cli.removeListener("RoomMember.membership", this._onRoomMemberMembership); cli.removeListener("RoomMember.membership", this._onRoomMemberMembership);
@ -308,6 +287,9 @@ module.exports = React.createClass({
promises.push(ps); promises.push(ps);
} }
// related groups
promises.push(this.saveRelatedGroups());
// encryption // encryption
p = this.saveEnableEncryption(); p = this.saveEnableEncryption();
if (!p.isFulfilled()) { if (!p.isFulfilled()) {
@ -325,6 +307,11 @@ module.exports = React.createClass({
return this.refs.alias_settings.saveSettings(); return this.refs.alias_settings.saveSettings();
}, },
saveRelatedGroups: function() {
if (!this.refs.related_groups) { return Promise.resolve(); }
return this.refs.related_groups.saveSettings();
},
saveColor: function() { saveColor: function() {
if (!this.refs.color_settings) { return Promise.resolve(); } if (!this.refs.color_settings) { return Promise.resolve(); }
return this.refs.color_settings.saveSettings(); return this.refs.color_settings.saveSettings();
@ -514,28 +501,6 @@ module.exports = React.createClass({
roomState.mayClientSendStateEvent("m.room.guest_access", cli)); roomState.mayClientSendStateEvent("m.room.guest_access", cli));
}, },
onManageIntegrations(ev) {
ev.preventDefault();
var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
Modal.createTrackedDialog('Integrations Manager', 'onManageIntegrations', IntegrationsManager, {
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) :
null,
onFinished: ()=>{
if (this._calcSavePromises().length === 0) {
this.props.onCancelClick(ev);
}
},
}, "mx_IntegrationsManager");
},
onShowIntegrationsError(ev) {
ev.preventDefault();
this.setState({
showIntegrationsError: !this.state.showIntegrationsError,
});
},
onLeaveClick() { onLeaveClick() {
dis.dispatch({ dis.dispatch({
action: 'leave_room', action: 'leave_room',
@ -634,6 +599,7 @@ module.exports = React.createClass({
var AliasSettings = sdk.getComponent("room_settings.AliasSettings"); var AliasSettings = sdk.getComponent("room_settings.AliasSettings");
var ColorSettings = sdk.getComponent("room_settings.ColorSettings"); var ColorSettings = sdk.getComponent("room_settings.ColorSettings");
var UrlPreviewSettings = sdk.getComponent("room_settings.UrlPreviewSettings"); var UrlPreviewSettings = sdk.getComponent("room_settings.UrlPreviewSettings");
var RelatedGroupSettings = sdk.getComponent("room_settings.RelatedGroupSettings");
var EditableText = sdk.getComponent('elements.EditableText'); var EditableText = sdk.getComponent('elements.EditableText');
var PowerSelector = sdk.getComponent('elements.PowerSelector'); var PowerSelector = sdk.getComponent('elements.PowerSelector');
var Loader = sdk.getComponent("elements.Spinner"); var Loader = sdk.getComponent("elements.Spinner");
@ -666,6 +632,14 @@ module.exports = React.createClass({
var self = this; var self = this;
let relatedGroupsSection;
if (UserSettingsStore.isFeatureEnabled('feature_groups')) {
relatedGroupsSection = <RelatedGroupSettings ref="related_groups"
roomId={this.props.room.roomId}
canSetRelatedGroups={roomState.mayClientSendStateEvent("m.room.related_groups", cli)}
relatedGroupsEvent={this.props.room.currentState.getStateEvents('m.room.related_groups', '')} />;
}
var userLevelsSection; var userLevelsSection;
if (Object.keys(user_levels).length) { if (Object.keys(user_levels).length) {
userLevelsSection = userLevelsSection =
@ -797,46 +771,10 @@ module.exports = React.createClass({
</div>; </div>;
} }
let integrationsButton;
let integrationsError;
if (this.scalarClient !== null) {
if (this.state.showIntegrationsError && this.state.scalar_error) {
console.error(this.state.scalar_error);
integrationsError = (
<span className="mx_RoomSettings_integrationsButton_errorPopup">
{ _t('Could not connect to the integration server') }
</span>
);
}
if (this.scalarClient.hasCredentials()) {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" onClick={ this.onManageIntegrations }>
{ _t('Manage Integrations') }
</div>
);
} else if (this.state.scalar_error) {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton_error" onClick={ this.onShowIntegrationsError }>
Integrations Error <img src="img/warning.svg" width="17"/>
{ integrationsError }
</div>
);
} else {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" style={{opacity: 0.5}}>
{ _t('Manage Integrations') }
</div>
);
}
}
return ( return (
<div className="mx_RoomSettings"> <div className="mx_RoomSettings">
{ leaveButton } { leaveButton }
{ integrationsButton }
{ tagsSection } { tagsSection }
@ -872,7 +810,7 @@ module.exports = React.createClass({
<input type="checkbox" disabled={ !roomState.mayClientSendStateEvent("m.room.aliases", cli) } <input type="checkbox" disabled={ !roomState.mayClientSendStateEvent("m.room.aliases", cli) }
onChange={ this._onToggle.bind(this, "isRoomPublished", true, false)} onChange={ this._onToggle.bind(this, "isRoomPublished", true, false)}
checked={this.state.isRoomPublished}/> checked={this.state.isRoomPublished}/>
{_t("List this room in %(domain)s's room directory?", { domain: MatrixClientPeg.get().getDomain() })} {_t("Publish this room to the public in %(domain)s's room directory?", { domain: MatrixClientPeg.get().getDomain() })}
</label> </label>
</div> </div>
<div className="mx_RoomSettings_settings"> <div className="mx_RoomSettings_settings">
@ -926,6 +864,8 @@ module.exports = React.createClass({
canonicalAliasEvent={this.props.room.currentState.getStateEvents('m.room.canonical_alias', '')} canonicalAliasEvent={this.props.room.currentState.getStateEvents('m.room.canonical_alias', '')}
aliasEvents={this.props.room.currentState.getStateEvents('m.room.aliases')} /> aliasEvents={this.props.room.currentState.getStateEvents('m.room.aliases')} />
{ relatedGroupsSection }
<UrlPreviewSettings ref="url_preview_settings" room={this.props.room} /> <UrlPreviewSettings ref="url_preview_settings" room={this.props.room} />
<h3>{ _t('Permissions') }</h3> <h3>{ _t('Permissions') }</h3>

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector 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.
@ -27,6 +28,8 @@ var RoomNotifs = require('../../../RoomNotifs');
var FormattingUtils = require('../../../utils/FormattingUtils'); var FormattingUtils = require('../../../utils/FormattingUtils');
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
var UserSettingsStore = require('../../../UserSettingsStore'); var UserSettingsStore = require('../../../UserSettingsStore');
import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomTile', displayName: 'RoomTile',
@ -39,7 +42,6 @@ module.exports = React.createClass({
room: React.PropTypes.object.isRequired, room: React.PropTypes.object.isRequired,
collapsed: React.PropTypes.bool.isRequired, collapsed: React.PropTypes.bool.isRequired,
selected: React.PropTypes.bool.isRequired,
unread: React.PropTypes.bool.isRequired, unread: React.PropTypes.bool.isRequired,
highlight: React.PropTypes.bool.isRequired, highlight: React.PropTypes.bool.isRequired,
isInvite: React.PropTypes.bool.isRequired, isInvite: React.PropTypes.bool.isRequired,
@ -58,6 +60,7 @@ module.exports = React.createClass({
badgeHover : false, badgeHover : false,
menuDisplayed: false, menuDisplayed: false,
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
selected: this.props.room.roomId === RoomViewStore.getRoomId(),
}); });
}, },
@ -87,8 +90,15 @@ module.exports = React.createClass({
} }
}, },
_onActiveRoomChange: function() {
this.setState({
selected: this.props.room.roomId === RoomViewStore.getRoomId(),
});
},
componentWillMount: function() { componentWillMount: function() {
MatrixClientPeg.get().on("accountData", this.onAccountData); MatrixClientPeg.get().on("accountData", this.onAccountData);
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
@ -96,6 +106,7 @@ module.exports = React.createClass({
if (cli) { if (cli) {
MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
} }
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
}, },
onClick: function(ev) { onClick: function(ev) {
@ -174,7 +185,7 @@ module.exports = React.createClass({
var classes = classNames({ var classes = classNames({
'mx_RoomTile': true, 'mx_RoomTile': true,
'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_selected': this.state.selected,
'mx_RoomTile_unread': this.props.unread, 'mx_RoomTile_unread': this.props.unread,
'mx_RoomTile_unreadNotify': notifBadges, 'mx_RoomTile_unreadNotify': notifBadges,
'mx_RoomTile_highlight': mentionBadges, 'mx_RoomTile_highlight': mentionBadges,
@ -221,7 +232,7 @@ module.exports = React.createClass({
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed, 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
}); });
if (this.props.selected) { if (this.state.selected) {
let nameSelected = <EmojiText>{name}</EmojiText>; let nameSelected = <EmojiText>{name}</EmojiText>;
label = <div title={ name } className={ nameClasses } dir="auto">{ nameSelected }</div>; label = <div title={ name } className={ nameClasses } dir="auto">{ nameSelected }</div>;

View file

@ -208,7 +208,7 @@ module.exports = React.createClass({
if (!this.state.cachedPassword) { if (!this.state.cachedPassword) {
currentPassword = <div className={rowClassName}> currentPassword = <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" />

View file

@ -71,7 +71,7 @@ export default class DevicesPanelEntry extends React.Component {
// pop up an interactive auth dialog // pop up an interactive auth dialog
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
Modal.createTrackedDialog('Delete Device Dialog', InteractiveAuthDialog, { Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
title: _t("Authentication"), title: _t("Authentication"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: error.data, authData: error.data,

View file

@ -0,0 +1,97 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import RoomViewStore from '../../../stores/RoomViewStore';
import CallHandler from '../../../CallHandler';
import dis from '../../../dispatcher';
import sdk from '../../../index';
module.exports = React.createClass({
displayName: 'CallPreview',
propTypes: {
// A Conference Handler implementation
// Must have a function signature:
// getConferenceCallForRoom(roomId: string): MatrixCall
ConferenceHandler: React.PropTypes.object,
},
getInitialState: function() {
return {
roomId: RoomViewStore.getRoomId(),
activeCall: CallHandler.getAnyActiveCall(),
};
},
componentWillMount: function() {
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this.dispatcherRef = dis.register(this._onAction);
},
componentWillUnmount: function() {
if (this._roomStoreToken) {
this._roomStoreToken.remove();
}
dis.unregister(this.dispatcherRef);
},
_onRoomViewStoreUpdate: function(payload) {
if (RoomViewStore.getRoomId() === this.state.roomId) return;
this.setState({
roomId: RoomViewStore.getRoomId(),
});
},
_onAction: function(payload) {
switch (payload.action) {
// listen for call state changes to prod the render method, which
// may hide the global CallView if the call it is tracking is dead
case 'call_state':
this.setState({
activeCall: CallHandler.getAnyActiveCall(),
});
break;
}
},
_onCallViewClick: function() {
const call = CallHandler.getAnyActiveCall();
if (call) {
dis.dispatch({
action: 'view_room',
room_id: call.groupRoomId || call.roomId,
});
}
},
render: function() {
const callForRoom = CallHandler.getCallForRoom(this.state.roomId);
const showCall = (this.state.activeCall && this.state.activeCall.call_state === 'connected' && !callForRoom);
if (showCall) {
const CallView = sdk.getComponent('voip.CallView');
return (
<CallView
className="mx_LeftPanel_callView" showVoice={true} onClick={this._onCallViewClick}
ConferenceHandler={this.props.ConferenceHandler}
/>
);
}
return null;
},
});

View file

@ -79,12 +79,6 @@ function createRoom(opts) {
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
let roomId; let roomId;
if (opts.andView) {
// We will possibly have a successful join, indicate as such
dis.dispatch({
action: 'will_join',
});
}
return client.createRoom(createOpts).finally(function() { return client.createRoom(createOpts).finally(function() {
modal.close(); modal.close();
}).then(function(res) { }).then(function(res) {
@ -104,8 +98,10 @@ function createRoom(opts) {
action: 'view_room', action: 'view_room',
room_id: roomId, room_id: roomId,
should_peek: false, should_peek: false,
// Creating a room will have joined us to the room // Creating a room will have joined us to the room,
joined: true, // so we are expecting the room to come down the sync
// stream, if it hasn't already.
joining: true,
}); });
} }
return roomId; return roomId;

47
src/groups.js Normal file
View file

@ -0,0 +1,47 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import PropTypes from 'prop-types';
export const GroupMemberType = PropTypes.shape({
userId: PropTypes.string.isRequired,
displayname: PropTypes.string,
avatarUrl: PropTypes.string,
});
export const GroupRoomType = PropTypes.shape({
name: PropTypes.string,
roomId: PropTypes.string.isRequired,
canonicalAlias: PropTypes.string,
avatarUrl: PropTypes.string,
});
export function groupMemberFromApiObject(apiObject) {
return {
userId: apiObject.user_id,
displayname: apiObject.displayname,
avatarUrl: apiObject.avatar_url,
};
}
export function groupRoomFromApiObject(apiObject) {
return {
name: apiObject.name,
roomId: apiObject.room_id,
canonicalAlias: apiObject.canonical_alias,
avatarUrl: apiObject.avatar_url,
};
}

View file

@ -1,5 +1,4 @@
{ {
"ar-iq": "العربية",
"Continue": "استمر", "Continue": "استمر",
"Username available": "اسم المستخدم متاح", "Username available": "اسم المستخدم متاح",
"Username not available": "الإسم المستخدم غير موجود", "Username not available": "الإسم المستخدم غير موجود",
@ -8,6 +7,5 @@
"Close": "إغلاق", "Close": "إغلاق",
"Create new room": "إنشاء غرفة جديدة", "Create new room": "إنشاء غرفة جديدة",
"Custom Server Options": "إعدادات السيرفر خاصة", "Custom Server Options": "إعدادات السيرفر خاصة",
"Direct Chat": "دردشة مباشرة",
"Dismiss": "صرف النظر" "Dismiss": "صرف النظر"
} }

65
src/i18n/strings/ca.json Normal file
View file

@ -0,0 +1,65 @@
{
"People": "Gent",
"Add a widget": "Afegeix un giny",
"af": "Afrikaans",
"ar-ae": "Àrab (Emirats Àrabs Units)",
"ar-bh": "Àrab (Bahrain)",
"ar-dz": "Àrab (Algèria)",
"ar-eg": "Àrab (Egipte)",
"ar-iq": "Àrab (Iraq)",
"ar-jo": "Àrab (Jordània)",
"ar-kw": "Àrab (Kuwait)",
"ar-lb": "Àrab (Líban)",
"ar-ly": "Àrab (Líbia)",
"ar-ma": "Àrab (Marroc)",
"ar-om": "Àrab (Oman)",
"ar-qa": "Àrab (Qatar)",
"ar-sa": "Àrab (Aràbia Saudita)",
"ca": "Català",
"cs": "Txec",
"de-at": "Alemany (Àustria)",
"de-ch": "Alemany (Suïssa)",
"de": "Alemany",
"de-li": "Alemany (Liechtenstein)",
"el": "Grec",
"de-lu": "Alemany (Luxemburg)",
"en-au": "Anglès (Austràlia)",
"Account": "Compte",
"VoIP": "Veu IP",
"No Microphones detected": "No s'ha detectat cap micròfon",
"No Webcams detected": "No s'ha detectat cap càmera web",
"Microphone": "Micròfon",
"Camera": "Càmera",
"Advanced": "Avançat",
"Algorithm": "Algoritme",
"Hide removed messages": "Amaga els missatges esborrats",
"Always show message timestamps": "Mostra sempre la marca de temps del missatge",
"Alias (optional)": "Àlies (opcional)",
"and": "i",
"An email has been sent to": "S'ha enviat un correu electrònic a",
"Cancel": "Cancel·la",
"Close": "Tanca",
"Create new room": "Crea una nova sala",
"Error": "Error",
"Failed to forget room %(errCode)s": "No s'ha pogut oblidar la sala %(errCode)s",
"Favourite": "Favorit",
"Mute": "Silenciat",
"Room directory": "Directori de sales",
"Settings": "Paràmetres",
"Start chat": "Inicia un xat",
"Failed to change password. Is your password correct?": "Hi ha hagut un error al canviar la vostra contrasenya. És correcte la vostra contrasenya?",
"Continue": "Continua",
"Custom Server Options": "Opcions de servidor personalitzat",
"Dismiss": "Omet",
"Notifications": "Notificacions",
"Remove": "Elimina",
"unknown error code": "codi d'error desconegut",
"Sunday": "Diumenge",
"Monday": "Dilluns",
"Tuesday": "Dimarts",
"Wednesday": "Dimecres",
"Thursday": "Dijous",
"Friday": "Divendres",
"Saturday": "Dissabte",
"OK": "D'acord"
}

218
src/i18n/strings/cs.json Normal file
View file

@ -0,0 +1,218 @@
{
"Close": "Zavřít",
"Favourites": "Oblíbené",
"Filter room members": "Filtrovat členy místnosti",
"Historical": "Historické",
"Home": "Úvod",
"%(displayName)s is typing": "%(displayName)s právě píše",
"Jump to first unread message.": "Přeskočit na první nepřečtenou zprávu.",
"Logout": "Odhlásit se",
"Low priority": "Nízká priorita",
"Notifications": "Upozornění",
"People": "Lidé",
"Rooms": "Místnosti",
"Scroll to unread messages": "Přejít k nepřečteným zprávám",
"Search": "Hledání",
"Send a message (unencrypted)": "Poslat zprávu (nezašifrovaně)",
"Settings": "Nastavení",
"Start Chat": "Začít chat",
"This room": "Tato místnost",
"Unencrypted room": "Nezašifrovaná místnost",
"Failed to upload file": "Nahrání souboru se nezdařilo",
"Video call": "Videohovor",
"Voice call": "Telefonát",
"Sun": "Ne",
"Mon": "Po",
"Tue": "Út",
"Wed": "St",
"Thu": "Čt",
"Fri": "Pá",
"Sat": "So",
"Jan": "Led",
"Feb": "Úno",
"Mar": "Bře",
"Apr": "Dub",
"May": "Kvě",
"Jun": "Čvn",
"Jul": "Čvc",
"Aug": "Srp",
"Sep": "Zář",
"Oct": "Říj",
"Nov": "Lis",
"Dec": "Pro",
"There are no visible files in this room": "V této místnosti nejsou žádné viditelné soubory",
"Create new room": "Založit novou místnost",
"Room directory": "Adresář místností",
"Start chat": "Začít chat",
"Options": "Možnosti",
"Register": "Zaregistrovat",
"Cancel": "Storno",
"Error": "Chyba",
"Favourite": "V oblíbených",
"Mute": "Ztišit",
"Continue": "Pokračovat",
"Failed to change password. Is your password correct?": "Nepodařilo se změnit heslo. Je vaše heslo správné?",
"Operation failed": "Chyba operace",
"Remove": "Odebrat",
"unknown error code": "neznámý kód chyby",
"Sunday": "Neděle",
"Monday": "Pondělí",
"Tuesday": "Úterý",
"Wednesday": "Středa",
"Thursday": "Čtvrtek",
"Friday": "Pátek",
"Saturday": "Sobota",
"OK": "OK",
"Failed to forget room %(errCode)s": "Nepodařilo se zapomenout místnost %(errCode)s",
"Dismiss": "Zahodit",
"powered by Matrix": "poháněno Matrixem",
"Custom Server Options": "Vlastní serverové volby",
"to favourite": "oblíbíte",
"to demote": "upozadíte",
"Drop here %(toAction)s": "Přetažením sem %(toAction)s",
"Add a widget": "Přidat widget",
"a room": "místnost",
"Accept": "Přijmout",
"%(targetName)s accepted an invitation.": "%(targetName)s přijal/a pozvání.",
"Account": "Účet",
"Access Token:": "Přístupový žeton:",
"Add": "Přidat",
"Add a topic": "Přidat téma",
"Add email address": "Přidat e-mailovou adresu",
"Add phone number": "Přidat telefonní číslo",
"Admin": "Správce",
"Admin tools": "Nástroje pro správu",
"Allow": "Povolit",
"VoIP": "VoIP",
"No Microphones detected": "Nerozpoznány žádné mikrofony",
"No Webcams detected": "Nerozpoznány žádné webkamery",
"Default Device": "Výchozí zařízení",
"Microphone": "Mikrofon",
"Camera": "Kamera",
"Advanced": "Pokročilé",
"Algorithm": "Algoritmus",
"Hide removed messages": "Skrýt odstraněné zprávy",
"Always show message timestamps": "Vždy zobrazovat časové značky zpráv",
"Authentication": "Ověření",
"and": "a",
"A new password must be entered.": "Musíte zadat nové heslo.",
"An error has occurred.": "Nastala chyba.",
"Anyone": "Kdokoliv",
"Are you sure?": "Určitě?",
"Are you sure you want to leave the room '%(roomName)s'?": "Určitě chcete odejít z místnosti '%(roomName)s'?",
"Are you sure you want to reject the invitation?": "Určitě chcete odmítnout pozvání?",
"Are you sure you want to upload the following files?": "Určitě chcete nahrát následující soubory?",
"Attachment": "Příloha",
"Autoplay GIFs and videos": "Automaticky přehrávat GIFy a videa",
"Bug Report": "Hlášení o chybě",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Nelze se připojit k domovskému serveru zkontrolujte prosím své připojení, prověřte, zdali je <a>SSL certifikát</a> vašeho domovského serveru důvěryhodný, a že některé z rozšíření prohlížeče neblokuje komunikaci.",
"Anyone who knows the room's link, apart from guests": "Kdokoliv, kdo má odkaz na místnost, kromě hostů",
"Anyone who knows the room's link, including guests": "Kdokoliv, kdo má odkaz na místnost, a to i hosté",
"Banned users": "Vykázaní uživatelé",
"Ban": "Vykázat",
"Bans user with given id": "Vykáže uživatele s daným id",
"Bulk Options": "Hromadné volby",
"Can't load user settings": "Nelze načíst uživatelské nastavení",
"Cannot add any more widgets": "Nelze přidat žádné další widgety",
"Change Password": "Změnit heslo",
"%(senderName)s changed their profile picture.": "%(senderName)s změnil/a svůj profilový obrázek.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s změnil/a název místnosti na %(roomName)s.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s odstranil/a název místnosti.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s změnil/a téma na \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Změny viditelnosti historie budou platné až pro budoucí zprávy v této místnosti",
"Changes your display nickname": "Změní vaši zobrazovanou přezdívku",
"Changes colour scheme of current room": "Změní barevné schéma aktuální místnosti",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "V současnosti změna hesla resetuje všechny šifrovací klíče na všech zařízeních, což vám znepřístupní historii zašifrovaných chatů, pokud si nejprve nevyexportujete klíče svých místností a pak je do nich znova nevložíte. Toto bude v budoucnu lépe ošetřeno.",
"Clear Cache and Reload": "Vymazat vyrovnávací paměť a načíst znovu",
"Clear Cache": "Vymazat vyrovnávací paměť",
"<a>Click here</a> to join the discussion!": "<a>Kliknutím zde</a> se přidáte k diskuzi!",
"Command error": "Chyba příkazu",
"Commands": "Příkazy",
"Conference call failed.": "Konferenční hovor selhal.",
"Conference calling is in development and may not be reliable.": "Konferenční hovory jsou stále ve vývoji a nemusí být spolehlivé.",
"Conference calls are not supported in encrypted rooms": "V šifrovaných místnostech nejsou konferenční hovory podporovány",
"Conference calls are not supported in this client": "V tomto klientovi nejsou konferenční hovory podporovány",
"Confirm password": "Potvrďte heslo",
"Confirm your new password": "Potvrďte své nové heslo",
"Could not connect to the integration server": "Nepodařilo se spojit se začleňovacím serverem",
"%(count)s new messages|one": "%(count)s nová zpráva",
"%(count)s new messages|other": "%(count)s nových zpráv",
"Create an account": "Vytvořit účet",
"Create Room": "Vytvořit místnost",
"Cryptography": "Kryptografie",
"Current password": "Současné heslo",
"Custom": "Vlastní",
"Custom level": "Vlastní úroveň",
"/ddg is not a command": "/ddg není příkazem",
"Deactivate Account": "Deaktivovat účet",
"Deactivate my account": "Deaktivovat můj účet",
"Decline": "Odmítnout",
"Decrypt %(text)s": "Dešifrovat %(text)s",
"Decryption error": "Chyba dešifrování",
"Delete": "Vymazat",
"Delete widget": "Vymazat widget",
"Default": "Výchozí",
"Device already verified!": "Zařízení již bylo ověřeno!",
"Device ID": "ID zařízení",
"Device ID:": "ID zařízení:",
"device id: ": "id zařízení: ",
"Device key:": "Klíč zařízení:",
"Devices": "Zařízení",
"Direct chats": "Přímé chaty",
"Disable Notifications": "Vypnout upozornění",
"disabled": "vypnuto",
"Disinvite": "Odvolat pozvání",
"Display name": "Zobrazované jméno",
"Don't send typing notifications": "Neupozorňovat ostatní, že píšu",
"Download %(text)s": "Stáhnout %(text)s",
"Drop File Here": "Přetáhněte soubor sem",
"Edit": "Upravit",
"Email": "E-mail",
"Email address": "E-mailová adresa",
"Email address (optional)": "E-mailová adresa (nepovinná)",
"Email, name or matrix ID": "E-mail, jméno nebo matrix ID",
"Emoji": "Emodži",
"Enable automatic language detection for syntax highlighting": "Zapnout kvůli zvýrazňování syntaxe automatické rozpoznávání jazyka",
"Enable encryption": "Zapnout šifrování",
"Enable Notifications": "Zapnout upozornění",
"enabled": "zapnuto",
"Encrypted by a verified device": "Zašifrováno ověřeným zařízením",
"Encrypted by an unverified device": "Zašifrováno neověřeným zařízením",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Zašifrované zprávy nepůjde vidět v klientech, kteří šifrování ještě nezavedli",
"Encrypted room": "Zašifrovaná místnost",
"Encryption is enabled in this room": "V této místnosti je zapnuto šifrování",
"Encryption is not enabled in this room": "V této místnosti není zapnuto šifrování",
"%(senderName)s ended the call.": "%(senderName)s ukončil/a hovor.",
"End-to-end encryption information": "Informace o end-to-end šifrování",
"End-to-end encryption is in beta and may not be reliable": "End-to-end šifrování je v raném vývoji a nemusí být spolehlivé",
"Enter Code": "Zadejte kód",
"Enter passphrase": "Zadejte heslo",
"Error decrypting attachment": "Chyba při dešifrování přílohy",
"Error: Problem communicating with the given homeserver.": "Chyba: problém v komunikaci s daným domovským serverem.",
"Event information": "Informace o události",
"Existing Call": "Probíhající hovor",
"Export": "Exportovat",
"Export E2E room keys": "Exportovat E2E klíče místnosti",
"Failed to ban user": "Nepodařilo se vykázat uživatele",
"Failed to delete device": "Nepodařilo se vymazat zařízení",
"Failed to join room": "Vstup do místnosti se nezdařil",
"Failed to kick": "Vykopnutí se nezdařilo",
"Failed to leave room": "Opuštění místnosti se nezdařilo",
"Failed to mute user": "Ztlumení uživatele se nezdařilo",
"Failed to send email": "Odeslání e-mailu se nezdařilo",
"Failed to save settings": "Uložení nastavení se nezdařilo",
"Failed to reject invitation": "Odmítnutí pozvánky se nezdařilo",
"Failed to reject invite": "Odmítnutí pozvání se nezdařilo",
"Failed to register as guest:": "Registrace jako host se nezdařila:",
"Failed to send request.": "Odeslání žádosti se nezdařilo.",
"Failed to set avatar.": "Nastavení avataru se nezdařilo.",
"Failed to set display name": "Nastavení zobrazovaného jména se nezdařilo",
"Failed to set up conference call": "Nastavení konferenčního hovoru se nezdařilo",
"Failed to toggle moderator status": "Změna statusu moderátora se nezdařila",
"Failed to unban": "Odvolání vykázání se nezdařilo",
"Failed to upload profile picture!": "Nahrání profilového obrázku se nezdařilo",
"Failure to create room": "Vytvoření místnosti se nezdařilo",
"Forget room": "Zapomenout místnost",
"Forgot your password?": "Zapomněl/a jste své heslo?",
"For security, this session has been signed out. Please sign in again.": "Z bezpečnostních důvodů bylo toto přihlášení ukončeno. Přihlašte se prosím znovu."
}

View file

@ -56,14 +56,10 @@
"Add phone number": "Tilføj telefonnummer", "Add phone number": "Tilføj telefonnummer",
"Admin": "Administrator", "Admin": "Administrator",
"Advanced": "Avanceret", "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", "an address": "en adresse",
"and": "og", "and": "og",
"An email has been sent to": "En e-mail blev sendt til", "An email has been sent to": "En e-mail blev sendt til",
"answered the call.": "svarede på kaldet", "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, 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", "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 leave the room?": "Er du sikker på du vil forlade rummet?",
@ -100,7 +96,6 @@
"Default": "Standard", "Default": "Standard",
"demote": "degradere", "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", "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", "Disable inline URL previews by default": "Deaktiver forrige weblinks forhåndsvisninger som standard",
"Display name": "Visningsnavn", "Display name": "Visningsnavn",
"Email Address": "Email adresse", "Email Address": "Email adresse",
@ -122,7 +117,6 @@
"Failed to unban": "Var ikke i stand til at ophæve forbuddet", "Failed to unban": "Var ikke i stand til at ophæve forbuddet",
"Favourite": "Favorit", "Favourite": "Favorit",
"Notifications": "Meddelser", "Notifications": "Meddelser",
"Please Register": "Vær venlig at registrere",
"Remove": "Fjerne", "Remove": "Fjerne",
"Settings": "Indstillinger", "Settings": "Indstillinger",
"unknown error code": "Ukendt fejlkode", "unknown error code": "Ukendt fejlkode",
@ -137,78 +131,45 @@
"%(names)s and one other are typing": "%(names)s og den anden skriver", "%(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", "%(names)s and %(count)s others are typing": "%(names)s og %(count)s andre skriver",
"%(senderName)s answered the call.": "%(senderName)s besvarede opkaldet.", "%(senderName)s answered the call.": "%(senderName)s besvarede opkaldet.",
"af": "Afrikaans", "Add a widget": "Tilføj en widget",
"ar-eg": "Arabisk (Egypten)", "ar-ae": "Arabisk (U.A.E.)",
"ar-ma": "Arabisk (Marokko)", "ar-bh": "Arabisk (Bahrain)",
"ar-sa": "Arabisk (Saudiarabien", "ar-dz": "Arabisk (Algeriet)",
"ar-sy": "Arabisk (Syrien)", "ar-iq": "Arabisk (Irak)",
"be": "Hviderussisk", "ar-jo": "Arabisk (Jordan)",
"bg": "Bulgarisk", "ar-kw": "Arabisk (Kuwait)",
"ca": "Katalansk", "ar-lb": "Arabisk (Libanon)",
"cs": "Tjekkisk", "ar-ly": "Arabisk (Libyen)",
"de-at": "Tysk (Østrig)", "ar-om": "Arabisk (Oman)",
"de-ch": "Tysk (Schweitz)", "ar-qa": "Arabisk (Qatar)",
"el": "Græsk", "ar-ye": "Arabisk (Jemen)",
"en-au": "Engelsk (Australien)", "ar-tn": "Arabisk (Tunesien)",
"en-ca": "Engelsk (Canada)", "de-li": "Tysk (Liechtenstein)",
"en-ie": "Engelsk (Irland)", "de-lu": "Tysk (Luxembourg)",
"en-nz": "Engelsk (New Zealand)", "en-bz": "Engelsk (Belize)",
"en-us": "Engelsk (USA)", "en-gb": "Engelsk (United Kingdom)",
"en-za": "Engelsk (Sydafrika)", "en-jm": "Engelsk (Jamaica)",
"es-ar": "Spansk (Argentina)", "en-tt": "Engelsk (Trinidad)",
"es-bo": "Spansk (Bolivia)", "es-co": "Spansk (Colombia)",
"es-cl": "Spansk (Chile)", "es-cr": "Spansk (Costa Rica)",
"es-ec": "Spansk (Ecuador)", "es-do": "Spansk (Dominikanske Republik)",
"es-hn": "Spansk (Honduras)", "es-gt": "Spansk (Guatemala)",
"es-mx": "Spansk (Mexico)", "es-pa": "Spansk (Panama)",
"es-ni": "Spansk (Nicaragua)", "es-pe": "Spansk (Peru)",
"es-py": "Spansk (Paraguay)", "es-pr": "Spansk (Puerto Rico)",
"es": "Spansk (Spanien)", "es-sv": "Spansk (El Salvador)",
"es-uy": "Spansk (Uruguay)", "eu": "Baskisk (Baskien)",
"es-ve": "Spansk (Venezuela)", "fo": "Færøsk",
"et": "Estonsk", "fr-lu": "Fransk (Luxembourg)",
"fa": "Farsi", "gd": "Gælisk (Skotland)",
"fi": "Finsk", "it-ch": "Italiensk (Schweiz)",
"fr-be": "Fransk (Belgien)", "ko": "Koreansk",
"fr-ca": "Fransk (Canada)", "mk": "Makedonsk (FYROM)",
"fr-ch": "Fransk (Schweitz)", "nl-be": "Nederlandsk (Belgien)",
"fr": "French", "rm": "Rætoromansk",
"ga": "Irsk", "ro-mo": "Rumænsk (Republikken Moldova)",
"he": "Hebræisk", "ru-mo": "Russisk (Republikken Moldova)",
"hi": "Hindi", "sv-fi": "Svensk (Finland)",
"hr": "Kroatisk", "sx": "Sutu",
"hu": "Ungarsk", "sz": "Samisk (Lappisk)"
"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"
} }

View file

@ -9,7 +9,7 @@
"Historical": "Archiv", "Historical": "Archiv",
"New passwords must match each other.": "Die neuen Passwörter müssen identisch sein.", "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.", "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 mit dem Benutzerkonto verbundene Email-Adresse eingegeben werden.", "The email address linked to your account must be entered.": "Es muss die mit dem Benutzerkonto verbundene E-Mail-Adresse eingegeben werden.",
"Failed to send email: ": "Email konnte nicht versendet werden: ", "Failed to send email: ": "Email konnte nicht versendet werden: ",
"unknown device": "Unbekanntes Gerät", "unknown device": "Unbekanntes Gerät",
"NOT verified": "NICHT verifiziert", "NOT verified": "NICHT verifiziert",
@ -21,21 +21,21 @@
"Ed25519 fingerprint": "Ed25519-Fingerprint", "Ed25519 fingerprint": "Ed25519-Fingerprint",
"User ID": "Benutzer-ID", "User ID": "Benutzer-ID",
"Curve25519 identity key": "Curve25519-Identitäts-Schlüssel", "Curve25519 identity key": "Curve25519-Identitäts-Schlüssel",
"Claimed Ed25519 fingerprint key": "Geforderter Ed25519 Fingerprint Schlüssel", "Claimed Ed25519 fingerprint key": "Geforderter Ed25519-Fingerprint-Schlüssel",
"none": "keiner", "none": "keiner",
"Algorithm": "Algorithmus", "Algorithm": "Algorithmus",
"unencrypted": "unverschlüsselt", "unencrypted": "unverschlüsselt",
"Decryption error": "Entschlüsselungs Fehler", "Decryption error": "Entschlüsselungs Fehler",
"Session ID": "Sitzungs-ID", "Session ID": "Sitzungs-ID",
"End-to-end encryption information": "Ende-zu-Ende-Verschlüsselungs-Informationen", "End-to-end encryption information": "Informationen zur Ende-zu-Ende-Verschlüsselung",
"Event information": "Ereignis-Information", "Event information": "Ereignis-Information",
"Sender device information": "Absender Geräte Informationen", "Sender device information": "Geräte-Informationen des Absenders",
"Displays action": "Zeigt Aktionen an", "Displays action": "Zeigt Aktionen an",
"Bans user with given id": "Schließt den Benutzer mit der angegebenen ID dauerhaft aus dem Raum aus", "Bans user with given id": "Verbannt den Benutzer mit der angegebenen ID",
"Deops user with given id": "Entfernt OP beim Benutzer mit der angegebenen ID", "Deops user with given id": "Entfernt OP beim Benutzer mit der angegebenen ID",
"Invites user with given id to current room": "Lädt den Benutzer mit der angegebenen ID in den aktuellen Raum ein", "Invites user with given id to current room": "Lädt den Benutzer mit der angegebenen ID in den aktuellen Raum ein",
"Joins room with given alias": "Betrete Raum mit angegebenen Alias", "Joins room with given alias": "Betrete Raum mit angegebenen Alias",
"Kicks user with given id": "Entfernt den Benutzer mit der angegebenen ID aus dem Raum", "Kicks user with given id": "Benutzer mit der angegebenen ID kicken",
"Changes your display nickname": "Ändert deinen angezeigten Nicknamen", "Changes your display nickname": "Ändert deinen angezeigten Nicknamen",
"Change Password": "Passwort ändern", "Change Password": "Passwort ändern",
"Searches DuckDuckGo for results": "Verwendet DuckDuckGo für Suchergebnisse", "Searches DuckDuckGo for results": "Verwendet DuckDuckGo für Suchergebnisse",
@ -49,24 +49,21 @@
"Send an encrypted message": "Verschlüsselte Nachricht senden", "Send an encrypted message": "Verschlüsselte Nachricht senden",
"Send a message (unencrypted)": "Nachricht senden (unverschlüsselt)", "Send a message (unencrypted)": "Nachricht senden (unverschlüsselt)",
"Warning!": "Warnung!", "Warning!": "Warnung!",
"Direct Chat": "Direkt-Chat",
"Error": "Fehler", "Error": "Fehler",
"accept": "akzeptiere", "accept": "akzeptiere",
"accepted an invitation": "Einladung akzeptieren", "accepted an invitation": "Einladung akzeptieren",
"accepted the invitation for": "Akzeptierte die Einladung für", "accepted the invitation for": "Akzeptierte die Einladung für",
"Add email address": "E-Mail-Adresse hinzufügen", "Add email address": "E-Mail-Adresse hinzufügen",
"Advanced": "Erweitert", "Advanced": "Erweitert",
"all room members, from the point they joined": "alle Raum-Mitglieder (ab dem Zeitpunkt, an dem sie beigetreten sind)",
"and": "und", "and": "und",
"An email has been sent to": "Eine E-Mail wurde gesendet an", "An email has been sent to": "Eine E-Mail wurde gesendet an",
"anyone": "Jeder",
"Anyone who knows the room's link, apart from guests": "Alle, denen der Raum-Link bekannt ist (ausgenommen Gäste)", "Anyone who knows the room's link, apart from guests": "Alle, denen der Raum-Link bekannt ist (ausgenommen Gäste)",
"Anyone who knows the room's link, including guests": "Alle, denen der Raum-Link bekannt ist (auch Gäste)", "Anyone who knows the room's link, including guests": "Alle, denen der Raum-Link bekannt ist (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 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 du die Einladung ablehnen willst?", "Are you sure you want to reject the invitation?": "Bist du sicher, dass du die Einladung ablehnen willst?",
"Are you sure you want to upload the following files?": "Bist du sicher, dass du die folgenden Dateien hochladen möchtest?", "Are you sure you want to upload the following files?": "Bist du sicher, dass du die folgenden Dateien hochladen möchtest?",
"banned": "gebannt", "banned": "gebannt",
"Banned users": "Dauerhaft aus dem Raum ausgeschlossene Benutzer", "Banned users": "Verbannte Benutzer",
"Bug Report": "Fehlerbericht", "Bug Report": "Fehlerbericht",
"changed avatar": "änderte Avatar", "changed avatar": "änderte Avatar",
"changed their display name from": "änderte seinen Anzeigenamen von", "changed their display name from": "änderte seinen Anzeigenamen von",
@ -84,7 +81,7 @@
"Deactivate Account": "Benutzerkonto deaktivieren", "Deactivate Account": "Benutzerkonto deaktivieren",
"Deactivate my account": "Mein Benutzerkonto deaktivieren", "Deactivate my account": "Mein Benutzerkonto deaktivieren",
"decline": "Ablehnen", "decline": "Ablehnen",
"Devices will not yet be able to decrypt history from before they joined the room": "Geräte werden nicht in der Lage sein, den Chatverlauf vor dem Betreten des Raumes zu entschlüsseln", "Devices will not yet be able to decrypt history from before they joined the room": "Geräte werden nicht in der Lage sein, den bisherigen Chatverlauf vor dem Betreten des Raumes zu entschlüsseln",
"Display name": "Anzeigename", "Display name": "Anzeigename",
"Email Address": "E-Mail-Adresse", "Email Address": "E-Mail-Adresse",
"Email, name or matrix ID": "E-Mail, Name oder Matrix-ID", "Email, name or matrix ID": "E-Mail, Name oder Matrix-ID",
@ -95,12 +92,10 @@
"ended the call.": "beendete den Anruf.", "ended the call.": "beendete den Anruf.",
"End-to-end encryption is in beta and may not be reliable": "Die Ende-zu-Ende-Verschlüsselung befindet sich aktuell im Beta-Stadium und ist eventuell noch nicht hundertprozentig zuverlässig", "End-to-end encryption is in beta and may not be reliable": "Die Ende-zu-Ende-Verschlüsselung befindet sich aktuell im Beta-Stadium und ist eventuell noch nicht hundertprozentig zuverlässig",
"Failed to send email": "Fehler beim Senden der E-Mail", "Failed to send email": "Fehler beim Senden der E-Mail",
"Account": "Konto", "Account": "Benutzerkonto",
"Add phone number": "Telefonnummer hinzufügen", "Add phone number": "Telefonnummer hinzufügen",
"an address": "an Adresse", "an address": "an Adresse",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Dein Passwort wurde erfolgreich geändert. Du wirst erst Benachrichtigungen auf anderen Geräten empfangen können, wenn du dich dort erneut anmeldest", "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Dein Passwort wurde erfolgreich geändert. Du wirst erst Benachrichtigungen auf anderen Geräten empfangen können, wenn du dich dort erneut anmeldest",
"all room members": "Alle Raum-Mitglieder",
"all room members, from the point they are invited": "alle Raum-Mitglieder (ab dem Zeitpunkt, an dem sie eingeladen wurden)",
"answered the call.": "beantwortete den Anruf.", "answered the call.": "beantwortete den Anruf.",
"Can't load user settings": "Benutzereinstellungen können nicht geladen werden", "Can't load user settings": "Benutzereinstellungen können nicht geladen werden",
"changed name": "änderte Namen", "changed name": "änderte Namen",
@ -116,7 +111,7 @@
"Failed to leave room": "Verlassen des Raums fehlgeschlagen", "Failed to leave room": "Verlassen des Raums fehlgeschlagen",
"Failed to reject invitation": "Einladung konnte nicht abgelehnt werden", "Failed to reject invitation": "Einladung konnte nicht abgelehnt werden",
"Failed to set avatar.": "Fehler beim Setzen des Profilbilds.", "Failed to set avatar.": "Fehler beim Setzen des Profilbilds.",
"Failed to unban": "Dauerhaftes Ausschließen aus dem Raum konnte nicht aufgehoben werden", "Failed to unban": "Aufheben der Verbannung fehlgeschlagen",
"Failed to upload file": "Datei-Upload fehlgeschlagen", "Failed to upload file": "Datei-Upload fehlgeschlagen",
"Favourite": "Favorit", "Favourite": "Favorit",
"favourite": "Favorit", "favourite": "Favorit",
@ -126,8 +121,6 @@
"For security, this session has been signed out. Please sign in again.": "Aus Sicherheitsgründen wurde diese Sitzung beendet. Bitte melde dich erneut an.", "For security, this session has been signed out. Please sign in again.": "Aus Sicherheitsgründen wurde diese Sitzung beendet. Bitte melde dich erneut an.",
"Found a bug?": "Fehler gefunden?", "Found a bug?": "Fehler gefunden?",
"Guests cannot join this room even if explicitly invited.": "Gäste können diesem Raum nicht beitreten, auch wenn sie explizit eingeladen wurden.", "Guests cannot join this room even if explicitly invited.": "Gäste können diesem Raum nicht beitreten, auch wenn sie explizit eingeladen wurden.",
"Guests can't set avatars. Please register.": "Gäste können kein Profilbild setzen. Bitte registrieren.",
"Guest users can't upload files. Please register to upload.": "Gäste können keine Dateien hochladen. Bitte zunächst registrieren.",
"had": "hatte", "had": "hatte",
"Hangup": "Auflegen", "Hangup": "Auflegen",
"Homeserver is": "Home-Server:", "Homeserver is": "Home-Server:",
@ -149,7 +142,6 @@
"left the room": "verließ den Raum", "left the room": "verließ den Raum",
"Logged in as": "Angemeldet als", "Logged in as": "Angemeldet als",
"Logout": "Abmelden", "Logout": "Abmelden",
"made future room history visible to": "mache kommende Raum-Historie sichtbar für",
"Manage Integrations": "Integrationen verwalten", "Manage Integrations": "Integrationen verwalten",
"Members only": "Nur Mitglieder", "Members only": "Nur Mitglieder",
"Mobile phone number": "Mobiltelefonnummer", "Mobile phone number": "Mobiltelefonnummer",
@ -173,13 +165,12 @@
"Phone": "Telefon", "Phone": "Telefon",
"placed a": "plazierte einen", "placed a": "plazierte einen",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Bitte prüfe deinen E-Mail-Posteingang und klicke auf den in der E-Mail enthaltenen Link. Anschließend auf \"Fortsetzen\" klicken.", "Please check your email and click on the link it contains. Once this is done, click continue.": "Bitte prüfe deinen E-Mail-Posteingang und klicke auf den in der E-Mail enthaltenen Link. Anschließend auf \"Fortsetzen\" klicken.",
"Please Register": "Bitte registrieren",
"Privacy warning": "Datenschutzwarnung", "Privacy warning": "Datenschutzwarnung",
"Privileged Users": "Privilegierte Nutzer", "Privileged Users": "Privilegierte Nutzer",
"Profile": "Profil", "Profile": "Profil",
"Refer a friend to Riot:": "Freunde zu Riot einladen:", "Refer a friend to Riot:": "Freunde zu Riot einladen:",
"rejected": "abgelehnt", "rejected": "abgelehnt",
"Once you&#39;ve followed the link it contains, click below": "Nachdem du dem darin enthaltenen Link gefolgt bist, klicke unten", "Once you've followed the link it contains, click below": "Nachdem du dem darin enthaltenen Link gefolgt bist, klicke unten",
"rejected the invitation.": "lehnte die Einladung ab.", "rejected the invitation.": "lehnte die Einladung ab.",
"Reject invitation": "Einladung ablehnen", "Reject invitation": "Einladung ablehnen",
"Remove Contact Information?": "Kontakt-Informationen entfernen?", "Remove Contact Information?": "Kontakt-Informationen entfernen?",
@ -221,16 +212,15 @@
"This is a preview of this room. Room interactions have been disabled": "Dies ist eine Vorschau dieses Raumes. Raum-Interaktionen wurden deaktiviert", "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": "Remote-Matrix-Server können auf diesen Raum nicht zugreifen", "This room is not accessible by remote Matrix servers": "Remote-Matrix-Server können auf diesen Raum nicht zugreifen",
"This room's internal ID is": "Die interne ID dieses Raumes ist", "This room's internal ID is": "Die interne ID dieses Raumes ist",
"To ban users": "Um Benutzer dauerhaft aus dem Raum auszuschließen", "To ban users": "Um Benutzer zu verbannen",
"To configure the room": "Um den Raum zu konfigurieren", "To configure the room": "Um den Raum zu konfigurieren",
"To invite users into the room": "Um Nutzer in den Raum einzuladen", "To invite users into the room": "Um Nutzer in den Raum einzuladen",
"to join the discussion": "um an der Diskussion teilzunehmen", "to join the discussion": "um an der Diskussion teilzunehmen",
"To kick users": "Um Benutzer aus dem Raum zu entfernen", "To kick users": "Um Benutzer zu kicken",
"Admin": "Administrator", "Admin": "Administrator",
"Server may be unavailable, overloaded, or you hit a bug.": "Server ist nicht verfügbar, überlastet oder du bist auf einen Fehler gestoßen.", "Server may be unavailable, overloaded, or you hit a bug.": "Server ist nicht verfügbar, überlastet oder du bist auf einen Fehler gestoßen.",
"Could not connect to the integration server": "Konnte keine Verbindung zum Integrations-Server herstellen", "Could not connect to the integration server": "Konnte keine Verbindung zum Integrations-Server herstellen",
"Disable inline URL previews by default": "URL-Vorschau im Chat standardmäßig deaktivieren", "Disable inline URL previews by default": "URL-Vorschau im Chat standardmäßig deaktivieren",
"Guests can't use labs features. Please register.": "Gäste können keine Labor-Funktionen nutzen. Bitte registrieren.",
"Labs": "Labor", "Labs": "Labor",
"Show panel": "Panel anzeigen", "Show panel": "Panel anzeigen",
"To redact messages": "Zum Nachrichten verbergen", "To redact messages": "Zum Nachrichten verbergen",
@ -241,11 +231,10 @@
"turned on end-to-end encryption (algorithm": "aktivierte Ende-zu-Ende-Verschlüsselung (Algorithmus", "turned on end-to-end encryption (algorithm": "aktivierte Ende-zu-Ende-Verschlüsselung (Algorithmus",
"Unable to add email address": "E-Mail-Adresse konnte nicht hinzugefügt werden", "Unable to add email address": "E-Mail-Adresse konnte nicht hinzugefügt werden",
"Unable to remove contact information": "Die Kontakt-Informationen konnten nicht gelöscht werden", "Unable to remove contact information": "Die Kontakt-Informationen konnten nicht gelöscht werden",
"Unable to verify email address.": "Unfähig die E-Mail-Adresse zu verifizieren.", "Unable to verify email address.": "Die E-Mail-Adresse konnte nicht verifiziert werden.",
"Unban": "Dauerhaftes Ausschließen aus dem Raum aufheben", "Unban": "Verbannung aufheben",
"Unencrypted room": "Unverschlüsselter Raum", "Unencrypted room": "Unverschlüsselter Raum",
"unknown error code": "Unbekannter Fehlercode", "unknown error code": "Unbekannter Fehlercode",
"unknown": "unbekannt",
"Upload avatar": "Profilbild hochladen", "Upload avatar": "Profilbild hochladen",
"uploaded a file": "hat eine Datei hochgeladen", "uploaded a file": "hat eine Datei hochgeladen",
"Upload Files": "Dateien hochladen", "Upload Files": "Dateien hochladen",
@ -260,10 +249,10 @@
"VoIP conference finished.": "VoIP-Konferenz wurde beendet.", "VoIP conference finished.": "VoIP-Konferenz wurde beendet.",
"VoIP conference started.": "VoIP-Konferenz gestartet.", "VoIP conference started.": "VoIP-Konferenz gestartet.",
"(warning: cannot be disabled again!)": "(Warnung: Kann nicht wieder deaktiviert werden!)", "(warning: cannot be disabled again!)": "(Warnung: Kann nicht wieder deaktiviert werden!)",
"was banned": "wurde dauerhaft aus dem Raum ausgeschlossen", "was banned": "wurde aus dem Raum verbannt",
"was invited": "wurde eingeladen", "was invited": "wurde eingeladen",
"was kicked": "wurde aus dem Raum entfernt", "was kicked": "wurde gekickt",
"was unbanned": "wurde vom dauerhaften Ausschluss aus dem Raum befreit", "was unbanned": "wurde von der Verbannung aus dem Raum befreit",
"was": "wurde", "was": "wurde",
"Who can access this room?": "Wer hat Zugang zu diesem Raum?", "Who can access this room?": "Wer hat Zugang zu diesem Raum?",
"Who can read history?": "Wer kann den bisherigen Chatverlauf lesen?", "Who can read history?": "Wer kann den bisherigen Chatverlauf lesen?",
@ -288,17 +277,15 @@
"Failed to set up conference call": "Konferenzgespräch konnte nicht gestartet werden", "Failed to set up conference call": "Konferenzgespräch konnte nicht gestartet werden",
"Failed to verify email address: make sure you clicked the link in the email": "Verifizierung der E-Mail-Adresse fehlgeschlagen: Bitte stelle sicher, dass du den Link in der E-Mail angeklickt hast", "Failed to verify email address: make sure you clicked the link in the email": "Verifizierung der E-Mail-Adresse fehlgeschlagen: Bitte stelle sicher, dass du den Link in der E-Mail angeklickt hast",
"Failure to create room": "Raumerstellung fehlgeschlagen", "Failure to create room": "Raumerstellung fehlgeschlagen",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Gastnutzer können keine neuen Räume erstellen. Bitte registriere dich um Räume zu erstellen und Chats zu starten.",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot hat keine Berechtigung, um Benachrichtigungen zu senden - bitte Browser-Einstellungen überprüfen", "Riot does not have permission to send you notifications - please check your browser settings": "Riot hat keine Berechtigung, um Benachrichtigungen zu senden - bitte Browser-Einstellungen überprüfen",
"Riot was not given permission to send notifications - please try again": "Riot hat keine Berechtigung für das Senden von Benachrichtigungen erhalten - bitte erneut versuchen", "Riot was not given permission to send notifications - please try again": "Riot hat keine Berechtigung für das Senden von Benachrichtigungen erhalten - bitte erneut versuchen",
"This email address is already in use": "Diese E-Mail-Adresse wird bereits verwendet", "This email address is already in use": "Diese E-Mail-Adresse wird bereits verwendet",
"This email address was not found": "Diese E-Mail-Adresse konnte nicht gefunden werden", "This email address was not found": "Diese E-Mail-Adresse konnte nicht gefunden werden",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "Die Datei '%(fileName)s' überschreitet das Größen-Limit für Uploads auf diesem Homeserver", "The file '%(fileName)s' exceeds this home server's size limit for uploads": "Die Datei '%(fileName)s' überschreitet das Größen-Limit für Uploads auf diesem Heimserver",
"The file '%(fileName)s' failed to upload": "Das Hochladen der Datei '%(fileName)s' schlug fehl", "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", "The remote side failed to pick up": "Die Gegenstelle konnte nicht abheben",
"This phone number is already in use": "Diese Telefonnummer wird bereits verwendet", "This phone number is already in use": "Diese Telefonnummer wird bereits verwendet",
"Unable to restore previous session": "Die vorherige Sitzung konnte nicht wiederhergestellt werden", "Unable to capture screen": "Der Bildschirm konnte nicht aufgenommen werden",
"Unable to capture screen": "Unfähig den Bildschirm aufzunehmen",
"Unable to enable Notifications": "Benachrichtigungen konnten nicht aktiviert werden", "Unable to enable Notifications": "Benachrichtigungen konnten nicht aktiviert werden",
"Upload Failed": "Upload fehlgeschlagen", "Upload Failed": "Upload fehlgeschlagen",
"VoIP is unsupported": "VoIP wird nicht unterstützt", "VoIP is unsupported": "VoIP wird nicht unterstützt",
@ -350,7 +337,7 @@
"%(names)s and one other are typing": "%(names)s und ein weiteres Raum-Mitglied schreiben", "%(names)s and one other are typing": "%(names)s und ein weiteres Raum-Mitglied schreiben",
"%(names)s and %(count)s others are typing": "%(names)s und %(count)s weitere Raum-Mitglieder schreiben", "%(names)s and %(count)s others are typing": "%(names)s und %(count)s weitere Raum-Mitglieder schreiben",
"%(senderName)s answered the call.": "%(senderName)s hat den Anruf angenommen.", "%(senderName)s answered the call.": "%(senderName)s hat den Anruf angenommen.",
"%(senderName)s banned %(targetName)s.": "%(senderName)s hat %(targetName)s dauerhaft aus dem Raum ausgeschlossen.", "%(senderName)s banned %(targetName)s.": "%(senderName)s hat %(targetName)s dauerhaft aus dem Raum verbannt.",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s hat den Anzeigenamen von \"%(oldDisplayName)s\" auf \"%(displayName)s\" geändert.", "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s hat den Anzeigenamen von \"%(oldDisplayName)s\" auf \"%(displayName)s\" geändert.",
"%(senderName)s changed their profile picture.": "%(senderName)s hat das Profilbild geändert.", "%(senderName)s changed their profile picture.": "%(senderName)s hat das Profilbild geändert.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s hat das Berechtigungslevel von %(powerLevelDiffText)s geändert.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s hat das Berechtigungslevel von %(powerLevelDiffText)s geändert.",
@ -364,13 +351,16 @@
"%(senderName)s invited %(targetName)s.": "%(senderName)s hat %(targetName)s eingeladen.", "%(senderName)s invited %(targetName)s.": "%(senderName)s hat %(targetName)s eingeladen.",
"%(displayName)s is typing": "%(displayName)s schreibt", "%(displayName)s is typing": "%(displayName)s schreibt",
"%(targetName)s joined the room.": "%(targetName)s hat den Raum betreten.", "%(targetName)s joined the room.": "%(targetName)s hat den Raum betreten.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s hat %(targetName)s aus dem Raum entfernt.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s hat %(targetName)s aus dem Raum gekickt.",
"%(targetName)s left the room.": "%(targetName)s hat den Raum verlassen.", "%(targetName)s left the room.": "%(targetName)s hat den Raum verlassen.",
"%(senderName)s made future room history visible to": "%(senderName)s hat den zukünftigen Chatverlauf sichtbar gemacht für", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s hat den zukünftigen Chatverlauf sichtbar gemacht für alle Raum-Mitglieder (ab dem Zeitpunkt, an dem sie eingeladen wurden).",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s hat den zukünftigen Chatverlauf sichtbar gemacht für alle Raum-Mitglieder (ab dem Zeitpunkt, an dem sie beigetreten sind).",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s hat den zukünftigen Chatverlauf sichtbar gemacht für Alle Raum-Mitglieder.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s hat den zukünftigen Chatverlauf sichtbar gemacht für Jeder.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s hat den zukünftigen Chatverlauf sichtbar gemacht für unbekannt (%(visibility)s).",
"Missing room_id in request": "Fehlende room_id in Anfrage", "Missing room_id in request": "Fehlende room_id in Anfrage",
"Missing user_id in request": "Fehlende user_id in Anfrage", "Missing user_id in request": "Fehlende user_id in Anfrage",
"Must be viewing a room": "Muss einen Raum ansehen", "Must be viewing a room": "Muss einen Raum ansehen",
"New Composer & Autocomplete": "Neuer Eingabeverarbeiter & Autovervollständigung",
"(not supported by this browser)": "(wird von diesem Browser nicht unterstützt)", "(not supported by this browser)": "(wird von diesem Browser nicht unterstützt)",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s startete einen %(callType)s-Anruf.", "%(senderName)s placed a %(callType)s call.": "%(senderName)s startete einen %(callType)s-Anruf.",
"Power level must be positive integer.": "Berechtigungslevel muss eine positive ganze Zahl sein.", "Power level must be positive integer.": "Berechtigungslevel muss eine positive ganze Zahl sein.",
@ -388,7 +378,7 @@
"These are experimental features that may break in unexpected ways": "Dies sind experimentelle Funktionen, die in unerwarteter Weise Fehler verursachen können", "These are experimental features that may break in unexpected ways": "Dies sind experimentelle Funktionen, die in unerwarteter Weise Fehler verursachen können",
"To use it, just wait for autocomplete results to load and tab through them.": "Um diese Funktion zu nutzen, warte einfach auf die Autovervollständigungsergebnisse und benutze dann die TAB-Taste zum durchblättern.", "To use it, just wait for autocomplete results to load and tab through them.": "Um diese Funktion zu nutzen, warte einfach auf die Autovervollständigungsergebnisse und benutze dann die TAB-Taste zum durchblättern.",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s hat die Ende-zu-Ende-Verschlüsselung aktiviert (Algorithmus: %(algorithm)s).", "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s hat die Ende-zu-Ende-Verschlüsselung aktiviert (Algorithmus: %(algorithm)s).",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s hat das dauerhafte Ausschließen von %(targetName)s aus dem Raum aufgehoben.", "%(senderName)s unbanned %(targetName)s.": "%(senderName)s hat die Verbannung von %(targetName)s aufgehoben.",
"Usage": "Verwendung", "Usage": "Verwendung",
"Use with caution": "Mit Vorsicht zu verwenden", "Use with caution": "Mit Vorsicht zu verwenden",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s hat die Einladung für %(targetName)s zurückgezogen.", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s hat die Einladung für %(targetName)s zurückgezogen.",
@ -399,7 +389,6 @@
"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.", "Riot was unable to find the correct Data for the selected Language.": "Riot war nicht in der Lage die korrekten Daten für die ausgewählte Sprache zu finden.",
"Connectivity to the server has been lost.": "Verbindung zum Server wurde unterbrochen.", "Connectivity to the server has been lost.": "Verbindung zum Server wurde unterbrochen.",
"Sent messages will be stored until your connection has returned.": "Gesendete Nachrichten werden gespeichert, bis die Internetverbindung wiederhergestellt wird.", "Sent messages will be stored until your connection has returned.": "Gesendete Nachrichten werden gespeichert, bis die Internetverbindung wiederhergestellt wird.",
"Auto-complete": "Autovervollständigung",
"Resend all": "Alle erneut senden", "Resend all": "Alle erneut senden",
"cancel all": "alles abbrechen", "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.", "now. You can also select individual messages to resend or cancel.": "jetzt. Du kannst auch einzelne Nachrichten zum erneuten Senden oder Abbrechen auswählen.",
@ -414,8 +403,6 @@
"ru": "Russisch", "ru": "Russisch",
"Drop here %(toAction)s": "Hierher ziehen: %(toAction)s", "Drop here %(toAction)s": "Hierher ziehen: %(toAction)s",
"Drop here to tag %(section)s": "Hierher ziehen: %(section)s taggen", "Drop here to tag %(section)s": "Hierher ziehen: %(section)s taggen",
"Press": "Drücke",
"tag as %(tagName)s": "als %(tagName)s markieren",
"to browse the directory": "um das Raum-Verzeichnis zu durchsuchen", "to browse the directory": "um das Raum-Verzeichnis zu durchsuchen",
"to demote": "um die Priorität herabzusetzen", "to demote": "um die Priorität herabzusetzen",
"to favourite": "zum Favorisieren", "to favourite": "zum Favorisieren",
@ -427,121 +414,6 @@
"click to reveal": "anzeigen", "click to reveal": "anzeigen",
"To remove other users' messages": "Um Nachrichten anderer Nutzer zu verbergen", "To remove other users' messages": "Um Nachrichten anderer Nutzer zu verbergen",
"You are trying to access %(roomName)s.": "Du versuchst, auf den Raum \"%(roomName)s\" zuzugreifen.", "You are trying to access %(roomName)s.": "Du versuchst, auf den Raum \"%(roomName)s\" zuzugreifen.",
"af": "Afrikaans",
"ar-ae": "Arabisch (VAE)",
"ar-bh": "Arabisch (Bahrain)",
"ar-dz": "Arabisch (Algerien)",
"ar-eg": "Arabisch (Ägypten)",
"ar-iq": "Arabisch (Irak)",
"ar-jo": "Arabisch (Jordanien)",
"ar-kw": "Arabisch (Kuwait)",
"ar-lb": "Arabisch (Libanon)",
"ar-ly": "Arabisch (Lybien)",
"ar-ma": "Arabisch (Marokko)",
"ar-om": "Arabisch (Oman)",
"ar-qa": "Arabisch (Katar)",
"ar-sa": "Arabisch (Saudi Arabien)",
"ar-sy": "Arabisch (Syrien)",
"ar-tn": "Arabisch (Tunesien)",
"ar-ye": "Arabisch (Jemen)",
"be": "Weißrussisch",
"bg": "Bulgarisch",
"cs": "Tschechisch",
"de-at": "Deutsch (Österreich)",
"de-ch": "Deutsch (Schweiz)",
"de-li": "Deutsch (Liechtenstein)",
"de-lu": "Deutsch (Luxemburg)",
"el": "Neugriechisch",
"en-au": "Englisch (Australien)",
"en-bz": "Englisch (Belize)",
"en-ca": "Englisch (Kanada)",
"en-gb": "Englisch (Vereinigtes Königreich)",
"en-ie": "Englisch (Irland)",
"en-jm": "Englisch (Jamaika)",
"en-nz": "Englisch (Neuseeland)",
"en-tt": "Englisch (Trinidad)",
"en-us": "Englisch (Vereinigte Staaten)",
"en-za": "Englisch (Südafrika)",
"es-ar": "Spanisch (Argentinien)",
"es-bo": "Spanisch (Bolivien)",
"es-cl": "Spanisch (Chile)",
"es-co": "Spanisch (Kolumbien)",
"es-cr": "Spanisch (Costa Rica)",
"es-do": "Spanisch (Dominikanische Republik)",
"es-ec": "Spanisch (Ecuador)",
"es-gt": "Spanisch (Guatemala)",
"es-hn": "Spanisch (Honduras)",
"es-mx": "Spanisch (Mexiko)",
"es-ni": "Spanisch (Nicaragua)",
"es-pa": "Spanisch (Panama)",
"es-pe": "Spanisch (Peru)",
"es-pr": "Spanisch (Puerto Rico)",
"es-py": "Spanisch (Paraguay)",
"es": "Spanisch (Spanien)",
"es-sv": "Spanisch (El Salvador)",
"es-uy": "Spanisch (Uruguay)",
"es-ve": "Spanisch (Venezuela)",
"et": "Estländisch",
"eu": "Baskisch (Baskenland)",
"fa": "Persisch (Farsi)",
"fr-be": "Französisch (Belgien)",
"fr-ca": "Französisch (Kanada)",
"fr-ch": "Französisch (Schweiz)",
"fr": "Französisch",
"fr-lu": "Französisch (Luxemburg)",
"gd": "Gälisch (Schottland)",
"he": "Hebräisch",
"hr": "Kroatisch",
"hu": "Ungarisch",
"id": "Indonesisch",
"is": "Isländisch",
"it-ch": "Italienisch (Schweiz)",
"it": "Italienisch",
"ja": "Japanisch",
"ji": "Jiddisch",
"ko": "Koreanisch",
"lt": "Litauisch",
"lv": "Lettisch",
"mk": "Mazedonisch (FYROM)",
"ms": "Malaysisch",
"mt": "Maltesisch",
"nl-be": "Niederländisch (Belgien)",
"nl": "Niederländisch",
"no": "Norwegisch",
"pl": "Polnisch",
"pt": "Portugiesisch",
"rm": "Rätoromanisch",
"ro-mo": "Rumänisch (Republik Moldau/Moldawien)",
"ro": "Rumänisch",
"ru-mo": "Russisch (Republik Moldau/Moldawien)",
"sb": "Sorbisch",
"sk": "Slowakisch",
"sl": "Slowenisch",
"sq": "Albanisch",
"sr": "Serbisch",
"sv-fi": "Schwedisch (Finnland)",
"sv": "Schwedisch",
"sx": "Sutu",
"sz": "Samisch (Lappisch)",
"th": "Thailändisch",
"tn": "Setswana",
"tr": "Türkisch",
"ts": "Tsonga",
"uk": "Ukrainisch",
"ur": "Urdu",
"ve": "Tshivenda",
"vi": "Vietnamesisch",
"zh-cn": "Chinesisch (Volksrepublik China)",
"zh-hk": "Chinesisch (Hong Kong SAR)",
"zh-sg": "Chinesisch (Singapur)",
"zh-tw": "Chinesisch (Taiwan)",
"zu": "Zulu",
"ca": "Katalanisch",
"fi": "Finnisch",
"fo": "Färöisch",
"ga": "Irisch",
"hi": "Hindi",
"xh": "Xhosa",
"Monday": "Montag", "Monday": "Montag",
"Tuesday": "Dienstag", "Tuesday": "Dienstag",
"Wednesday": "Mittwoch", "Wednesday": "Mittwoch",
@ -550,29 +422,25 @@
"Saturday": "Samstag", "Saturday": "Samstag",
"Sunday": "Sonntag", "Sunday": "Sonntag",
"Failed to forget room %(errCode)s": "Das Entfernen des Raums ist fehlgeschlagen %(errCode)s", "Failed to forget room %(errCode)s": "Das Entfernen des Raums ist fehlgeschlagen %(errCode)s",
"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 den darin enthaltenen Verifizierungscode eingeben",
"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 %(count)s others...|other": "und %(count)s weitere...",
"and %(count)s others...": { "and %(count)s others...|one": "und ein(e) weitere(r)...",
"other": "und %(count)s weitere...",
"one": "und ein(e) weitere(r)..."
},
"Are you sure?": "Bist du sicher?", "Are you sure?": "Bist du sicher?",
"Attachment": "Anhang", "Attachment": "Anhang",
"Ban": "Dauerhaft aus dem Raum ausschließen", "Ban": "Verbannen",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Verbindungsaufbau zum Heimserver nicht möglich - bitte Internetverbindung überprüfen und sicherstellen, ob das <a>SSL-Zertifikat des Heimservers</a> vertrauenswürdig ist.", "Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Verbindungsaufbau zum Heimserver nicht möglich - bitte Internetverbindung überprüfen und sicherstellen, ob das <a>SSL-Zertifikat des Heimservers</a> vertrauenswürdig ist.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Es kann keine Verbindung zum Heimserver via HTTP aufgebaut werden, wenn die Adresszeile des Browsers eine HTTPS-URL enthält. Entweder HTTPS verwenden oder alternativ <a>unsichere Skripte erlauben</a>.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Es kann keine Verbindung zum Heimserver via HTTP aufgebaut werden, wenn die Adresszeile des Browsers eine HTTPS-URL enthält. Entweder HTTPS verwenden oder alternativ <a>unsichere Skripte erlauben</a>.",
"changing room on a RoomView is not supported": "Das Ändern eines Raumes in einer RaumAnsicht wird nicht unterstützt",
"Click to mute audio": "Klicke um den Ton stumm zu stellen", "Click to mute audio": "Klicke um den Ton stumm zu stellen",
"Click to mute video": "Klicken, um das Video stummzuschalten", "Click to mute video": "Klicken, um das Video stummzuschalten",
"Command error": "Befehlsfehler", "Command error": "Befehlsfehler",
"Decrypt %(text)s": "%(text)s entschlüsseln", "Decrypt %(text)s": "%(text)s entschlüsseln",
"Delete": "Löschen", "Delete": "Löschen",
"Devices": "Geräte", "Devices": "Geräte",
"Direct chats": "Direkte Chats", "Direct chats": "Direkt-Chats",
"Disinvite": "Einladung zurückziehen", "Disinvite": "Einladung zurückziehen",
"Download %(text)s": "%(text)s herunterladen", "Download %(text)s": "%(text)s herunterladen",
"Enter Code": "Code eingeben", "Enter Code": "Code eingeben",
"Failed to ban user": "Dauerhaftes Ausschließen des Benutzers aus dem Raum fehlgeschlagen", "Failed to ban user": "Verbannen des Benutzers fehlgeschlagen",
"Failed to change power level": "Ändern des Berechtigungslevels fehlgeschlagen", "Failed to change power level": "Ändern des Berechtigungslevels fehlgeschlagen",
"Failed to delete device": "Löschen des Geräts fehlgeschlagen", "Failed to delete device": "Löschen des Geräts fehlgeschlagen",
"Failed to join room": "Betreten des Raumes ist fehlgeschlagen", "Failed to join room": "Betreten des Raumes ist fehlgeschlagen",
@ -589,7 +457,7 @@
"'%(alias)s' is not a valid format for an address": "'%(alias)s' ist kein gültiges Adressformat", "'%(alias)s' is not a valid format for an address": "'%(alias)s' ist kein gültiges Adressformat",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' ist kein gültiges Alias-Format", "'%(alias)s' is not a valid format for an alias": "'%(alias)s' ist kein gültiges Alias-Format",
"Join Room": "Dem Raum beitreten", "Join Room": "Dem Raum beitreten",
"Kick": "Aus dem Raum entfernen", "Kick": "Kicken",
"Level": "Berechtigungslevel", "Level": "Berechtigungslevel",
"Local addresses for this room:": "Lokale Adressen dieses Raumes:", "Local addresses for this room:": "Lokale Adressen dieses Raumes:",
"Markdown is disabled": "Markdown ist deaktiviert", "Markdown is disabled": "Markdown ist deaktiviert",
@ -612,14 +480,13 @@
"Some of your messages have not been sent.": "Einige deiner Nachrichten wurden nicht gesendet.", "Some of your messages have not been sent.": "Einige deiner Nachrichten wurden nicht gesendet.",
"Submit": "Absenden", "Submit": "Absenden",
"The main address for this room is: %(canonical_alias_section)s": "Die Hauptadresse für diesen Raum ist: %(canonical_alias_section)s", "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?", "%(actionVerb)s this person?": "Diese Person %(actionVerb)s?",
"This room has no local addresses": "Dieser Raum hat keine lokale Adresse", "This room has no local addresses": "Dieser Raum hat keine lokale Adresse",
"This room is private or inaccessible to guests. You may be able to join if you register": "Dieser Raum ist privat oder für Gäste nicht zugänglich. Du kannst jedoch eventuell beitreten, wenn du dich registrierst", "This room is private or inaccessible to guests. You may be able to join if you register": "Dieser Raum ist privat oder für Gäste nicht zugänglich. Du kannst jedoch eventuell beitreten, wenn du dich registrierst",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Es wurde versucht, einen bestimmten Punkt im Chatverlauf dieses Raumes zu laden. Dir fehlt jedoch die Berechtigung, die betreffende Nachricht zu sehen.", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Es wurde versucht, einen bestimmten Punkt im Chatverlauf dieses Raumes zu laden. Dir fehlt jedoch die Berechtigung, die betreffende Nachricht zu sehen.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Es wurde versucht, einen bestimmten Punkt im Chatverlauf dieses Raumes zu laden, der Punkt konnte jedoch nicht gefunden werden.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Es wurde versucht, einen bestimmten Punkt im Chatverlauf dieses Raumes zu laden, der Punkt konnte jedoch nicht gefunden werden.",
"Turn Markdown off": "Markdown deaktiveren", "Turn Markdown off": "Markdown deaktiveren",
"Turn Markdown on": "Markdown einschalten", "Turn Markdown on": "Markdown aktivieren",
"Unable to load device list": "Geräteliste konnte nicht geladen werden", "Unable to load device list": "Geräteliste konnte nicht geladen werden",
"Unknown room %(roomId)s": "Unbekannter Raum %(roomId)s", "Unknown room %(roomId)s": "Unbekannter Raum %(roomId)s",
"Usage: /markdown on|off": "Verwendung: /markdown on|off", "Usage: /markdown on|off": "Verwendung: /markdown on|off",
@ -630,10 +497,10 @@
"Room": "Raum", "Room": "Raum",
"(~%(searchCount)s results)": "(~%(searchCount)s Ergebnisse)", "(~%(searchCount)s results)": "(~%(searchCount)s Ergebnisse)",
"Cancel": "Abbrechen", "Cancel": "Abbrechen",
"bold": "fett", "bold": "Fett",
"italic": "kursiv", "italic": "Kursiv",
"strike": "durchstreichen", "strike": "Durchgestrichen",
"underline": "unterstreichen", "underline": "Unterstrichen",
"code": "Code", "code": "Code",
"quote": "Zitat", "quote": "Zitat",
"bullet": "Aufzählung", "bullet": "Aufzählung",
@ -651,7 +518,7 @@
"%(items)s and %(remaining)s others": "%(items)s und %(remaining)s weitere", "%(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 one other": "%(items)s und ein(e) weitere(r)",
"%(items)s and %(lastItem)s": "%(items)s und %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s und %(lastItem)s",
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)s sind dem Raum %(repeats)s mal beigetreten", "%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)shaben den Raum %(repeats)s-mal betreten",
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)shat den Raum %(repeats)s mal betreten", "%(oneUser)sjoined %(repeats)s times": "%(oneUser)shat den Raum %(repeats)s mal betreten",
"%(severalUsers)sjoined": "%(severalUsers)shaben den Raum betreten", "%(severalUsers)sjoined": "%(severalUsers)shaben den Raum betreten",
"%(oneUser)sjoined": "%(oneUser)shat den Raum betreten", "%(oneUser)sjoined": "%(oneUser)shat den Raum betreten",
@ -667,7 +534,7 @@
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)shat den Raum %(repeats)s mal verlassen und wieder neu betreten", "%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)shat den Raum %(repeats)s mal verlassen und wieder neu betreten",
"%(severalUsers)sleft and rejoined": "%(severalUsers)shaben den Raum verlassen und wieder neu betreten", "%(severalUsers)sleft and rejoined": "%(severalUsers)shaben den Raum verlassen und wieder neu betreten",
"%(oneUser)sleft left and rejoined": "%(oneUser)sging und trat erneut bei", "%(oneUser)sleft left and rejoined": "%(oneUser)sging und trat erneut bei",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)shaben ihre Einladung %(repeats)s mal abgelehnt", "%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)shaben ihre Einladung %(repeats)s-mal abgelehnt",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)shat die Einladung %(repeats)s mal abgelehnt", "%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)shat die Einladung %(repeats)s mal abgelehnt",
"%(severalUsers)srejected their invitations": "%(severalUsers)shaben ihre Einladung abgelehnt", "%(severalUsers)srejected their invitations": "%(severalUsers)shaben ihre Einladung abgelehnt",
"%(oneUser)srejected their invitation": "%(oneUser)shat die Einladung abgelehnt", "%(oneUser)srejected their invitation": "%(oneUser)shat die Einladung abgelehnt",
@ -675,18 +542,18 @@
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)swurde die Einladung %(repeats)s mal wieder entzogen", "%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)swurde die Einladung %(repeats)s mal wieder entzogen",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)szogen ihre Einladungen zurück", "%(severalUsers)shad their invitations withdrawn": "%(severalUsers)szogen ihre Einladungen zurück",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)swurde die ursprüngliche Einladung wieder entzogen", "%(oneUser)shad their invitation withdrawn": "%(oneUser)swurde die ursprüngliche Einladung wieder entzogen",
"were invited %(repeats)s times": "wurden %(repeats)s mal eingeladen", "were invited %(repeats)s times": "wurden %(repeats)s-mal eingeladen",
"was invited %(repeats)s times": "wurde %(repeats)s mal eingeladen", "was invited %(repeats)s times": "wurde %(repeats)s mal eingeladen",
"were invited": "wurden eingeladen", "were invited": "wurden eingeladen",
"were banned %(repeats)s times": "wurden %(repeats)s-mal dauerhaft aus dem Raum ausgeschlossen", "were banned %(repeats)s times": "wurden %(repeats)s-mal aus dem Raum verbannt",
"was banned %(repeats)s times": "wurde %(repeats)s-mal aus dem Raum ausgeschlossen", "was banned %(repeats)s times": "wurde %(repeats)s-mal aus dem Raum verbannt",
"were banned": "wurden dauerhaft aus dem Raum ausgeschlossen", "were banned": "wurden aus dem Raum verbannt",
"were unbanned %(repeats)s times": "wurden %(repeats)s mal vom dauerhaften Ausschluss aus dem Raum befreit", "were unbanned %(repeats)s times": "wurden %(repeats)s-mal von der Verbannung aus dem Raum befreit",
"was unbanned %(repeats)s times": "wurde %(repeats)s mal vom dauerhaften Ausschluss aus dem Raum befreit", "was unbanned %(repeats)s times": "wurde %(repeats)s-mal von der Verbannung aus dem Raum befreit",
"were unbanned": "wurden vom dauerhaften Ausschluss aus dem Raum befreit", "were unbanned": "wurden von der Verbannung aus dem Raum befreit",
"were kicked %(repeats)s times": "wurden %(repeats)s-mal aus dem Raum entfernt", "were kicked %(repeats)s times": "wurden %(repeats)s-mal gekickt",
"was kicked %(repeats)s times": "wurde %(repeats)s-mal aus dem Raum entfernt", "was kicked %(repeats)s times": "wurde %(repeats)s-mal gekickt",
"were kicked": "wurden aus dem Raum entfernt", "were kicked": "wurden gekickt",
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)shaben ihren Namen %(repeats)s mal geändert", "%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)shaben ihren Namen %(repeats)s mal geändert",
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)shat den Namen %(repeats)s mal geändert", "%(oneUser)schanged their name %(repeats)s times": "%(oneUser)shat den Namen %(repeats)s mal geändert",
"%(severalUsers)schanged their name": "%(severalUsers)shaben ihre Namen geändert", "%(severalUsers)schanged their name": "%(severalUsers)shaben ihre Namen geändert",
@ -714,7 +581,6 @@
"riot-web version:": "Version von riot-web:", "riot-web version:": "Version von riot-web:",
"Scroll to bottom of page": "Zum Ende der Seite springen", "Scroll to bottom of page": "Zum Ende der Seite springen",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Zeitstempel im 12-Stunden-Format anzeigen (z. B. 2:30pm)", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Zeitstempel im 12-Stunden-Format anzeigen (z. B. 2:30pm)",
"to tag as %(tagName)s": "um als \"%(tagName)s\" zu markieren",
"Email address": "E-Mail-Adresse", "Email address": "E-Mail-Adresse",
"Error decrypting attachment": "Fehler beim Entschlüsseln des Anhangs", "Error decrypting attachment": "Fehler beim Entschlüsseln des Anhangs",
"Mute": "Stummschalten", "Mute": "Stummschalten",
@ -735,7 +601,6 @@
"You must join the room to see its files": "Du musst dem Raum beitreten, um die Raum-Dateien sehen zu können", "You must join the room to see its files": "Du musst dem Raum beitreten, um die Raum-Dateien sehen zu können",
"Reject all %(invitedRooms)s invites": "Alle %(invitedRooms)s Einladungen ablehnen", "Reject all %(invitedRooms)s invites": "Alle %(invitedRooms)s Einladungen ablehnen",
"Start new Chat": "Starte neuen Chat", "Start new Chat": "Starte neuen Chat",
"Guest users can't invite users. Please register.": "Gäste können keine Benutzer einladen. Bitte registrieren.",
"Failed to invite": "Einladen fehlgeschlagen", "Failed to invite": "Einladen fehlgeschlagen",
"Failed to invite user": "Einladen des Nutzers ist fehlgeschlagen", "Failed to invite user": "Einladen des Nutzers ist fehlgeschlagen",
"Confirm Removal": "Entfernen bestätigen", "Confirm Removal": "Entfernen bestätigen",
@ -745,12 +610,11 @@
"To continue, please enter your password.": "Zum fortfahren bitte Passwort eingeben.", "To continue, please enter your password.": "Zum fortfahren bitte Passwort eingeben.",
"Device name": "Geräte-Name", "Device name": "Geräte-Name",
"Device key": "Geräte-Schlüssel", "Device key": "Geräte-Schlüssel",
"In future this verification process will be more sophisticated.": "Zukünftig wird dieser Verifizierungsprozess technisch ausgereifter und eleganter gestaltet werden.", "In future this verification process will be more sophisticated.": "Zukünftig wird der Verifizierungsprozess technisch ausgereifter und eleganter gestaltet werden.",
"Verify device": "Gerät verifizieren", "Verify device": "Gerät verifizieren",
"I verify that the keys match": "Ich bestätige, dass die Schlüssel passen", "I verify that the keys match": "Ich bestätige, dass die Schlüssel identisch sind",
"Unable to restore session": "Sitzungswiederherstellung fehlgeschlagen", "Unable to restore session": "Sitzungswiederherstellung fehlgeschlagen",
"Continue anyway": "Trotzdem fortfahren", "Continue anyway": "Trotzdem fortfahren",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Dein Anzeigename ist der Name, der anderen Nutzern angezeigt wird, wenn du in Räumen kommunizierst. Welchen Anzeigenamen möchtest du wählen?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Nicht verifizierte Geräte werden aktuell blockiert und auf die Sperrliste gesetzt. Um Nachrichten an diese Geräte senden zu können, müssen diese zunächst verifiziert werden.", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Nicht verifizierte Geräte werden aktuell blockiert und auf die Sperrliste gesetzt. Um Nachrichten an diese Geräte senden zu können, müssen diese zunächst verifiziert werden.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" enthält Geräte, die du bislang noch nicht gesehen hast.", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" enthält Geräte, die du bislang noch nicht gesehen hast.",
"Unknown devices": "Unbekannte Geräte", "Unknown devices": "Unbekannte Geräte",
@ -759,9 +623,9 @@
"ex. @bob:example.com": "z. B. @bob:example.com", "ex. @bob:example.com": "z. B. @bob:example.com",
"Add User": "Benutzer hinzufügen", "Add User": "Benutzer hinzufügen",
"Sign in with CAS": "Mit CAS anmelden", "Sign in with CAS": "Mit CAS anmelden",
"Custom Server Options": "Erweiterte Server-Optionen", "Custom Server Options": "Benutzerdefinierte Server-Optionen",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Du kannst die erweiterten Server-Optionen nutzen, um dich an anderen Matrix-Servern anzumelden, indem du eine andere Heimserver-URL angibst.", "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "Du kannst die erweiterten Server-Optionen nutzen, um dich an anderen Matrix-Servern anzumelden, indem du eine andere Heimserver-URL angibst.",
"This allows you to use this app with an existing Matrix account on a different home server.": "Dies erlaubt dir diese App mit einem existierenden Matrix-Konto auf einem anderen Heimserver zu verwenden.", "This allows you to use this app with an existing Matrix account on a different home server.": "Dies erlaubt es dir, diese App mit einem existierenden Matrix-Benutzerkonto auf einem anderen Heimserver zu verwenden.",
"Dismiss": "Ablehnen", "Dismiss": "Ablehnen",
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Du kannst auch einen angepassten Idantitätsserver angeben aber dies wird typischerweise Interaktionen mit anderen Nutzern auf Basis der E-Mail-Adresse verhindern.", "You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "Du kannst auch einen angepassten Idantitätsserver angeben aber dies wird typischerweise Interaktionen mit anderen Nutzern auf Basis der E-Mail-Adresse verhindern.",
"Please check your email to continue registration.": "Bitte prüfe deine E-Mails, um mit der Registrierung fortzufahren.", "Please check your email to continue registration.": "Bitte prüfe deine E-Mails, um mit der Registrierung fortzufahren.",
@ -800,9 +664,9 @@
"Online": "Online", "Online": "Online",
" (unsupported)": " (nicht unterstützt)", " (unsupported)": " (nicht unterstützt)",
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Dieser Prozess erlaubt es dir, die zuvor von einem anderen Matrix-Client exportierten Verschlüsselungs-Schlüssel zu importieren. Danach kannst du alle Nachrichten entschlüsseln, die auch bereits auf dem anderen Client entschlüsselt werden konnten.", "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Dieser Prozess erlaubt es dir, die zuvor von einem anderen Matrix-Client exportierten Verschlüsselungs-Schlüssel zu importieren. Danach kannst du alle Nachrichten entschlüsseln, die auch bereits auf dem anderen Client entschlüsselt werden konnten.",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Dies wird dein Konto permanent unbenutzbar machen. Du wirst dich nicht mit derselben Nutzer-ID erneut registrieren können.", "This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Dies wird dein Benutzerkonto dauerhaft unbenutzbar machen. Du wirst nicht in der Lage sein, dich mit derselben Benutzer-ID erneut zu registrieren.",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "Um sicherzustellen, dass diesem Gerät vertraut werden kann, kontaktiere bitte den Eigentümer des Geräts über ein anderes Kommunikationsmittel (z.B. im persönlichen Gespräch oder durch einen Telefon-Anruf) und vergewissere dich, dass der Schlüssel, den der Eigentümer in den Nutzer-Einstellungen für dieses Gerät sieht, mit dem folgenden Schlüssel identisch ist:", "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "Um sicherzustellen, dass diesem Gerät vertraut werden kann, kontaktiere bitte den Eigentümer des Geräts über ein anderes Kommunikationsmittel (z.B. im persönlichen Gespräch oder durch einen Telefon-Anruf) und vergewissere dich, dass der Schlüssel, den der Eigentümer in den Nutzer-Einstellungen für dieses Gerät sieht, mit dem folgenden Schlüssel identisch ist:",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Wenn er passt, betätige den Bestätigen-Button unten. Wenn nicht, fängt jemand anderes dieses Gerät ab und du möchtest wahrscheinlich lieber den Blacklist-Button betätigen.", "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Wenn er identisch ist, bitte den Bestätigen-Button unten verwenden. Falls er nicht identisch sein sollte, hat eine Fremdperson Kontrolle über dieses Gerät und es sollte gesperrt werden.",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Bei der Wiederherstellung deiner letzten Sitzung ist ein Fehler aufgetreten. Um fortzufahren, musst du dich erneut anmelden. Ein zuvor verschlüsselter Chatverlauf wird in der Folge nicht mehr lesbar sein.", "We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Bei der Wiederherstellung deiner letzten Sitzung ist ein Fehler aufgetreten. Um fortzufahren, musst du dich erneut anmelden. Ein zuvor verschlüsselter Chatverlauf wird in der Folge nicht mehr lesbar sein.",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Wenn du zuvor eine aktuellere Version von Riot verwendet hast, ist deine Sitzung eventuell inkompatibel mit dieser Version. Bitte schließe dieses Fenster und kehre zur aktuelleren Version zurück.", "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Wenn du zuvor eine aktuellere Version von Riot verwendet hast, ist deine Sitzung eventuell inkompatibel mit dieser Version. Bitte schließe dieses Fenster und kehre zur aktuelleren Version zurück.",
"Blacklist": "Blockieren", "Blacklist": "Blockieren",
@ -813,7 +677,7 @@
"Idle": "Untätig", "Idle": "Untätig",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Wir empfehlen dir, für jedes Gerät den Verifizierungsprozess durchzuführen, um sicherzustellen, dass sie tatsächlich ihrem rechtmäßigem Eigentümer gehören. Alternativ kannst du die Nachrichten auch ohne Verifizierung erneut senden.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Wir empfehlen dir, für jedes Gerät den Verifizierungsprozess durchzuführen, um sicherzustellen, dass sie tatsächlich ihrem rechtmäßigem Eigentümer gehören. Alternativ kannst du die Nachrichten auch ohne Verifizierung erneut senden.",
"Ongoing conference call%(supportedText)s.": "Laufendes Konferenzgespräch%(supportedText)s.", "Ongoing conference call%(supportedText)s.": "Laufendes Konferenzgespräch%(supportedText)s.",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du wirst jetzt auf die Website eines Drittanbieters weitergeleitet, damit du dein Konto für die Verwendung von %(integrationsUrl)s authentifizieren kannst. Möchtest du fortfahren?", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du wirst jetzt auf die Website eines Drittanbieters weitergeleitet, damit du dein Benutzerkonto für die Verwendung von %(integrationsUrl)s authentifizieren kannst. Möchtest du fortfahren?",
"Disable URL previews for this room (affects only you)": "URL-Vorschau für diesen Raum deaktivieren (betrifft nur dich)", "Disable URL previews for this room (affects only you)": "URL-Vorschau für diesen Raum deaktivieren (betrifft nur dich)",
"Start automatically after system login": "Nach System-Login automatisch starten", "Start automatically after system login": "Nach System-Login automatisch starten",
"Desktop specific": "Desktopspezifisch", "Desktop specific": "Desktopspezifisch",
@ -822,7 +686,6 @@
"disabled": "deaktiviert", "disabled": "deaktiviert",
"enabled": "aktiviert", "enabled": "aktiviert",
"Invited": "Eingeladen", "Invited": "Eingeladen",
"Set a Display Name": "Anzeigenamen festlegen",
"for %(amount)ss": "für %(amount)ss", "for %(amount)ss": "für %(amount)ss",
"for %(amount)sm": "seit %(amount)smin", "for %(amount)sm": "seit %(amount)smin",
"for %(amount)sh": "für %(amount)sh", "for %(amount)sh": "für %(amount)sh",
@ -841,7 +704,7 @@
"Export": "Export", "Export": "Export",
"Failed to register as guest:": "Die Registrierung als Gast ist fehlgeschlagen:", "Failed to register as guest:": "Die Registrierung als Gast ist fehlgeschlagen:",
"Guest access is disabled on this Home Server.": "Gastzugang ist auf diesem Heimserver deaktivert.", "Guest access is disabled on this Home Server.": "Gastzugang ist auf diesem Heimserver deaktivert.",
"Import": "Import", "Import": "Importieren",
"Incorrect username and/or password.": "Inkorrekter Nutzername und/oder Passwort.", "Incorrect username and/or password.": "Inkorrekter Nutzername und/oder Passwort.",
"Results from DuckDuckGo": "Ergebnisse von DuckDuckGo", "Results from DuckDuckGo": "Ergebnisse von DuckDuckGo",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Den Signaturschlüssel den du bereitstellst stimmt mit dem Schlüssel den du von %(userId)s's Gerät %(deviceId)s empfangen hast überein. Gerät als verifiziert markiert.", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Den Signaturschlüssel den du bereitstellst stimmt mit dem Schlüssel den du von %(userId)s's Gerät %(deviceId)s empfangen hast überein. Gerät als verifiziert markiert.",
@ -853,7 +716,7 @@
"device id: ": "Geräte-ID: ", "device id: ": "Geräte-ID: ",
"Device key:": "Geräte-Schlüssel:", "Device key:": "Geräte-Schlüssel:",
"Email address (optional)": "E-Mail-Adresse (optional)", "Email address (optional)": "E-Mail-Adresse (optional)",
"List this room in %(domain)s's room directory?": "Diesen Raum zum Raum-Verzeichnis von %(domain)s hinzufügen?", "Publish this room to the public in %(domain)s's room directory?": "Diesen Raum im Raum-Verzeichnis von %(domain)s veröffentlichen?",
"Mobile phone number (optional)": "Mobilfunknummer (optional)", "Mobile phone number (optional)": "Mobilfunknummer (optional)",
"Password:": "Passwort:", "Password:": "Passwort:",
"Register": "Registrieren", "Register": "Registrieren",
@ -861,7 +724,6 @@
"Setting a user name will create a fresh account": "Die Eingabe eines Benutzernamens wird ein neues Konto erzeugen", "Setting a user name will create a fresh account": "Die Eingabe eines Benutzernamens wird ein neues Konto erzeugen",
"Tagged as: ": "Markiert als: ", "Tagged as: ": "Markiert als: ",
"This Home Server does not support login using email address.": "Dieser Heimserver unterstützt den Login mittels E-Mail-Adresse nicht.", "This Home Server does not support login using email address.": "Dieser Heimserver unterstützt den Login mittels E-Mail-Adresse nicht.",
"There was a problem logging in.": "Beim Anmelden ist ein Problem aufgetreten.",
"Unknown (user, device) pair:": "Unbekanntes (Nutzer-/Gerät-)Paar:", "Unknown (user, device) pair:": "Unbekanntes (Nutzer-/Gerät-)Paar:",
"Remote addresses for this room:": "Remote-Adressen für diesen Raum:", "Remote addresses for this room:": "Remote-Adressen für diesen Raum:",
"Unrecognised command:": "Unbekannter Befehl:", "Unrecognised command:": "Unbekannter Befehl:",
@ -872,26 +734,23 @@
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNUNG: SCHLÜSSEL-VERIFIZIERUNG FEHLGESCHLAGEN! Der Signatur-Schlüssel für %(userId)s und das Gerät %(deviceId)s ist \"%(fprint)s\", welcher nicht mit dem bereitgestellten Schlüssel \"%(fingerprint)s\" übereinstimmt. Dies kann bedeuten, dass deine Kommunikation abgehört wird!", "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNUNG: SCHLÜSSEL-VERIFIZIERUNG FEHLGESCHLAGEN! Der Signatur-Schlüssel für %(userId)s und das Gerät %(deviceId)s ist \"%(fprint)s\", welcher nicht mit dem bereitgestellten Schlüssel \"%(fingerprint)s\" übereinstimmt. Dies kann bedeuten, dass deine Kommunikation abgehört wird!",
"You have <a>disabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>deaktiviert</a>.", "You have <a>disabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>deaktiviert</a>.",
"You have <a>enabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>aktiviert</a>.", "You have <a>enabled</a> URL previews by default.": "Du hast die URL-Vorschau standardmäßig <a>aktiviert</a>.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Du hast einen ungültigen Kontakt eingegeben. Versuche es mit der Matrix-Kennung oder der E-Mail-Adresse des Kontakts.",
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName hat das Raum-Bild geändert zu <img/>", "$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName hat das Raum-Bild geändert zu <img/>",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s hat das Raum-Bild für %(roomName)s geändert", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s hat das Raum-Bild für %(roomName)s geändert",
"Hide removed messages": "Gelöschte Nachrichten verbergen", "Hide removed messages": "Gelöschte Nachrichten verbergen",
"Start new chat": "Neuen Chat starten", "Start new chat": "Neuen Chat starten",
"Disable markdown formatting": "Markdown-Formatierung deaktivieren",
"Add": "Hinzufügen", "Add": "Hinzufügen",
"%(count)s new messages.one": "%(count)s neue Nachricht", "%(count)s new messages|one": "%(count)s neue Nachricht",
"%(count)s new messages.other": "%(count)s neue Nachrichten", "%(count)s new messages|other": "%(count)s neue Nachrichten",
"Error: Problem communicating with the given homeserver.": "Fehler: Problem bei der Kommunikation mit dem angegebenen Home-Server.", "Error: Problem communicating with the given homeserver.": "Fehler: Problem bei der Kommunikation mit dem angegebenen Home-Server.",
"Failed to fetch avatar URL": "Abrufen der Avatar-URL fehlgeschlagen", "Failed to fetch avatar URL": "Abrufen der Avatar-URL fehlgeschlagen",
"The phone number entered looks invalid": "Die eingegebene Telefonnummer scheint ungültig zu sein", "The phone number entered looks invalid": "Die eingegebene Telefonnummer scheint ungültig zu sein",
"This room is private or inaccessible to guests. You may be able to join if you register.": "Dieser Raum ist privat oder für Gäste nicht betretbar. Du kannst evtl. beitreten wenn du dich registrierst.", "This room is private or inaccessible to guests. You may be able to join if you register.": "Dieser Raum ist privat oder für Gäste nicht betretbar. Du kannst evtl. beitreten wenn du dich registrierst.",
"Uploading %(filename)s and %(count)s others.zero": "%(filename)s wird hochgeladen", "Uploading %(filename)s and %(count)s others|zero": "%(filename)s wird hochgeladen",
"Uploading %(filename)s and %(count)s others.one": "%(filename)s und %(count)s weitere Dateien werden hochgeladen", "Uploading %(filename)s and %(count)s others|one": "%(filename)s und %(count)s weitere Dateien werden hochgeladen",
"Uploading %(filename)s and %(count)s others.other": "%(filename)s und %(count)s weitere Dateien werden hochgeladen", "Uploading %(filename)s and %(count)s others|other": "%(filename)s und %(count)s weitere Dateien werden hochgeladen",
"You must <a>register</a> to use this functionality": "Du musst dich <a>registrieren</a>, um diese Funktionalität nutzen zu können", "You must <a>register</a> to use this functionality": "Du musst dich <a>registrieren</a>, um diese Funktionalität nutzen zu können",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Alle erneut senden</a> oder <a>alle verwerfen</a>. Du kannst auch einzelne Nachrichten erneut senden oder verwerfen.", "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Alle erneut senden</a> oder <a>alle verwerfen</a>. Du kannst auch einzelne Nachrichten erneut senden oder verwerfen.",
"Create new room": "Neuen Raum erstellen", "Create new room": "Neuen Raum erstellen",
"Welcome page": "Willkommensseite",
"Room directory": "Raum-Verzeichnis", "Room directory": "Raum-Verzeichnis",
"Start chat": "Chat starten", "Start chat": "Chat starten",
"New Password": "Neues Passwort", "New Password": "Neues Passwort",
@ -905,15 +764,15 @@
"Something went wrong!": "Etwas ging schief!", "Something went wrong!": "Etwas ging schief!",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "Dies wird dein zukünftiger Benutzername auf dem <span></span> Heimserver. Alternativ kannst du auch einen <a>anderen Server</a> auswählen.", "This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "Dies wird dein zukünftiger Benutzername auf dem <span></span> Heimserver. Alternativ kannst du auch einen <a>anderen Server</a> auswählen.",
"If you already have a Matrix account you can <a>log in</a> instead.": "Wenn du bereits ein Matrix-Benutzerkonto hast, kannst du dich stattdessen auch direkt <a>anmelden</a>.", "If you already have a Matrix account you can <a>log in</a> instead.": "Wenn du bereits ein Matrix-Benutzerkonto hast, kannst du dich stattdessen auch direkt <a>anmelden</a>.",
"Home": "Start", "Home": "Startseite",
"Username invalid: %(errMessage)s": "Ungültiger Benutzername: %(errMessage)s", "Username invalid: %(errMessage)s": "Ungültiger Benutzername: %(errMessage)s",
"a room": "einen Raum", "a room": "einen Raum",
"Accept": "Akzeptieren", "Accept": "Akzeptieren",
"Active call (%(roomName)s)": "Aktiver Anruf (%(roomName)s)", "Active call (%(roomName)s)": "Aktiver Anruf (%(roomName)s)",
"Admin tools": "Admin-Werkzeuge", "Admin Tools": "Admin-Werkzeuge",
"And %(count)s more...": "Und %(count)s weitere...", "And %(count)s more...": "Und %(count)s weitere...",
"Alias (optional)": "Alias (optional)", "Alias (optional)": "Alias (optional)",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Verbindung zum Heimserver fehlgeschlagen - bitte prüfe deine Verbindung, stelle sicher, dass dem <a>SSL-Zertifikat deines Heimservers</a> vertraut wird und keine Browser-Erweiterung Anfragen blockiert.", "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Verbindung zum Heimserver fehlgeschlagen - bitte überprüfe die Internetverbindung und stelle sicher, dass dem <a>SSL-Zertifikat deines Heimservers</a> vertraut wird und dass Anfragen nicht durch eine Browser-Erweiterung blockiert werden.",
"<a>Click here</a> to join the discussion!": "<a>Hier klicken</a>, um an der Diskussion teilzunehmen!", "<a>Click here</a> to join the discussion!": "<a>Hier klicken</a>, um an der Diskussion teilzunehmen!",
"Close": "Schließen", "Close": "Schließen",
"Custom": "Erweitert", "Custom": "Erweitert",
@ -939,13 +798,12 @@
"Room contains unknown devices": "Raum enthält unbekannte Geräte", "Room contains unknown devices": "Raum enthält unbekannte Geräte",
"%(roomName)s does not exist.": "%(roomName)s existert nicht.", "%(roomName)s does not exist.": "%(roomName)s existert nicht.",
"%(roomName)s is not accessible at this time.": "%(roomName)s ist aktuell nicht zugreifbar.", "%(roomName)s is not accessible at this time.": "%(roomName)s ist aktuell nicht zugreifbar.",
"Searching known users": "Suche nach bekannten Nutzern",
"Seen by %(userName)s at %(dateTime)s": "Gesehen von %(userName)s um %(dateTime)s", "Seen by %(userName)s at %(dateTime)s": "Gesehen von %(userName)s um %(dateTime)s",
"Send anyway": "Trotzdem senden", "Send anyway": "Trotzdem senden",
"Set": "Setze", "Set": "Setze",
"Start authentication": "Authentifizierung beginnen", "Start authentication": "Authentifizierung beginnen",
"Show Text Formatting Toolbar": "Text-Formatierungs-Werkzeugleiste anzeigen", "Show Text Formatting Toolbar": "Text-Formatierungs-Werkzeugleiste anzeigen",
"This invitation was sent to an email address which is not associated with this account:": "Diese Einledung wurde an eine E-Mail-Adresse gesendet, die nicht mit diesem Konto verknüpft ist:", "This invitation was sent to an email address which is not associated with this account:": "Diese Einladung wurde an eine E-Mail-Adresse gesendet, die nicht mit diesem Benutzerkonto verknüpft ist:",
"This room": "In diesem Raum", "This room": "In diesem Raum",
"To link to a room it must have <a>an address</a>.": "Um einen Raum zu verlinken, muss er <a>eine Adresse</a> haben.", "To link to a room it must have <a>an address</a>.": "Um einen Raum zu verlinken, muss er <a>eine Adresse</a> haben.",
"Undecryptable": "Nicht entschlüsselbar", "Undecryptable": "Nicht entschlüsselbar",
@ -959,12 +817,12 @@
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (Berechtigungslevel %(powerLevelNumber)s)", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (Berechtigungslevel %(powerLevelNumber)s)",
"Verified": "Verifiziert", "Verified": "Verifiziert",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Möchtest du diese Einladung <acceptText>akzeptieren</acceptText> oder <declineText>ablehnen</declineText>?", "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Möchtest du diese Einladung <acceptText>akzeptieren</acceptText> oder <declineText>ablehnen</declineText>?",
"You have been banned from %(roomName)s by %(userName)s.": "Du wurdest von %(userName)s dauerhaft aus dem Raum %(roomName)s ausgeschlossen.", "You have been banned from %(roomName)s by %(userName)s.": "Du wurdest von %(userName)s aus dem Raum %(roomName)s verbannt.",
"You have been kicked from %(roomName)s by %(userName)s.": "Du wurdest von %(userName)s aus dem Raum %(roomName)s entfernt.", "You have been kicked from %(roomName)s by %(userName)s.": "Du wurdest von %(userName)s aus dem Raum \"%(roomName)s\" gekickt.",
"You may wish to login with a different account, or add this email to this account.": "Du möchtest dich eventuell mit einem anderen Konto anmelden oder alternativ diese E-Mail-Adresse diesem Konto hinzufügen.", "You may wish to login with a different account, or add this email to this account.": "Du möchtest dich eventuell mit einem anderen Konto anmelden oder alternativ diese E-Mail-Adresse diesem Konto hinzufügen.",
"Your home server does not support device management.": "Dein Heimserver unterstützt kein Geräte-Management.", "Your home server does not support device management.": "Dein Heimserver unterstützt kein Geräte-Management.",
"(~%(count)s results).one": "(~%(count)s Ergebnis)", "(~%(count)s results)|one": "(~%(count)s Ergebnis)",
"(~%(count)s results).other": "(~%(count)s Ergebnis)", "(~%(count)s results)|other": "(~%(count)s Ergebnis)",
"Device Name": "Geräte-Name", "Device Name": "Geräte-Name",
"(could not connect media)": "(Medienverbindung konnte nicht hergestellt werden)", "(could not connect media)": "(Medienverbindung konnte nicht hergestellt werden)",
"(no answer)": "(keine Antwort)", "(no answer)": "(keine Antwort)",
@ -979,12 +837,73 @@
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "Du bist bislang keinem Raum beigetreten! Auf <CreateRoomButton> klicken, um einen Raum zu erstellen, oder auf <RoomDirectoryButton> klicken, um das Raum-Verzeichnis zu durchsuchen", "You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "Du bist bislang keinem Raum beigetreten! Auf <CreateRoomButton> klicken, um einen Raum zu erstellen, oder auf <RoomDirectoryButton> klicken, um das Raum-Verzeichnis zu durchsuchen",
"To return to your account in future you need to set a password": "Um in Zukunft auf dein Benutzerkonto zugreifen zu können, musst du ein Passwort einrichten", "To return to your account in future you need to set a password": "Um in Zukunft auf dein Benutzerkonto zugreifen zu können, musst du ein Passwort einrichten",
"Skip": "Überspringen", "Skip": "Überspringen",
"Start verification": "Verifikation starten", "Start verification": "Verifizierung starten",
"Share without verifying": "Ohne Verifizierung teilen", "Share without verifying": "Ohne Verifizierung verwenden",
"Ignore request": "Anfrage ignorieren", "Ignore request": "Anforderung ignorieren",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "Du hast das neue Gerät '%(displayName)s' hinzugefügt, welches nun Verschlüsselungs-Schlüssel anfragt.", "You added a new device '%(displayName)s', which is requesting encryption keys.": "Du hast das neue Gerät '%(displayName)s' hinzugefügt, welches nun Verschlüsselungs-Schlüssel anfordert.",
"Encryption key request": "Verschlüsselungs-Schlüssel-Anfrage", "Encryption key request": "Anforderung von Verschlüsselungs-Schlüsseln",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Dein nicht verifiziertes Gerät '%(displayName)s' fordert Verschlüsselungs-Schlüssel an.", "Your unverified device '%(displayName)s' is requesting encryption keys.": "Dein nicht verifiziertes Gerät '%(displayName)s' fordert Verschlüsselungs-Schlüssel an.",
"Updates": "Updates", "Updates": "Updates",
"Check for update": "Suche nach Updates" "Check for update": "Suche nach Updates",
"Add a widget": "Widget hinzufügen",
"Allow": "Erlauben",
"Changes colour scheme of current room": "Ändere Farbschema des aktuellen Raumes",
"Delete widget": "Widget entfernen",
"Define the power level of a user": "Setze das Berechtigungslevel eines Benutzers",
"Edit": "Bearbeiten",
"Enable automatic language detection for syntax highlighting": "Automatische Spracherkennung für die Syntax-Hervorhebung aktivieren",
"Hide Apps": "Apps verbergen",
"Hide join/leave messages (invites/kicks/bans unaffected)": "Verberge Beitritt-/Verlassen-Meldungen (außer Einladungen/Kicks/Bans)",
"Hide avatar and display name changes": "Verberge Avatar- und Anzeigenamen-Änderungen",
"Matrix Apps": "Matrix-Apps",
"Revoke widget access": "Ziehe Widget-Zugriff zurück",
"Sets the room topic": "Setzt das Raum-Thema",
"Show Apps": "Apps anzeigen",
"To get started, please pick a username!": "Um zu starten, wähle bitte einen Nutzernamen!",
"Unable to create widget.": "Widget kann nicht erstellt werden.",
"Unbans user with given id": "Verbannung aufheben für Benutzer mit angegebener ID",
"You are not in this room.": "Du bist nicht in diesem Raum.",
"You do not have permission to do that in this room.": "Du hast keine Berechtigung, dies in diesem Raum zu tun.",
"Verifies a user, device, and pubkey tuple": "Verifiziert ein Tupel aus Nutzer, Gerät und öffentlichem Schlüssel",
"Autocomplete Delay (ms):": "Verzögerung zur Autovervollständigung (ms):",
"This Home server does not support groups": "Dieser Heimserver unterstützt keine Gruppen",
"Loading device info...": "Lädt Geräte-Info...",
"Groups": "Gruppen",
"Create a new group": "Erstelle eine neue Gruppe",
"Create Group": "Erstelle Gruppe",
"Group Name": "Gruppenname",
"Example": "Beispiel",
"Create": "Erstelle",
"Group ID": "Gruppen-ID",
"+example:%(domain)s": "+beispiel:%(domain)s",
"Group IDs must be of the form +localpart:%(domain)s": "Gruppen-IDs müssen von der Form '+lokalteil:%(domain)s' sein",
"It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s": "Es ist aktuell nur möglich, Gruppen auf deinem eigenen Heimserver zu erstellen. Nutze eine Gruppen-ID, die mit %(domain)s endet",
"Room creation failed": "Raum-Erstellung fehlgeschlagen",
"You are a member of these groups:": "Du bist Mitglied in folgenden Gruppen:",
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Erstelle eine Gruppe um deine Community darzustellen! Definiere eine Menge von Räumen und deine eigene angepasste Startseite um deinen Bereich im Matrix-Universum zu markieren.",
"Join an existing group": "Trete eine existierenden Gruppe bei",
"To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "Um einer bereits vorhandenen Gruppe beitreten zu können, muss dir die Gruppen-Kennung bekannt sein. Diese sieht aus wie <i>+example:matrix.org</i>.",
"Featured Rooms:": "Hervorgehobene Räume:",
"Error whilst fetching joined groups": "Fehler beim Laden beigetretener Gruppen",
"Featured Users:": "Hervorgehobene Nutzer:",
"Edit Group": "Gruppe bearbeiten",
"Automatically replace plain text Emoji": "Klartext-Emoji automatisch ersetzen",
"Failed to upload image": "Bild-Hochladen fehlgeschlagen",
"Failed to update group": "Aktualisieren der Gruppe fehlgeschlagen",
"Hide avatars in user and room mentions": "Verberge Profilbilder in Benutzer- und Raum-Erwähnungen",
"AM": "a. m.",
"PM": "p. m.",
"The maximum permitted number of widgets have already been added to this room.": "Die maximal erlaubte Anzahl an hinzufügbaren Widgets für diesen Raum wurde erreicht.",
"Cannot add any more widgets": "Kann keine weiteren Widgets hinzufügen",
"Do you want to load widget from URL:": "Möchtest du das Widget von folgender URL laden:",
"Integrations Error": "Integrations-Error",
"NOTE: Apps are not end-to-end encrypted": "BEACHTE: Apps sind nicht Ende-zu-Ende verschlüsselt",
"%(widgetName)s widget added by %(senderName)s": "Widget \"%(widgetName)s\" von %(senderName)s hinzugefügt",
"%(widgetName)s widget removed by %(senderName)s": "Widget \"%(widgetName)s\" von %(senderName)s entfernt",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "In der Desktop-Version kann derzeit nicht geprüft werden, ob ein Benutzer ein Roboter ist. Bitte einen <a>Webbrowser</a> verwenden",
"%(widgetName)s widget modified by %(senderName)s": "Das Widget '%(widgetName)s' wurde von %(senderName)s bearbeitet",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s",
"%(weekDayName)s, %(monthName)s %(day)s": "%(weekDayName)s, %(day)s %(monthName)s",
"Copied!": "Kopiert!",
"Failed to copy": "Kopieren fehlgeschlagen"
} }

View file

@ -1,33 +1,14 @@
{ {
"af": "Αφρικάνικα",
"Error": "Σφάλμα", "Error": "Σφάλμα",
"Failed to forget room %(errCode)s": "Δεν ήταν δυνατή η διαγραφή του δωματίου (%(errCode)s)", "Failed to forget room %(errCode)s": "Δεν ήταν δυνατή η διαγραφή του δωματίου (%(errCode)s)",
"Failed to join the room": "Δεν ήταν δυνατή η σύνδεση στο δωμάτιο",
"Mute": "Σίγαση", "Mute": "Σίγαση",
"Notifications": "Ειδοποιήσεις", "Notifications": "Ειδοποιήσεις",
"Operation failed": "Η λειτουργία απέτυχε", "Operation failed": "Η λειτουργία απέτυχε",
"Please Register": "Παρακαλούμε εγγραφείτε",
"Remove": "Αφαίρεση", "Remove": "Αφαίρεση",
"Search": "Αναζήτηση", "Search": "Αναζήτηση",
"Settings": "Ρυθμίσεις", "Settings": "Ρυθμίσεις",
"unknown error code": "άγνωστος κωδικός σφάλματος", "unknown error code": "άγνωστος κωδικός σφάλματος",
"Sunday": "Κυριακή", "Sunday": "Κυριακή",
"ar-ae": "Αραβικά (Η.Α.Ε)",
"ar-bh": "Αραβικά (Μπαχρέιν)",
"ar-dz": "Αραβικά (Αλγερία)",
"ar-eg": "Αραβικά (Αίγυπτος)",
"ar-iq": "Αραβικά (Ιράκ)",
"ar-jo": "Αραβικά (Ιορδανία)",
"ar-kw": "Αραβικά (Κουβέιτ)",
"ar-lb": "Αραβικά (Λίβανος)",
"ar-ly": "Αραβικά (Λιβύη)",
"ar-ma": "Αραβικά (Μαρόκο)",
"ar-om": "Αραβικά (Ομάν)",
"ar-qa": "Αραβικά (Κατάρ)",
"ar-sa": "Αραβικά (Σαουδική Αραβία)",
"ar-sy": "Αραβικά (Συρία)",
"ar-tn": "Αραβικά (Τυνισία)",
"ar-ye": "Αραβικά (Υεμένη)",
"accept": "αποδοχή", "accept": "αποδοχή",
"%(targetName)s accepted an invitation.": "%(targetName)s δέχτηκε την πρόσκληση.", "%(targetName)s accepted an invitation.": "%(targetName)s δέχτηκε την πρόσκληση.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s δέχτηκες την πρόσκληση για %(displayName)s.", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s δέχτηκες την πρόσκληση για %(displayName)s.",
@ -60,125 +41,16 @@
"%(senderName)s banned %(targetName)s.": "Ο χρήστης %(senderName)s έδιωξε τον χρήστη %(targetName)s.", "%(senderName)s banned %(targetName)s.": "Ο χρήστης %(senderName)s έδιωξε τον χρήστη %(targetName)s.",
"Autoplay GIFs and videos": "Αυτόματη αναπαραγωγή GIFs και βίντεο", "Autoplay GIFs and videos": "Αυτόματη αναπαραγωγή GIFs και βίντεο",
"Bug Report": "Αναφορά σφάλματος", "Bug Report": "Αναφορά σφάλματος",
"anyone": "οποιοσδήποτε",
"Anyone who knows the room's link, apart from guests": "Oποιοσδήποτε", "Anyone who knows the room's link, apart from guests": "Oποιοσδήποτε",
"all room members, from the point they joined": "όλα τα μέλη, από τη στιγμή που συνδέθηκαν",
"%(items)s and %(lastItem)s": "%(items)s %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s %(lastItem)s",
"be": "Λευκορώσικα",
"bg": "Βουλγάρικα",
"ca": "Καταλανικά",
"cs": "Τσέχικα",
"da": "Δανέζικα",
"de-at": "Γερμανικά (Αυστρία)",
"de-ch": "Γερμανικά (Ελβετία)",
"de": "Γερμανικά",
"de-li": "Γερμανικά (Λιχτενστάιν)",
"de-lu": "Γερμανικά (Λουξεμβούργο)",
"el": "Ελληνικά",
"en-au": "Αγγλικά (Αυστραλία)",
"en-bz": "Αγγλικά (Μπελίζε)",
"en-ca": "Αγγλικά (Καναδάς)",
"en": "Αγγλικά",
"en-gb": "Αγγλικά (Ηνωμένο Βασίλειο)",
"en-ie": "Αγγλικά (Ιρλανδία)",
"en-jm": "Αγγλικά (Τζαμάικα)",
"en-nz": "Αγγλικά (Νέα Ζηλανδία)",
"en-tt": "Αγγλικά (Τρινιντάντ)",
"en-us": "Αγγλικά (Ηνωμένες Πολιτείες)",
"en-za": "Αγγλικά (Νότια Αφρική)",
"es-ar": "Ισπανικά (Αργεντινή)",
"es-bo": "Ισπανικά (Βολιβία)",
"es-cl": "Ισπανικά (Χιλή)",
"es-co": "Ισπανικά (Κολομβία)",
"es-cr": "Ισπανικά (Κόστα Ρίκα)",
"es-do": "Ισπανικά (Δομινικανή Δημοκρατία)",
"es-ec": "Ισπανικά (Ισημερινός)",
"es-gt": "Ισπανικά (Γουατεμάλα)",
"es-hn": "Ισπανικά (Ονδούρα)",
"es-mx": "Ισπανικά (Μεξικό)",
"es-ni": "Ισπανικά (Νικαράγουα)",
"es-pa": "Ισπανικά (Παναμάς)",
"es-pe": "Ισπανικά (Περού)",
"es-pr": "Ισπανικά (Πουέρτο Ρίκο)",
"es-py": "Ισπανικά (Παραγουάη)",
"es": "Ισπανικά (Ισπανία)",
"es-sv": "Ισπανικά (Ελ Σαλβαδόρ)",
"es-uy": "Ισπανικά (Ουρουγουάη)",
"es-ve": "Ισπανικά (Βενεζουέλα)",
"et": "Εσθονικά",
"eu": "Βασκική (Βασκική)",
"fa": "Φάρσι",
"fi": "Φινλανδικά",
"fo": "Φαρόε",
"fr-be": "Γαλλικά (Βέλγιο)",
"fr-ca": "Γαλλικά (Καναδάς)",
"fr-ch": "Γαλλικά (Ελβετία)",
"fr": "Γαλλικά",
"fr-lu": "Γαλλικά (Λουξεμβούργο)",
"ga": "Ιρλανδικά",
"gd": "Γαελική (Σκωτία)",
"he": "Εβραϊκά",
"hi": "Χίντι",
"hr": "Κροατικά",
"hu": "Ουγγρικά",
"is": "Ισλανδικά",
"it-ch": "Ιταλικά (Ελβετία)",
"it": "Ιταλικά",
"ja": "Ιαπωνικά",
"ji": "Γίντις",
"ko": "Κορεάτικα",
"lt": "Λιθουανικά",
"mk": "Μακεδονική (ΠΓΔΜ)",
"ms": "Μαλαισίας",
"mt": "Μαλτέζικα",
"nl-be": "Ολλανδικά (Βέλγιο)",
"nl": "Ολλανδικά",
"no": "Νορβηγικά",
"pl": "Πολωνέζικα",
"pt-br": "Πορτογαλικά Βραζιλίας",
"pt": "Πορτογαλικά",
"rm": "Ραιτορωμαϊκά",
"ro-mo": "Ρουμάνικα (Δημοκρατία της Μολδαβίας)",
"ro": "Ρουμάνικα",
"ru-mo": "Ρώσικα (Δημοκρατία της Μολδαβίας)",
"ru": "Ρώσικα",
"sb": "Σορβικά",
"sk": "Σλοβάκικα",
"sl": "Σλοβενικά",
"sq": "Αλβανικά",
"sr": "Σερβικά",
"sv-fi": "Σουηδικά (Φινλανδία)",
"sv": "Σουηδικά",
"sx": "Σούτου",
"sz": "Σάμη (Λαπωνικά)",
"th": "Ταϊλανδέζικα",
"tn": "Τσουάνα",
"tr": "Τουρκικά",
"ts": "Τσονγκά",
"uk": "Ουκρανικά",
"ur": "Ουρντού",
"ve": "Venda",
"vi": "Βιετναμέζικα",
"xh": "Ξόσα",
"zh-cn": "Κινέζικα (ΛΔΚ)",
"zh-hk": "Κινέζικα (ΕΔΠ Χονγκ Κονγκ)",
"zh-sg": "Κινέζικα (Σιγκαπούρη)",
"zh-tw": "Κινέζικα (Ταϊβάν)",
"zu": "Ζουλού",
"id": "Ινδονησιακά",
"lv": "Λετονικά",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Ένα μήνυμα στάλθηκε στο +%(msisdn)s. Παρακαλώ γράψε τον κωδικό επαλήθευσης που περιέχει", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Ένα μήνυμα στάλθηκε στο +%(msisdn)s. Παρακαλώ γράψε τον κωδικό επαλήθευσης που περιέχει",
"Access Token:": "Κωδικός πρόσβασης:", "Access Token:": "Κωδικός πρόσβασης:",
"Always show message timestamps": "Εμφάνιση πάντα της ένδειξης ώρας στα μηνύματα", "Always show message timestamps": "Εμφάνιση πάντα της ένδειξης ώρας στα μηνύματα",
"all room members": "όλα τα μέλη",
"all room members, from the point they are invited": "όλα τα μέλη, από τη στιγμή που προσκλήθηκαν",
"an address": "μία διεύθηνση", "an address": "μία διεύθηνση",
"%(items)s and %(remaining)s others": "%(items)s και %(remaining)s ακόμα", "%(items)s and %(remaining)s others": "%(items)s και %(remaining)s ακόμα",
"%(items)s and one other": "%(items)s και ένας ακόμα", "%(items)s and one other": "%(items)s και ένας ακόμα",
"and %(count)s others...": { "and %(count)s others...|one": "και ένας ακόμα...",
"other": "και %(count)s άλλοι...", "and %(count)s others...|other": "και %(count)s άλλοι...",
"one": "και ένας ακόμα..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s και %(lastPerson)s γράφουν", "%(names)s and %(lastPerson)s are typing": "%(names)s και %(lastPerson)s γράφουν",
"%(names)s and one other are typing": "%(names)s και ένας ακόμα γράφουν", "%(names)s and one other are typing": "%(names)s και ένας ακόμα γράφουν",
"%(names)s and %(count)s others are typing": "%(names)s και %(count)s άλλοι γράφουν", "%(names)s and %(count)s others are typing": "%(names)s και %(count)s άλλοι γράφουν",
@ -222,7 +94,6 @@
"device id: ": "αναγνωριστικό συσκευής: ", "device id: ": "αναγνωριστικό συσκευής: ",
"Device key:": "Κλειδί συσκευής:", "Device key:": "Κλειδί συσκευής:",
"Devices": "Συσκευές", "Devices": "Συσκευές",
"Direct Chat": "Απευθείας συνομιλία",
"Direct chats": "Απευθείας συνομιλίες", "Direct chats": "Απευθείας συνομιλίες",
"disabled": "ανενεργό", "disabled": "ανενεργό",
"Disinvite": "Ανάκληση πρόσκλησης", "Disinvite": "Ανάκληση πρόσκλησης",
@ -265,7 +136,6 @@
"For security, this session has been signed out. Please sign in again.": "Για λόγους ασφαλείας, αυτή η συνεδρία έχει τερματιστεί. Παρακαλούμε συνδεθείτε ξανά.", "For security, this session has been signed out. Please sign in again.": "Για λόγους ασφαλείας, αυτή η συνεδρία έχει τερματιστεί. Παρακαλούμε συνδεθείτε ξανά.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Για λόγους ασφαλείας, τα κλειδιά κρυπτογράφησης θα διαγράφονται από τον περιηγητή κατά την αποσύνδεση σας. Εάν επιθυμείτε να αποκρυπτογραφήσετε τις συνομιλίες σας στο μέλλον, εξάγετε τα κλειδιά σας και κρατήστε τα ασφαλή.", "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Για λόγους ασφαλείας, τα κλειδιά κρυπτογράφησης θα διαγράφονται από τον περιηγητή κατά την αποσύνδεση σας. Εάν επιθυμείτε να αποκρυπτογραφήσετε τις συνομιλίες σας στο μέλλον, εξάγετε τα κλειδιά σας και κρατήστε τα ασφαλή.",
"Found a bug?": "Βρήκατε κάποιο πρόβλημα;", "Found a bug?": "Βρήκατε κάποιο πρόβλημα;",
"Guest users can't upload files. Please register to upload.": "Οι επισκέπτες δεν μπορούν να ανεβάσουν αρχεία. Παρακαλώ εγγραφείτε πρώτα.",
"had": "είχε", "had": "είχε",
"Hangup": "Κλείσιμο", "Hangup": "Κλείσιμο",
"Historical": "Ιστορικό", "Historical": "Ιστορικό",
@ -297,7 +167,6 @@
"left": "έφυγε", "left": "έφυγε",
"%(targetName)s left the room.": "Ο χρήστης %(targetName)s έφυγε από το δωμάτιο.", "%(targetName)s left the room.": "Ο χρήστης %(targetName)s έφυγε από το δωμάτιο.",
"Level": "Επίπεδο", "Level": "Επίπεδο",
"List this room in %(domain)s's room directory?": "Να εμφανίζεται το δωμάτιο στο γενικό ευρετήριο του διακομιστή %(domain)s;",
"Local addresses for this room:": "Τοπική διεύθυνση για το δωμάτιο:", "Local addresses for this room:": "Τοπική διεύθυνση για το δωμάτιο:",
"Logged in as:": "Συνδεθήκατε ως:", "Logged in as:": "Συνδεθήκατε ως:",
"Login as guest": "Σύνδεση ως επισκέπτης", "Login as guest": "Σύνδεση ως επισκέπτης",
@ -387,12 +256,11 @@
"Create new room": "Δημιουργία νέου δωματίου", "Create new room": "Δημιουργία νέου δωματίου",
"Room directory": "Ευρετήριο", "Room directory": "Ευρετήριο",
"Start chat": "Έναρξη συνομιλίας", "Start chat": "Έναρξη συνομιλίας",
"Welcome page": "Αρχική σελίδα",
"a room": "ένα δωμάτιο", "a room": "ένα δωμάτιο",
"Accept": "Αποδοχή", "Accept": "Αποδοχή",
"Active call (%(roomName)s)": "Ενεργή κλήση (%(roomName)s)", "Active call (%(roomName)s)": "Ενεργή κλήση (%(roomName)s)",
"Add": "Προσθήκη", "Add": "Προσθήκη",
"Admin tools": "Εργαλεία διαχειριστή", "Admin Tools": "Εργαλεία διαχειριστή",
"And %(count)s more...": "Και %(count)s περισσότερα...", "And %(count)s more...": "Και %(count)s περισσότερα...",
"No media permissions": "Χωρίς δικαιώματα πολυμέσων", "No media permissions": "Χωρίς δικαιώματα πολυμέσων",
"Alias (optional)": "Ψευδώνυμο (προαιρετικό)", "Alias (optional)": "Ψευδώνυμο (προαιρετικό)",
@ -406,12 +274,11 @@
"click to reveal": "κάντε κλικ για εμφάνιση", "click to reveal": "κάντε κλικ για εμφάνιση",
"Click to unmute video": "Κάντε κλικ για άρση σίγασης του βίντεο", "Click to unmute video": "Κάντε κλικ για άρση σίγασης του βίντεο",
"Click to unmute audio": "Κάντε κλικ για άρση σίγασης του ήχου", "Click to unmute audio": "Κάντε κλικ για άρση σίγασης του ήχου",
"%(count)s new messages.one": "%(count)s νέο μήνυμα", "%(count)s new messages|one": "%(count)s νέο μήνυμα",
"%(count)s new messages.other": "%(count)s νέα μηνύματα", "%(count)s new messages|other": "%(count)s νέα μηνύματα",
"Custom": "Προσαρμοσμένο", "Custom": "Προσαρμοσμένο",
"Decline": "Απόρριψη", "Decline": "Απόρριψη",
"Disable Notifications": "Απενεργοποίηση ειδοποιήσεων", "Disable Notifications": "Απενεργοποίηση ειδοποιήσεων",
"Disable markdown formatting": "Απενεργοποίηση μορφοποίησης markdown",
"Drop File Here": "Αποθέστε εδώ το αρχείο", "Drop File Here": "Αποθέστε εδώ το αρχείο",
"Enable Notifications": "Ενεργοποίηση ειδοποιήσεων", "Enable Notifications": "Ενεργοποίηση ειδοποιήσεων",
"Encryption is enabled in this room": "Η κρυπτογράφηση είναι ενεργοποιημένη σε αυτό το δωμάτιο", "Encryption is enabled in this room": "Η κρυπτογράφηση είναι ενεργοποιημένη σε αυτό το δωμάτιο",
@ -455,7 +322,6 @@
"Room %(roomId)s not visible": "Το δωμάτιο %(roomId)s δεν είναι ορατό", "Room %(roomId)s not visible": "Το δωμάτιο %(roomId)s δεν είναι ορατό",
"%(roomName)s does not exist.": "Το %(roomName)s δεν υπάρχει.", "%(roomName)s does not exist.": "Το %(roomName)s δεν υπάρχει.",
"Searches DuckDuckGo for results": "Γίνεται αναζήτηση στο DuckDuckGo για αποτελέσματα", "Searches DuckDuckGo for results": "Γίνεται αναζήτηση στο DuckDuckGo για αποτελέσματα",
"Searching known users": "Αναζήτηση γνωστών χρηστών",
"Seen by %(userName)s at %(dateTime)s": "Διαβάστηκε από %(userName)s στις %(dateTime)s", "Seen by %(userName)s at %(dateTime)s": "Διαβάστηκε από %(userName)s στις %(dateTime)s",
"Send anyway": "Αποστολή ούτως ή άλλως", "Send anyway": "Αποστολή ούτως ή άλλως",
"Send Invites": "Αποστολή προσκλήσεων", "Send Invites": "Αποστολή προσκλήσεων",
@ -472,7 +338,6 @@
"The main address for this room is": "Η κύρια διεύθυνση για το δωμάτιο είναι", "The main address for this room is": "Η κύρια διεύθυνση για το δωμάτιο είναι",
"%(actionVerb)s this person?": "%(actionVerb)s αυτού του ατόμου;", "%(actionVerb)s this person?": "%(actionVerb)s αυτού του ατόμου;",
"The file '%(fileName)s' failed to upload": "Απέτυχε η αποστολή του αρχείου '%(fileName)s'", "The file '%(fileName)s' failed to upload": "Απέτυχε η αποστολή του αρχείου '%(fileName)s'",
"There was a problem logging in.": "Υπήρξε ένα πρόβλημα κατά την σύνδεση.",
"This room has no local addresses": "Αυτό το δωμάτιο δεν έχει τοπικές διευθύνσεις", "This room has no local addresses": "Αυτό το δωμάτιο δεν έχει τοπικές διευθύνσεις",
"This doesn't appear to be a valid email address": "Δεν μοιάζει με μια έγκυρη διεύθυνση ηλεκτρονικής αλληλογραφίας", "This doesn't appear to be a valid email address": "Δεν μοιάζει με μια έγκυρη διεύθυνση ηλεκτρονικής αλληλογραφίας",
"This phone number is already in use": "Αυτός ο αριθμός τηλεφώνου είναι ήδη σε χρήση", "This phone number is already in use": "Αυτός ο αριθμός τηλεφώνου είναι ήδη σε χρήση",
@ -491,7 +356,6 @@
"Turn Markdown on": "Ενεργοποίηση Markdown", "Turn Markdown on": "Ενεργοποίηση Markdown",
"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 verify email address.": "Αδυναμία επιβεβαίωσης διεύθυνσης ηλεκτρονικής αλληλογραφίας.", "Unable to verify email address.": "Αδυναμία επιβεβαίωσης διεύθυνσης ηλεκτρονικής αλληλογραφίας.",
"Unban": "Άρση αποκλεισμού", "Unban": "Άρση αποκλεισμού",
"%(senderName)s unbanned %(targetName)s.": "Ο χρήστης %(senderName)s έδιωξε τον χρήστη %(targetName)s.", "%(senderName)s unbanned %(targetName)s.": "Ο χρήστης %(senderName)s έδιωξε τον χρήστη %(targetName)s.",
@ -503,7 +367,6 @@
"unknown caller": "άγνωστος καλών", "unknown caller": "άγνωστος καλών",
"unknown device": "άγνωστη συσκευή", "unknown device": "άγνωστη συσκευή",
"Unknown room %(roomId)s": "Άγνωστο δωμάτιο %(roomId)s", "Unknown room %(roomId)s": "Άγνωστο δωμάτιο %(roomId)s",
"unknown": "άγνωστο",
"Unmute": "Άρση σίγασης", "Unmute": "Άρση σίγασης",
"Unnamed Room": "Ανώνυμο δωμάτιο", "Unnamed Room": "Ανώνυμο δωμάτιο",
"Unrecognised command:": "Μη αναγνωρίσιμη εντολή:", "Unrecognised command:": "Μη αναγνωρίσιμη εντολή:",
@ -556,7 +419,6 @@
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Ορισμός ονόματος εμφάνισης:", "Set a display name:": "Ορισμός ονόματος εμφάνισης:",
"Set a Display Name": "Ορισμός ονόματος εμφάνισης",
"Upload an avatar:": "Αποστολή προσωπικής εικόνας:", "Upload an avatar:": "Αποστολή προσωπικής εικόνας:",
"This server does not support authentication with a phone number.": "Αυτός ο διακομιστής δεν υποστηρίζει πιστοποίηση με αριθμό τηλεφώνου.", "This server does not support authentication with a phone number.": "Αυτός ο διακομιστής δεν υποστηρίζει πιστοποίηση με αριθμό τηλεφώνου.",
"Missing password.": "Λείπει ο κωδικός πρόσβασης.", "Missing password.": "Λείπει ο κωδικός πρόσβασης.",
@ -569,9 +431,8 @@
"Make Moderator": "Ορισμός συντονιστή", "Make Moderator": "Ορισμός συντονιστή",
"Encrypt room": "Κρυπτογράφηση δωματίου", "Encrypt room": "Κρυπτογράφηση δωματίου",
"Room": "Δωμάτιο", "Room": "Δωμάτιο",
"Auto-complete": "Αυτόματη συμπλήρωση", "(~%(count)s results)|one": "(~%(count)s αποτέλεσμα)",
"(~%(count)s results).one": "(~%(count)s αποτέλεσμα)", "(~%(count)s results)|other": "(~%(count)s αποτελέσματα)",
"(~%(count)s results).other": "(~%(count)s αποτελέσματα)",
"Active call": "Ενεργή κλήση", "Active call": "Ενεργή κλήση",
"strike": "επιγράμμιση", "strike": "επιγράμμιση",
"bullet": "κουκκίδα", "bullet": "κουκκίδα",
@ -595,7 +456,6 @@
"Import room keys": "Εισαγωγή κλειδιών δωματίου", "Import room keys": "Εισαγωγή κλειδιών δωματίου",
"File to import": "Αρχείο για εισαγωγή", "File to import": "Αρχείο για εισαγωγή",
"Start new chat": "Έναρξη νέας συνομιλίας", "Start new chat": "Έναρξη νέας συνομιλίας",
"Guest users can't invite users. Please register.": "Οι επισκέπτες δεν έχουν τη δυνατότητα να προσκαλέσουν άλλους χρήστες. Παρακαλούμε εγγραφείτε πρώτα.",
"Confirm Removal": "Επιβεβαίωση αφαίρεσης", "Confirm Removal": "Επιβεβαίωση αφαίρεσης",
"Unknown error": "Άγνωστο σφάλμα", "Unknown error": "Άγνωστο σφάλμα",
"Incorrect password": "Λανθασμένος κωδικός πρόσβασης", "Incorrect password": "Λανθασμένος κωδικός πρόσβασης",
@ -659,9 +519,6 @@
"Failed to unban": "Δεν ήταν δυνατή η άρση του αποκλεισμού", "Failed to unban": "Δεν ήταν δυνατή η άρση του αποκλεισμού",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s από %(fromPowerLevel)s σε %(toPowerLevel)s", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s από %(fromPowerLevel)s σε %(toPowerLevel)s",
"Guest access is disabled on this Home Server.": "Έχει απενεργοποιηθεί η πρόσβαση στους επισκέπτες σε αυτόν τον διακομιστή.", "Guest access is disabled on this Home Server.": "Έχει απενεργοποιηθεί η πρόσβαση στους επισκέπτες σε αυτόν τον διακομιστή.",
"Guests can't set avatars. Please register.": "Οι επισκέπτες δεν μπορούν να ορίσουν προσωπικές εικόνες. Παρακαλούμε εγγραφείτε.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Οι επισκέπτες δεν μπορούν να δημιουργήσουν νέα δωμάτια. Παρακαλούμε εγγραφείτε για να δημιουργήσετε ένα δωμάτιο και να ξεκινήσετε μια συνομιλία.",
"Guests can't use labs features. Please register.": "Οι επισκέπτες δεν μπορούν να χρησιμοποιήσουν τα πειραματικά χαρακτηριστικά. Παρακαλούμε εγγραφείτε.",
"Guests cannot join this room even if explicitly invited.": "Οι επισκέπτες δεν μπορούν να συνδεθούν στο δωμάτιο ακόμη και αν έχουν καλεστεί.", "Guests cannot join this room even if explicitly invited.": "Οι επισκέπτες δεν μπορούν να συνδεθούν στο δωμάτιο ακόμη και αν έχουν καλεστεί.",
"Hide Text Formatting Toolbar": "Απόκρυψη εργαλειοθήκης μορφοποίησης κειμένου", "Hide Text Formatting Toolbar": "Απόκρυψη εργαλειοθήκης μορφοποίησης κειμένου",
"Incoming call from %(name)s": "Εισερχόμενη κλήση από %(name)s", "Incoming call from %(name)s": "Εισερχόμενη κλήση από %(name)s",
@ -674,14 +531,17 @@
"Invites user with given id to current room": "Προσκαλεί τον χρήστη με το δοσμένο αναγνωριστικό στο τρέχον δωμάτιο", "Invites user with given id to current room": "Προσκαλεί τον χρήστη με το δοσμένο αναγνωριστικό στο τρέχον δωμάτιο",
"'%(alias)s' is not a valid format for an address": "Το '%(alias)s' δεν είναι μια έγκυρη μορφή διεύθυνσης", "'%(alias)s' is not a valid format for an address": "Το '%(alias)s' δεν είναι μια έγκυρη μορφή διεύθυνσης",
"'%(alias)s' is not a valid format for an alias": "Το '%(alias)s' δεν είναι μια έγκυρη μορφή ψευδώνυμου", "'%(alias)s' is not a valid format for an alias": "Το '%(alias)s' δεν είναι μια έγκυρη μορφή ψευδώνυμου",
"%(senderName)s made future room history visible to": "Ο %(senderName)s έκανε το μελλοντικό ιστορικό του δωματίου δημόσιο", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "Ο %(senderName)s έκανε το μελλοντικό ιστορικό του δωματίου δημόσιο όλα τα μέλη, από τη στιγμή που προσκλήθηκαν.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "Ο %(senderName)s έκανε το μελλοντικό ιστορικό του δωματίου δημόσιο όλα τα μέλη, από τη στιγμή που συνδέθηκαν.",
"%(senderName)s made future room history visible to all room members.": "Ο %(senderName)s έκανε το μελλοντικό ιστορικό του δωματίου δημόσιο όλα τα μέλη.",
"%(senderName)s made future room history visible to anyone.": "Ο %(senderName)s έκανε το μελλοντικό ιστορικό του δωματίου δημόσιο οποιοσδήποτε.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "Ο %(senderName)s έκανε το μελλοντικό ιστορικό του δωματίου δημόσιο άγνωστο (%(visibility)s).",
"Missing user_id in request": "Λείπει το user_id στο αίτημα", "Missing user_id in request": "Λείπει το user_id στο αίτημα",
"Mobile phone number (optional)": "Αριθμός κινητού τηλεφώνου (προαιρετικό)", "Mobile phone number (optional)": "Αριθμός κινητού τηλεφώνου (προαιρετικό)",
"Must be viewing a room": "Πρέπει να βλέπετε ένα δωμάτιο", "Must be viewing a room": "Πρέπει να βλέπετε ένα δωμάτιο",
"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 Composer & Autocomplete": "Νέος επεξεργαστής κειμένου και αυτόματη συμπλήρωση",
"not set": "δεν έχει οριστεί", "not set": "δεν έχει οριστεί",
"not specified": "μη καθορισμένο", "not specified": "μη καθορισμένο",
"NOT verified": "ΧΩΡΙΣ επαλήθευση", "NOT verified": "ΧΩΡΙΣ επαλήθευση",
@ -689,12 +549,11 @@
"No display name": "Χωρίς όνομα", "No display name": "Χωρίς όνομα",
"No users have specific privileges in this room": "Κανένας χρήστης δεν έχει συγκεκριμένα δικαιώματα σε αυτό το δωμάτιο", "No users have specific privileges in this room": "Κανένας χρήστης δεν έχει συγκεκριμένα δικαιώματα σε αυτό το δωμάτιο",
"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've followed the link it contains, click below": "Μόλις ακολουθήσετε τον σύνδεσμο που περιέχει, κάντε κλικ παρακάτω",
"Only people who have been invited": "Μόνο άτομα που έχουν προσκληθεί", "Only people who have been invited": "Μόνο άτομα που έχουν προσκληθεί",
"Otherwise, <a>click here</a> to send a bug report.": "Διαφορετικά, κάντε <a>κλικ εδώ</a> για να αποστείλετε μια αναφορά σφάλματος.", "Otherwise, <a>click here</a> to send a bug report.": "Διαφορετικά, κάντε <a>κλικ εδώ</a> για να αποστείλετε μια αναφορά σφάλματος.",
"%(senderName)s placed a %(callType)s call.": "Ο %(senderName)s πραγματοποίησε μια %(callType)s κλήση.", "%(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.": "Παρακαλούμε ελέγξτε την ηλεκτρονική σας αλληλογραφία και κάντε κλικ στον σύνδεσμο που περιέχει. Μόλις γίνει αυτό, κάντε κλίκ στο κουμπί συνέχεια.", "Please check your email and click on the link it contains. Once this is done, click continue.": "Παρακαλούμε ελέγξτε την ηλεκτρονική σας αλληλογραφία και κάντε κλικ στον σύνδεσμο που περιέχει. Μόλις γίνει αυτό, κάντε κλίκ στο κουμπί συνέχεια.",
"Press": "Πατήστε",
"Refer a friend to Riot:": "Πείτε για το Riot σε έναν φίλο σας:", "Refer a friend to Riot:": "Πείτε για το Riot σε έναν φίλο σας:",
"Rejoin": "Επανασύνδεση", "Rejoin": "Επανασύνδεση",
"%(senderName)s removed their profile picture.": "Ο %(senderName)s αφαίρεσε τη φωτογραφία του προφίλ του.", "%(senderName)s removed their profile picture.": "Ο %(senderName)s αφαίρεσε τη φωτογραφία του προφίλ του.",
@ -720,8 +579,8 @@
"to start a chat with someone": "για να ξεκινήσετε μια συνομιλία με κάποιον", "to start a chat with someone": "για να ξεκινήσετε μια συνομιλία με κάποιον",
"Unable to capture screen": "Αδυναμία σύλληψης οθόνης", "Unable to capture screen": "Αδυναμία σύλληψης οθόνης",
"Unknown (user, device) pair:": "Άγνωστο ζεύγος (χρήστη, συσκευής):", "Unknown (user, device) pair:": "Άγνωστο ζεύγος (χρήστη, συσκευής):",
"Uploading %(filename)s and %(count)s others.zero": "Γίνεται αποστολή του %(filename)s", "Uploading %(filename)s and %(count)s others|zero": "Γίνεται αποστολή του %(filename)s",
"Uploading %(filename)s and %(count)s others.other": "Γίνεται αποστολή του %(filename)s και %(count)s υπολοίπων", "Uploading %(filename)s and %(count)s others|other": "Γίνεται αποστολή του %(filename)s και %(count)s υπολοίπων",
"uploaded a file": "ανέβασε ένα αρχείο", "uploaded a file": "ανέβασε ένα αρχείο",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (δύναμη %(powerLevelNumber)s)", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (δύναμη %(powerLevelNumber)s)",
"Verification Pending": "Εκκρεμεί επιβεβαίωση", "Verification Pending": "Εκκρεμεί επιβεβαίωση",
@ -802,10 +661,8 @@
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "Ο %(senderName)s έστειλε μια πρόσκληση στον %(targetDisplayName)s για να συνδεθεί στο δωμάτιο.", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "Ο %(senderName)s έστειλε μια πρόσκληση στον %(targetDisplayName)s για να συνδεθεί στο δωμάτιο.",
"%(senderName)s set their display name to %(displayName)s.": "Ο %(senderName)s όρισε το όνομα του σε %(displayName)s.", "%(senderName)s set their display name to %(displayName)s.": "Ο %(senderName)s όρισε το όνομα του σε %(displayName)s.",
"Sorry, this homeserver is using a login which is not recognised ": "Συγγνώμη, ο διακομιστής χρησιμοποιεί έναν τρόπο σύνδεσης που δεν αναγνωρίζεται ", "Sorry, this homeserver is using a login which is not recognised ": "Συγγνώμη, ο διακομιστής χρησιμοποιεί έναν τρόπο σύνδεσης που δεν αναγνωρίζεται ",
"tag as %(tagName)s": "ετικέτα ως %(tagName)s",
"tag direct chat": "προσθήκη ετικέτας στην απευθείας συνομιλία", "tag direct chat": "προσθήκη ετικέτας στην απευθείας συνομιλία",
"The phone number entered looks invalid": "Ο αριθμός που καταχωρίσατε δεν είναι έγκυρος", "The phone number entered looks invalid": "Ο αριθμός που καταχωρίσατε δεν είναι έγκυρος",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Αυτή η ενέργεια δεν μπορεί να εκτελεστεί από έναν επισκέπτη. Παρακαλούμε εγγραφείτε για να μπορέσετε να το κάνετε.",
"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": "Το αρχείο '%(fileName)s' υπερβαίνει το όριο μεγέθους του διακομιστή για αποστολές", "The file '%(fileName)s' exceeds this home server's size limit for uploads": "Το αρχείο '%(fileName)s' υπερβαίνει το όριο μεγέθους του διακομιστή για αποστολές",
"The remote side failed to pick up": "Η απομακρυσμένη πλευρά απέτυχε να συλλέξει", "The remote side failed to pick up": "Η απομακρυσμένη πλευρά απέτυχε να συλλέξει",
@ -817,11 +674,10 @@
"This room is not accessible by remote Matrix servers": "Αυτό το δωμάτιο δεν είναι προσβάσιμο από απομακρυσμένους διακομιστές Matrix", "This room is not accessible by remote Matrix servers": "Αυτό το δωμάτιο δεν είναι προσβάσιμο από απομακρυσμένους διακομιστές Matrix",
"to demote": "για υποβίβαση", "to demote": "για υποβίβαση",
"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 tag as %(tagName)s": "για να οριστεί ετικέτα ως %(tagName)s",
"to tag direct chat": "για να οριστεί ετικέτα σε απευθείας συνομιλία", "to tag direct chat": "για να οριστεί ετικέτα σε απευθείας συνομιλία",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "Ο %(senderName)s ενεργοποίησε την από άκρο σε άκρο κρυπτογράφηση (algorithm %(algorithm)s).", "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "Ο %(senderName)s ενεργοποίησε την από άκρο σε άκρο κρυπτογράφηση (algorithm %(algorithm)s).",
"Undecryptable": "Μη αποκρυπτογραφημένο", "Undecryptable": "Μη αποκρυπτογραφημένο",
"Uploading %(filename)s and %(count)s others.one": "Γίνεται αποστολή του %(filename)s και %(count)s υπολοίπα", "Uploading %(filename)s and %(count)s others|one": "Γίνεται αποστολή του %(filename)s και %(count)s υπολοίπα",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Θα θέλατε να <acceptText>δεχθείτε</acceptText> ή να <declineText>απορρίψετε</declineText> την πρόσκληση;", "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Θα θέλατε να <acceptText>δεχθείτε</acceptText> ή να <declineText>απορρίψετε</declineText> την πρόσκληση;",
"You already have existing direct chats with this user:": "Έχετε ήδη απευθείας συνομιλίες με τον χρήστη:", "You already have existing direct chats with this user:": "Έχετε ήδη απευθείας συνομιλίες με τον χρήστη:",
"You are trying to access %(roomName)s.": "Προσπαθείτε να έχετε πρόσβαση στο %(roomName)s.", "You are trying to access %(roomName)s.": "Προσπαθείτε να έχετε πρόσβαση στο %(roomName)s.",
@ -829,7 +685,6 @@
"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 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 <a>disabled</a> URL previews by default.": "Έχετε <a>απενεργοποιημένη</a> από προεπιλογή την προεπισκόπηση συνδέσμων.", "You have <a>disabled</a> URL previews by default.": "Έχετε <a>απενεργοποιημένη</a> από προεπιλογή την προεπισκόπηση συνδέσμων.",
"You have <a>enabled</a> URL previews by default.": "Έχετε <a>ενεργοποιημένη</a> από προεπιλογή την προεπισκόπηση συνδέσμων.", "You have <a>enabled</a> URL previews by default.": "Έχετε <a>ενεργοποιημένη</a> από προεπιλογή την προεπισκόπηση συνδέσμων.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Έχετε πληκτρολογήσει μια άκυρη επαφή. Χρησιμοποιήστε το Matrix ID ή την ηλεκτρονική διεύθυνση αλληλογραφίας τους.",
"You may wish to login with a different account, or add this email to this account.": "Μπορεί να θέλετε να συνδεθείτε με διαφορετικό λογαριασμό, ή να προσθέσετε αυτή τη διεύθυνση ηλεκτρονικής αλληλογραφίας σε αυτόν τον λογαριασμό.", "You may wish to login with a different account, or add this email to this account.": "Μπορεί να θέλετε να συνδεθείτε με διαφορετικό λογαριασμό, ή να προσθέσετε αυτή τη διεύθυνση ηλεκτρονικής αλληλογραφίας σε αυτόν τον λογαριασμό.",
"You need to be able to invite users to do that.": "Για να το κάνετε αυτό πρέπει να έχετε τη δυνατότητα να προσκαλέσετε χρήστες.", "You need to be able to invite users to do that.": "Για να το κάνετε αυτό πρέπει να έχετε τη δυνατότητα να προσκαλέσετε χρήστες.",
"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?": "Φαίνεται ότι αποστέλετε αρχεία, είστε βέβαιοι ότι θέλετε να αποχωρήσετε;",
@ -850,7 +705,6 @@
"You must join the room to see its files": "Πρέπει να συνδεθείτε στο δωμάτιο για να δείτε τα αρχεία του", "You must join the room to see its files": "Πρέπει να συνδεθείτε στο δωμάτιο για να δείτε τα αρχεία του",
"Reject all %(invitedRooms)s invites": "Απόρριψη όλων των προσκλήσεων %(invitedRooms)s", "Reject all %(invitedRooms)s invites": "Απόρριψη όλων των προσκλήσεων %(invitedRooms)s",
"Failed to invite the following users to the %(roomName)s room:": "Δεν ήταν δυνατή η πρόσκληση των χρηστών στο δωμάτιο %(roomName)s:", "Failed to invite the following users to the %(roomName)s room:": "Δεν ήταν δυνατή η πρόσκληση των χρηστών στο δωμάτιο %(roomName)s:",
"changing room on a RoomView is not supported": "Δεν υποστηρίζεται η αλλαγή δωματίου σε RoomView",
"demote": "υποβίβαση", "demote": "υποβίβαση",
"Deops user with given id": "Deop χρήστη με το συγκεκριμένο αναγνωριστικό", "Deops user with given id": "Deop χρήστη με το συγκεκριμένο αναγνωριστικό",
"Disable inline URL previews by default": "Απενεργοποίηση προεπισκόπησης συνδέσμων από προεπιλογή", "Disable inline URL previews by default": "Απενεργοποίηση προεπισκόπησης συνδέσμων από προεπιλογή",
@ -904,7 +758,6 @@
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Εάν ταιριάζει, πατήστε το κουμπί επιβεβαίωσης παρακάτω. Εάν όχι, τότε κάποιος άλλος παρακολουθεί αυτή τη συσκευή και ίσως θέλετε να πατήσετε το κουμπί της μαύρης λίστας.", "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Εάν ταιριάζει, πατήστε το κουμπί επιβεβαίωσης παρακάτω. Εάν όχι, τότε κάποιος άλλος παρακολουθεί αυτή τη συσκευή και ίσως θέλετε να πατήσετε το κουμπί της μαύρης λίστας.",
"We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Παρουσιάστηκε ένα σφάλμα κατά την προσπάθεια επαναφοράς της προηγούμενης συνεδρίας. Αν συνεχίσετε, θα χρειαστεί να συνδεθείτε ξανά και το κρυπτογραφημένο ιστορικό συνομιλιών θα είναι μη αναγνώσιμο.", "We encountered an error trying to restore your previous session. If you continue, you will need to log in again, and encrypted chat history will be unreadable.": "Παρουσιάστηκε ένα σφάλμα κατά την προσπάθεια επαναφοράς της προηγούμενης συνεδρίας. Αν συνεχίσετε, θα χρειαστεί να συνδεθείτε ξανά και το κρυπτογραφημένο ιστορικό συνομιλιών θα είναι μη αναγνώσιμο.",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Αν χρησιμοποιούσατε προηγουμένως μια πιο πρόσφατη έκδοση του Riot, η συνεδρία σας ίσως είναι μη συμβατή με αυτήν την έκδοση. Κλείστε αυτό το παράθυρο και επιστρέψτε στην πιο πρόσφατη έκδοση.", "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Αν χρησιμοποιούσατε προηγουμένως μια πιο πρόσφατη έκδοση του Riot, η συνεδρία σας ίσως είναι μη συμβατή με αυτήν την έκδοση. Κλείστε αυτό το παράθυρο και επιστρέψτε στην πιο πρόσφατη έκδοση.",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Το εμφανιζόμενο όνομα είναι το πως θα εμφανιστείτε σε άλλους όταν μιλάτε σε δωμάτια. Τι θα θέλατε να είναι;",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Αυτήν τη στιγμή βάζετε σε μαύρη λίστα μη επιβαιωμένες συσκευές. Για να στείλετε μηνύματα σε αυτές τις συσκευές, πρέπει να τις επιβεβαιώσετε.", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Αυτήν τη στιγμή βάζετε σε μαύρη λίστα μη επιβαιωμένες συσκευές. Για να στείλετε μηνύματα σε αυτές τις συσκευές, πρέπει να τις επιβεβαιώσετε.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Σας συνιστούμε να ολοκληρώσετε τη διαδικασία επαλήθευσης για κάθε συσκευή και να επιβεβαιώσετε ότι ανήκουν στον νόμιμο κάτοχό της, αλλά εάν προτιμάτε μπορείτε να στείλετε ξανά το μήνυμα χωρίς επαλήθευση.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Σας συνιστούμε να ολοκληρώσετε τη διαδικασία επαλήθευσης για κάθε συσκευή και να επιβεβαιώσετε ότι ανήκουν στον νόμιμο κάτοχό της, αλλά εάν προτιμάτε μπορείτε να στείλετε ξανά το μήνυμα χωρίς επαλήθευση.",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Θα μεταφερθείτε σε έναν ιστότοπου τρίτου για να πραγματοποιηθεί η πιστοποίηση του λογαριασμού σας με το %(integrationsUrl)s. Θα θέλατε να συνεχίσετε;", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Θα μεταφερθείτε σε έναν ιστότοπου τρίτου για να πραγματοποιηθεί η πιστοποίηση του λογαριασμού σας με το %(integrationsUrl)s. Θα θέλατε να συνεχίσετε;",

View file

@ -1,125 +1,5 @@
{ {
"Add a widget": "Add a widget", "Add a widget": "Add a widget",
"af": "Afrikaans",
"ar-ae": "Arabic (U.A.E.)",
"ar-bh": "Arabic (Bahrain)",
"ar-dz": "Arabic (Algeria)",
"ar-eg": "Arabic (Egypt)",
"ar-iq": "Arabic (Iraq)",
"ar-jo": "Arabic (Jordan)",
"ar-kw": "Arabic (Kuwait)",
"ar-lb": "Arabic (Lebanon)",
"ar-ly": "Arabic (Libya)",
"ar-ma": "Arabic (Morocco)",
"ar-om": "Arabic (Oman)",
"ar-qa": "Arabic (Qatar)",
"ar-sa": "Arabic (Saudi Arabia)",
"ar-sy": "Arabic (Syria)",
"ar-tn": "Arabic (Tunisia)",
"ar-ye": "Arabic (Yemen)",
"be": "Belarusian",
"bg": "Bulgarian",
"ca": "Catalan",
"cs": "Czech",
"da": "Danish",
"de-at": "German (Austria)",
"de-ch": "German (Switzerland)",
"de": "German",
"de-li": "German (Liechtenstein)",
"de-lu": "German (Luxembourg)",
"el": "Greek",
"en-au": "English (Australia)",
"en-bz": "English (Belize)",
"en-ca": "English (Canada)",
"en": "English",
"en-gb": "English (United Kingdom)",
"en-ie": "English (Ireland)",
"en-jm": "English (Jamaica)",
"en-nz": "English (New Zealand)",
"en-tt": "English (Trinidad)",
"en-us": "English (United States)",
"en-za": "English (South Africa)",
"es-ar": "Spanish (Argentina)",
"es-bo": "Spanish (Bolivia)",
"es-cl": "Spanish (Chile)",
"es-co": "Spanish (Colombia)",
"es-cr": "Spanish (Costa Rica)",
"es-do": "Spanish (Dominican Republic)",
"es-ec": "Spanish (Ecuador)",
"es-gt": "Spanish (Guatemala)",
"es-hn": "Spanish (Honduras)",
"es-mx": "Spanish (Mexico)",
"es-ni": "Spanish (Nicaragua)",
"es-pa": "Spanish (Panama)",
"es-pe": "Spanish (Peru)",
"es-pr": "Spanish (Puerto Rico)",
"es-py": "Spanish (Paraguay)",
"es": "Spanish (Spain)",
"es-sv": "Spanish (El Salvador)",
"es-uy": "Spanish (Uruguay)",
"es-ve": "Spanish (Venezuela)",
"et": "Estonian",
"eu": "Basque (Basque)",
"fa": "Farsi",
"fi": "Finnish",
"fo": "Faeroese",
"fr-be": "French (Belgium)",
"fr-ca": "French (Canada)",
"fr-ch": "French (Switzerland)",
"fr": "French",
"fr-lu": "French (Luxembourg)",
"ga": "Irish",
"gd": "Gaelic (Scotland)",
"he": "Hebrew",
"hi": "Hindi",
"hr": "Croatian",
"hu": "Hungarian",
"id": "Indonesian",
"is": "Icelandic",
"it-ch": "Italian (Switzerland)",
"it": "Italian",
"ja": "Japanese",
"ji": "Yiddish",
"ko": "Korean",
"lt": "Lithuanian",
"lv": "Latvian",
"mk": "Macedonian (FYROM)",
"ms": "Malaysian",
"mt": "Maltese",
"nl-be": "Dutch (Belgium)",
"nl": "Dutch",
"no": "Norwegian",
"pl": "Polish",
"pt-br": "Brazilian Portuguese",
"pt": "Portuguese",
"rm": "Rhaeto-Romanic",
"ro-mo": "Romanian (Republic of Moldova)",
"ro": "Romanian",
"ru-mo": "Russian (Republic of Moldova)",
"ru": "Russian",
"sb": "Sorbian",
"sk": "Slovak",
"sl": "Slovenian",
"sq": "Albanian",
"sr": "Serbian",
"sv-fi": "Swedish (Finland)",
"sv": "Swedish",
"sx": "Sutu",
"sz": "Sami (Lappish)",
"th": "Thai",
"tn": "Tswana",
"tr": "Turkish",
"ts": "Tsonga",
"uk": "Ukrainian",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamese",
"xh": "Xhosa",
"zh-cn": "Chinese (PRC)",
"zh-hk": "Chinese (Hong Kong SAR)",
"zh-sg": "Chinese (Singapore)",
"zh-tw": "Chinese (Taiwan)",
"zu": "Zulu",
"a room": "a room", "a room": "a room",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains", "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", "Accept": "Accept",
@ -133,7 +13,7 @@
"Add email address": "Add email address", "Add email address": "Add email address",
"Add phone number": "Add phone number", "Add phone number": "Add phone number",
"Admin": "Admin", "Admin": "Admin",
"Admin tools": "Admin tools", "Admin Tools": "Admin tools",
"Allow": "Allow", "Allow": "Allow",
"And %(count)s more...": "And %(count)s more...", "And %(count)s more...": "And %(count)s more...",
"VoIP": "VoIP", "VoIP": "VoIP",
@ -152,24 +32,18 @@
"Always show message timestamps": "Always show message timestamps", "Always show message timestamps": "Always show message timestamps",
"Authentication": "Authentication", "Authentication": "Authentication",
"Alias (optional)": "Alias (optional)", "Alias (optional)": "Alias (optional)",
"all room members": "all room members",
"all room members, from the point they are invited": "all room members, from the point they are invited",
"all room members, from the point they joined": "all room members, from the point they joined",
"and": "and", "and": "and",
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others", "%(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 one other": "%(items)s and one other",
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
"and %(count)s others...": { "and %(count)s others...|other": "and %(count)s others...",
"other": "and %(count)s others...", "and %(count)s others...|one": "and one other...",
"one": "and one other..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", "%(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 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", "%(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", "An email has been sent to": "An email has been sent to",
"A new password must be entered.": "A new password must be entered.", "A new password must be entered.": "A new password must be entered.",
"%(senderName)s answered the call.": "%(senderName)s answered the call.", "%(senderName)s answered the call.": "%(senderName)s answered the call.",
"anyone": "anyone",
"An error has occurred.": "An error has occurred.", "An error has occurred.": "An error has occurred.",
"Anyone": "Anyone", "Anyone": "Anyone",
"Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests",
@ -193,6 +67,7 @@
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.", "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
"Can't load user settings": "Can't load user settings", "Can't load user settings": "Can't load user settings",
"Cannot add any more widgets": "Cannot add any more widgets",
"Change Password": "Change Password", "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 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 their profile picture.": "%(senderName)s changed their profile picture.",
@ -203,7 +78,6 @@
"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 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", "Changes your display nickname": "Changes your display nickname",
"Changes colour scheme of current room": "Changes colour scheme of current room", "Changes colour scheme of current room": "Changes colour scheme of current room",
"changing room on a RoomView is not supported": "changing room on a RoomView is not supported",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key", "Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key",
"Clear Cache and Reload": "Clear Cache and Reload", "Clear Cache and Reload": "Clear Cache and Reload",
@ -226,10 +100,8 @@
"Confirm your new password": "Confirm your new password", "Confirm your new password": "Confirm your new password",
"Continue": "Continue", "Continue": "Continue",
"Could not connect to the integration server": "Could not connect to the integration server", "Could not connect to the integration server": "Could not connect to the integration server",
"%(count)s new messages": { "%(count)s new messages|one": "%(count)s new message",
"one": "%(count)s new message", "%(count)s new messages|other": "%(count)s new messages",
"other": "%(count)s new messages"
},
"Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one", "Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one",
"Create an account": "Create an account", "Create an account": "Create an account",
"Create Room": "Create Room", "Create Room": "Create Room",
@ -257,15 +129,14 @@
"Device key:": "Device key:", "Device key:": "Device key:",
"Devices": "Devices", "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", "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", "Direct chats": "Direct chats",
"Disable Notifications": "Disable Notifications", "Disable Notifications": "Disable Notifications",
"disabled": "disabled", "disabled": "disabled",
"Disable inline URL previews by default": "Disable inline URL previews by default", "Disable inline URL previews by default": "Disable inline URL previews by default",
"Disable markdown formatting": "Disable markdown formatting",
"Disinvite": "Disinvite", "Disinvite": "Disinvite",
"Display name": "Display name", "Display name": "Display name",
"Displays action": "Displays action", "Displays action": "Displays action",
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
"Don't send typing notifications": "Don't send typing notifications", "Don't send typing notifications": "Don't send typing notifications",
"Download %(text)s": "Download %(text)s", "Download %(text)s": "Download %(text)s",
"Drop File Here": "Drop File Here", "Drop File Here": "Drop File Here",
@ -307,7 +178,6 @@
"Failed to fetch avatar URL": "Failed to fetch avatar URL", "Failed to fetch avatar URL": "Failed to fetch avatar URL",
"Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s",
"Failed to join room": "Failed to join room", "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 kick": "Failed to kick",
"Failed to leave room": "Failed to leave room", "Failed to leave room": "Failed to leave room",
"Failed to load timeline position": "Failed to load timeline position", "Failed to load timeline position": "Failed to load timeline position",
@ -340,10 +210,6 @@
"Found a bug?": "Found a bug?", "Found a bug?": "Found a bug?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s",
"Guest access is disabled on this Home Server.": "Guest access is disabled on this Home Server.", "Guest access is disabled on this Home Server.": "Guest access is disabled on this Home Server.",
"Guests can't set avatars. Please register.": "Guests can't set avatars. Please register.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Guest users can't create new rooms. Please register to create room and start a chat.",
"Guest users can't upload files. Please register to upload.": "Guest users can't upload files. Please register to upload.",
"Guests can't use labs features. Please register.": "Guests can't use labs features. Please register.",
"Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.",
"had": "had", "had": "had",
"Hangup": "Hangup", "Hangup": "Hangup",
@ -364,6 +230,7 @@
"Incoming voice call from %(name)s": "Incoming voice call from %(name)s", "Incoming voice call from %(name)s": "Incoming voice call from %(name)s",
"Incorrect username and/or password.": "Incorrect username and/or password.", "Incorrect username and/or password.": "Incorrect username and/or password.",
"Incorrect verification code": "Incorrect verification code", "Incorrect verification code": "Incorrect verification code",
"Integrations Error": "Integrations Error",
"Interface Language": "Interface Language", "Interface Language": "Interface Language",
"Invalid alias format": "Invalid alias format", "Invalid alias format": "Invalid alias format",
"Invalid address format": "Invalid address format", "Invalid address format": "Invalid address format",
@ -389,25 +256,40 @@
"Kick": "Kick", "Kick": "Kick",
"Kicks user with given id": "Kicks user with given id", "Kicks user with given id": "Kicks user with given id",
"Labs": "Labs", "Labs": "Labs",
"Ignored Users": "Ignored Users",
"Ignore": "Ignore",
"Unignore": "Unignore",
"User Options": "User Options",
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
"Unignored user": "Unignored user",
"Ignored user": "Ignored user",
"Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward",
"Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
"Last seen": "Last seen", "Last seen": "Last seen",
"Leave room": "Leave room", "Leave room": "Leave room",
"left and rejoined": "left and rejoined", "left and rejoined": "left and rejoined",
"left": "left", "left": "left",
"%(targetName)s left the room.": "%(targetName)s left the room.", "%(targetName)s left the room.": "%(targetName)s left the room.",
"Level:": "Level:", "Level:": "Level:",
"List this room in %(domain)s's room directory?": "List this room in %(domain)s's room directory?", "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
"Local addresses for this room:": "Local addresses for this room:", "Local addresses for this room:": "Local addresses for this room:",
"Logged in as:": "Logged in as:", "Logged in as:": "Logged in as:",
"Login as guest": "Login as guest", "Login as guest": "Login as guest",
"Logout": "Logout", "Logout": "Logout",
"Low priority": "Low priority", "Low priority": "Low priority",
"%(senderName)s made future room history visible to": "%(senderName)s made future room history visible to", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).",
"Manage Integrations": "Manage Integrations", "Manage Integrations": "Manage Integrations",
"Markdown is disabled": "Markdown is disabled", "Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled", "Markdown is enabled": "Markdown is enabled",
"matrix-react-sdk version:": "matrix-react-sdk version:", "matrix-react-sdk version:": "matrix-react-sdk version:",
"Matrix Apps": "Matrix Apps", "Matrix Apps": "Matrix Apps",
"Members only": "Members only", "Members only": "Members only",
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
"Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", "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 room_id in request": "Missing room_id in request",
"Missing user_id in request": "Missing user_id in request", "Missing user_id in request": "Missing user_id in request",
@ -422,7 +304,6 @@
"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": "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", "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 address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"New Composer & Autocomplete": "New Composer & Autocomplete",
"New password": "New password", "New password": "New password",
"New passwords don't match": "New passwords don't match", "New passwords don't match": "New passwords don't match",
"New passwords must match each other.": "New passwords must match each other.", "New passwords must match each other.": "New passwords must match each other.",
@ -435,6 +316,7 @@
"AM": "AM", "AM": "AM",
"PM": "PM", "PM": "PM",
"NOT verified": "NOT verified", "NOT verified": "NOT verified",
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
"No devices with registered encryption keys": "No devices with registered encryption keys", "No devices with registered encryption keys": "No devices with registered encryption keys",
"No display name": "No display name", "No display name": "No display name",
"No more results": "No more results", "No more results": "No more results",
@ -443,7 +325,7 @@
"OK": "OK", "OK": "OK",
"olm version:": "olm version:", "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 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", "Once you've followed the link it contains, click below": "Once you've followed the link it contains, click below",
"Only people who have been invited": "Only people who have been invited", "Only people who have been invited": "Only people who have been invited",
"Operation failed": "Operation failed", "Operation failed": "Operation failed",
"Otherwise, <a>click here</a> to send a bug report.": "Otherwise, <a>click here</a> to send a bug report.", "Otherwise, <a>click here</a> to send a bug report.": "Otherwise, <a>click here</a> to send a bug report.",
@ -455,9 +337,7 @@
"Phone": "Phone", "Phone": "Phone",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s placed a %(callType)s call.", "%(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 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.", "Power level must be positive integer.": "Power level must be positive integer.",
"Press": "Press",
"Press <StartChatButton> to start a chat with someone": "Press <StartChatButton> to start a chat with someone", "Press <StartChatButton> to start a chat with someone": "Press <StartChatButton> to start a chat with someone",
"Privacy warning": "Privacy warning", "Privacy warning": "Privacy warning",
"Private Chat": "Private Chat", "Private Chat": "Private Chat",
@ -503,7 +383,6 @@
"Search": "Search", "Search": "Search",
"Search failed": "Search failed", "Search failed": "Search failed",
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results", "Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
"Searching known users": "Searching known users",
"Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s",
"Send a message (unencrypted)": "Send a message (unencrypted)", "Send a message (unencrypted)": "Send a message (unencrypted)",
"Send an encrypted message": "Send an encrypted message", "Send an encrypted message": "Send an encrypted message",
@ -545,14 +424,13 @@
"Start Chat": "Start Chat", "Start Chat": "Start Chat",
"Submit": "Submit", "Submit": "Submit",
"Success": "Success", "Success": "Success",
"tag as %(tagName)s": "tag as %(tagName)s",
"tag direct chat": "tag direct chat", "tag direct chat": "tag direct chat",
"Tagged as: ": "Tagged as: ", "Tagged as: ": "Tagged as: ",
"The default role for new room members is": "The default role for new room members is", "The default role for new room members is": "The default role for new room members is",
"The main address for this room is": "The main address for this room is", "The main address for this room is": "The main address for this room is",
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
"The phone number entered looks invalid": "The phone number entered looks invalid", "The phone number entered looks invalid": "The phone number entered looks invalid",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.",
"This action cannot be performed by a guest user. Please register to be able to do this.": "This action cannot be performed by a guest user. Please register to be able to do this.",
"This email address is already in use": "This email address is already in use", "This email address is already in use": "This email address is already in use",
"This email address was not found": "This email address was not found", "This email address was not found": "This email address was not found",
"%(actionVerb)s this person?": "%(actionVerb)s this person?", "%(actionVerb)s this person?": "%(actionVerb)s this person?",
@ -562,7 +440,6 @@
"The remote side failed to pick up": "The remote side failed to pick up", "The remote side failed to pick up": "The remote side failed to pick up",
"This Home Server does not support login using email address.": "This Home Server does not support login using email address.", "This Home Server does not support login using email address.": "This Home Server does not support login using email address.",
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:", "This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
"There was a problem logging in.": "There was a problem logging in.",
"This room has no local addresses": "This room has no local addresses", "This room has no local addresses": "This room has no local addresses",
"This room is not recognised.": "This room is not recognised.", "This room is not recognised.": "This room is not recognised.",
"These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways", "These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways",
@ -590,7 +467,6 @@
"To send events of type": "To send events of type", "To send events of type": "To send events of type",
"To send messages": "To send messages", "To send messages": "To send messages",
"to start a chat with someone": "to start a chat with someone", "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 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.", "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 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.",
@ -601,7 +477,6 @@
"Unable to add email address": "Unable to add email address", "Unable to add email address": "Unable to add email address",
"Unable to create widget.": "Unable to create widget.", "Unable to create widget.": "Unable to create widget.",
"Unable to remove contact information": "Unable to remove contact information", "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.", "Unable to verify email address.": "Unable to verify email address.",
"Unban": "Unban", "Unban": "Unban",
"Unbans user with given id": "Unbans user with given id", "Unbans user with given id": "Unbans user with given id",
@ -619,17 +494,14 @@
"unknown error code": "unknown error code", "unknown error code": "unknown error code",
"Unknown room %(roomId)s": "Unknown room %(roomId)s", "Unknown room %(roomId)s": "Unknown room %(roomId)s",
"Unknown (user, device) pair:": "Unknown (user, device) pair:", "Unknown (user, device) pair:": "Unknown (user, device) pair:",
"unknown": "unknown",
"Unmute": "Unmute", "Unmute": "Unmute",
"Unnamed Room": "Unnamed Room", "Unnamed Room": "Unnamed Room",
"Unrecognised command:": "Unrecognised command:", "Unrecognised command:": "Unrecognised command:",
"Unrecognised room alias:": "Unrecognised room alias:", "Unrecognised room alias:": "Unrecognised room alias:",
"Unverified": "Unverified", "Unverified": "Unverified",
"Uploading %(filename)s and %(count)s others": { "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
"zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
"one": "Uploading %(filename)s and %(count)s other", "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
"other": "Uploading %(filename)s and %(count)s others"
},
"uploaded a file": "uploaded a file", "uploaded a file": "uploaded a file",
"Upload avatar": "Upload avatar", "Upload avatar": "Upload avatar",
"Upload Failed": "Upload Failed", "Upload Failed": "Upload Failed",
@ -685,7 +557,6 @@
"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 been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.", "You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.", "You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "You have entered an invalid contact. Try using their Matrix ID or email address.",
"You have no visible notifications": "You have no visible notifications", "You have no visible notifications": "You have no visible notifications",
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.", "You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
"you must be a": "you must be a", "you must be a": "you must be a",
@ -722,9 +593,10 @@
"Dec": "Dec", "Dec": "Dec",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)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, %(monthName)s %(day)s %(fullYear)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s": "%(weekDayName)s, %(monthName)s %(day)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Set a display name:", "Set a display name:": "Set a display name:",
"Set a Display Name": "Set a Display Name",
"Upload an avatar:": "Upload an avatar:", "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.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.",
"Missing password.": "Missing password.", "Missing password.": "Missing password.",
@ -743,14 +615,13 @@
"Encrypt room": "Encrypt room", "Encrypt room": "Encrypt room",
"There are no visible files in this room": "There are no visible files in this room", "There are no visible files in this room": "There are no visible files in this room",
"Room": "Room", "Room": "Room",
"Copied!": "Copied!",
"Failed to copy": "Failed to copy",
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "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.", "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
"Auto-complete": "Auto-complete",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.", "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
"(~%(count)s results)": { "(~%(count)s results)|one": "(~%(count)s result)",
"one": "(~%(count)s result)", "(~%(count)s results)|other": "(~%(count)s results)",
"other": "(~%(count)s results)"
},
"Cancel": "Cancel", "Cancel": "Cancel",
"or": "or", "or": "or",
"Active call": "Active call", "Active call": "Active call",
@ -819,7 +690,6 @@
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar", "%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
"Please select the destination room for this message": "Please select the destination room for this message", "Please select the destination room for this message": "Please select the destination room for this message",
"Create new room": "Create new room", "Create new room": "Create new room",
"Welcome page": "Welcome page",
"Room directory": "Room directory", "Room directory": "Room directory",
"Start chat": "Start chat", "Start chat": "Start chat",
"New Password": "New Password", "New Password": "New Password",
@ -842,7 +712,6 @@
"You must join the room to see its files": "You must join the room to see its files", "You must join the room to see its files": "You must join the room to see its files",
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Start new chat": "Start new chat", "Start new chat": "Start new chat",
"Guest users can't invite users. Please register.": "Guest users can't invite users. Please register.",
"Failed to invite": "Failed to invite", "Failed to invite": "Failed to invite",
"Failed to invite user": "Failed to invite user", "Failed to invite user": "Failed to invite user",
"Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:",
@ -866,7 +735,6 @@
"Unable to restore session": "Unable to restore session", "Unable to restore session": "Unable to restore session",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.", "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.",
"Continue anyway": "Continue anyway", "Continue anyway": "Continue anyway",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
@ -950,6 +818,7 @@
"Autocomplete Delay (ms):": "Autocomplete Delay (ms):", "Autocomplete Delay (ms):": "Autocomplete Delay (ms):",
"This Home server does not support groups": "This Home server does not support groups", "This Home server does not support groups": "This Home server does not support groups",
"Loading device info...": "Loading device info...", "Loading device info...": "Loading device info...",
"Message removed by %(userId)s": "Message removed by %(userId)s",
"Groups": "Groups", "Groups": "Groups",
"Create a new group": "Create a new group", "Create a new group": "Create a new group",
"Create Group": "Create Group", "Create Group": "Create Group",
@ -964,7 +833,7 @@
"You are a member of these groups:": "You are a member of these groups:", "You are a member of these groups:": "You are a member of these groups:",
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.", "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.",
"Join an existing group": "Join an existing group", "Join an existing group": "Join an existing group",
"To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an exisitng group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.", "To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.",
"Featured Rooms:": "Featured Rooms:", "Featured Rooms:": "Featured Rooms:",
"Error whilst fetching joined groups": "Error whilst fetching joined groups", "Error whilst fetching joined groups": "Error whilst fetching joined groups",
"Featured Users:": "Featured Users:", "Featured Users:": "Featured Users:",
@ -973,5 +842,72 @@
"Failed to upload image": "Failed to upload image", "Failed to upload image": "Failed to upload image",
"Failed to update group": "Failed to update group", "Failed to update group": "Failed to update group",
"Hide avatars in user and room mentions": "Hide avatars in user and room mentions", "Hide avatars in user and room mentions": "Hide avatars in user and room mentions",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>" "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
"Description": "Description",
"Filter group members": "Filter group members",
"Filter group rooms": "Filter group rooms",
"Remove from group": "Remove from group",
"Invite new group members": "Invite new group members",
"Who would you like to add to this group?": "Who would you like to add to this group?",
"Name or matrix ID": "Name or matrix ID",
"Invite to Group": "Invite to Group",
"Unable to accept invite": "Unable to accept invite",
"Unable to leave room": "Unable to leave room",
"%(inviter)s has invited you to join this group": "%(inviter)s has invited you to join this group",
"You are a member of this group": "You are a member of this group",
"Leave": "Leave",
"Failed to remove user from group": "Failed to remove user from group",
"Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:",
"Failed to invite users group": "Failed to invite users group",
"Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s",
"Unable to reject invite": "Unable to reject invite",
"Leave Group": "Leave Group",
"Leave %(groupName)s?": "Leave %(groupName)s?",
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
"Flair": "Flair",
"Add a Room": "Add a Room",
"Add a User": "Add a User",
"Add users to the group summary": "Add users to the group summary",
"Who would you like to add to this summary?": "Who would you like to add to this summary?",
"Add to summary": "Add to summary",
"Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:",
"Add rooms to the group summary": "Add rooms to the group summary",
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
"Room name or alias": "Room name or alias",
"You are an administrator of this group": "You are an administrator of this group",
"Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:",
"Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s",
"The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.",
"Failed to remove a user from the summary of %(groupId)s": "Failed to remove a user from the summary of %(groupId)s",
"The user '%(displayName)s' could not be removed from the summary.": "The user '%(displayName)s' could not be removed from the summary.",
"Light theme": "Light theme",
"Dark theme": "Dark theme",
"Unknown": "Unknown",
"Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:",
"The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.",
"Add rooms to the group": "Add rooms to the group",
"Which rooms would you like to add to this group?": "Which rooms would you like to add to this group?",
"Add to group": "Add to group",
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
"Unpublish": "Unpublish",
"This group is published on your profile": "This group is published on your profile",
"Publish": "Publish",
"This group is not published on your profile": "This group is not published on your profile",
"Matrix ID": "Matrix ID",
"Matrix Room ID": "Matrix Room ID",
"email address": "email address",
"Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.",
"You have entered an invalid address.": "You have entered an invalid address.",
"Failed to remove room from group": "Failed to remove room from group",
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
"Removing a room from the group will also remove it from the group page.": "Removing a room from the group will also remove it from the group page.",
"Related Groups": "Related Groups",
"Related groups for this room:": "Related groups for this room:",
"This room has no related groups": "This room has no related groups",
"New group ID (e.g. +foo:%(localDomain)s)": "New group ID (e.g. +foo:%(localDomain)s)"
} }

View file

@ -1,125 +1,5 @@
{ {
"Add a widget": "Add a widget", "Add a widget": "Add a widget",
"af": "Afrikaans",
"ar-ae": "Arabic (U.A.E.)",
"ar-bh": "Arabic (Bahrain)",
"ar-dz": "Arabic (Algeria)",
"ar-eg": "Arabic (Egypt)",
"ar-iq": "Arabic (Iraq)",
"ar-jo": "Arabic (Jordan)",
"ar-kw": "Arabic (Kuwait)",
"ar-lb": "Arabic (Lebanon)",
"ar-ly": "Arabic (Libya)",
"ar-ma": "Arabic (Morocco)",
"ar-om": "Arabic (Oman)",
"ar-qa": "Arabic (Qatar)",
"ar-sa": "Arabic (Saudi Arabia)",
"ar-sy": "Arabic (Syria)",
"ar-tn": "Arabic (Tunisia)",
"ar-ye": "Arabic (Yemen)",
"be": "Belarusian",
"bg": "Bulgarian",
"ca": "Catalan",
"cs": "Czech",
"da": "Danish",
"de-at": "German (Austria)",
"de-ch": "German (Switzerland)",
"de": "German",
"de-li": "German (Liechtenstein)",
"de-lu": "German (Luxembourg)",
"el": "Greek",
"en-au": "English (Australia)",
"en-bz": "English (Belize)",
"en-ca": "English (Canada)",
"en": "English",
"en-gb": "English (United Kingdom)",
"en-ie": "English (Ireland)",
"en-jm": "English (Jamaica)",
"en-nz": "English (New Zealand)",
"en-tt": "English (Trinidad)",
"en-us": "English (United States)",
"en-za": "English (South Africa)",
"es-ar": "Spanish (Argentina)",
"es-bo": "Spanish (Bolivia)",
"es-cl": "Spanish (Chile)",
"es-co": "Spanish (Colombia)",
"es-cr": "Spanish (Costa Rica)",
"es-do": "Spanish (Dominican Republic)",
"es-ec": "Spanish (Ecuador)",
"es-gt": "Spanish (Guatemala)",
"es-hn": "Spanish (Honduras)",
"es-mx": "Spanish (Mexico)",
"es-ni": "Spanish (Nicaragua)",
"es-pa": "Spanish (Panama)",
"es-pe": "Spanish (Peru)",
"es-pr": "Spanish (Puerto Rico)",
"es-py": "Spanish (Paraguay)",
"es": "Spanish (Spain)",
"es-sv": "Spanish (El Salvador)",
"es-uy": "Spanish (Uruguay)",
"es-ve": "Spanish (Venezuela)",
"et": "Estonian",
"eu": "Basque (Basque)",
"fa": "Farsi",
"fi": "Finnish",
"fo": "Faeroese",
"fr-be": "French (Belgium)",
"fr-ca": "French (Canada)",
"fr-ch": "French (Switzerland)",
"fr": "French",
"fr-lu": "French (Luxembourg)",
"ga": "Irish",
"gd": "Gaelic (Scotland)",
"he": "Hebrew",
"hi": "Hindi",
"hr": "Croatian",
"hu": "Hungarian",
"id": "Indonesian",
"is": "Icelandic",
"it-ch": "Italian (Switzerland)",
"it": "Italian",
"ja": "Japanese",
"ji": "Yiddish",
"ko": "Korean",
"lt": "Lithuanian",
"lv": "Latvian",
"mk": "Macedonian (FYROM)",
"ms": "Malaysian",
"mt": "Maltese",
"nl-be": "Dutch (Belgium)",
"nl": "Dutch",
"no": "Norwegian",
"pl": "Polish",
"pt-br": "Brazilian Portuguese",
"pt": "Portuguese",
"rm": "Rhaeto-Romanic",
"ro-mo": "Romanian (Republic of Moldova)",
"ro": "Romanian",
"ru-mo": "Russian (Republic of Moldova)",
"ru": "Russian",
"sb": "Sorbian",
"sk": "Slovak",
"sl": "Slovenian",
"sq": "Albanian",
"sr": "Serbian",
"sv-fi": "Swedish (Finland)",
"sv": "Swedish",
"sx": "Sutu",
"sz": "Sami (Lappish)",
"th": "Thai",
"tn": "Tswana",
"tr": "Turkish",
"ts": "Tsonga",
"uk": "Ukrainian",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamese",
"xh": "Xhosa",
"zh-cn": "Chinese (PRC)",
"zh-hk": "Chinese (Hong Kong SAR)",
"zh-sg": "Chinese (Singapore)",
"zh-tw": "Chinese (Taiwan)",
"zu": "Zulu",
"AM": "AM", "AM": "AM",
"PM": "PM", "PM": "PM",
"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", "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",
@ -146,25 +26,19 @@
"Hide removed messages": "Hide removed messages", "Hide removed messages": "Hide removed messages",
"Always show message timestamps": "Always show message timestamps", "Always show message timestamps": "Always show message timestamps",
"Authentication": "Authentication", "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", "an address": "an address",
"and": "and", "and": "and",
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others", "%(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 one other": "%(items)s and one other",
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
"and %(count)s others...": { "and %(count)s others...|other": "and %(count)s others...",
"other": "and %(count)s others...", "and %(count)s others...|one": "and one other...",
"one": "and one other..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", "%(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 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", "%(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", "An email has been sent to": "An email has been sent to",
"A new password must be entered.": "A new password must be entered.", "A new password must be entered.": "A new password must be entered.",
"%(senderName)s answered the call.": "%(senderName)s answered the call.", "%(senderName)s answered the call.": "%(senderName)s answered the call.",
"anyone": "anyone",
"An error has occurred.": "An error has occurred.", "An error has occurred.": "An error has occurred.",
"Anyone": "Anyone", "Anyone": "Anyone",
"Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests",
@ -195,7 +69,6 @@
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)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 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", "Changes your display nickname": "Changes your display nickname",
"changing room on a RoomView is not supported": "changing room on a RoomView is not supported",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key", "Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key",
"Clear Cache and Reload": "Clear Cache and Reload", "Clear Cache and Reload": "Clear Cache and Reload",
@ -241,7 +114,6 @@
"Device key:": "Device key:", "Device key:": "Device key:",
"Devices": "Devices", "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", "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", "Direct chats": "Direct chats",
"disabled": "disabled", "disabled": "disabled",
"Disable inline URL previews by default": "Disable inline URL previews by default", "Disable inline URL previews by default": "Disable inline URL previews by default",
@ -279,7 +151,6 @@
"Failed to delete device": "Failed to delete device", "Failed to delete device": "Failed to delete device",
"Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s",
"Failed to join room": "Failed to join room", "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 kick": "Failed to kick",
"Failed to leave room": "Failed to leave room", "Failed to leave room": "Failed to leave room",
"Failed to load timeline position": "Failed to load timeline position", "Failed to load timeline position": "Failed to load timeline position",
@ -311,10 +182,6 @@
"Found a bug?": "Found a bug?", "Found a bug?": "Found a bug?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s",
"Guest access is disabled on this Home Server.": "Guest access is disabled on this Home Server.", "Guest access is disabled on this Home Server.": "Guest access is disabled on this Home Server.",
"Guests can't set avatars. Please register.": "Guests can't set avatars. Please register.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Guest users can't create new rooms. Please register to create room and start a chat.",
"Guest users can't upload files. Please register to upload.": "Guest users can't upload files. Please register to upload.",
"Guests can't use labs features. Please register.": "Guests can't use labs features. Please register.",
"Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.",
"had": "had", "had": "had",
"Hangup": "Hangup", "Hangup": "Hangup",
@ -354,24 +221,39 @@
"Kick": "Kick", "Kick": "Kick",
"Kicks user with given id": "Kicks user with given id", "Kicks user with given id": "Kicks user with given id",
"Labs": "Labs", "Labs": "Labs",
"Ignored Users": "Ignored Users",
"Ignore": "Ignore",
"Unignore": "Unignore",
"User Options": "User Options",
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
"Unignored user": "Unignored user",
"Ignored user": "Ignored user",
"Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward",
"Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
"Leave room": "Leave room", "Leave room": "Leave room",
"left and rejoined": "left and rejoined", "left and rejoined": "left and rejoined",
"left": "left", "left": "left",
"%(targetName)s left the room.": "%(targetName)s left the room.", "%(targetName)s left the room.": "%(targetName)s left the room.",
"Level": "Level", "Level": "Level",
"List this room in %(domain)s's room directory?": "List this room in %(domain)s's room directory?", "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
"Local addresses for this room:": "Local addresses for this room:", "Local addresses for this room:": "Local addresses for this room:",
"Logged in as:": "Logged in as:", "Logged in as:": "Logged in as:",
"Login as guest": "Login as guest", "Login as guest": "Login as guest",
"Logout": "Logout", "Logout": "Logout",
"Low priority": "Low priority", "Low priority": "Low priority",
"%(senderName)s made future room history visible to": "%(senderName)s made future room history visible to", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).",
"Manage Integrations": "Manage Integrations", "Manage Integrations": "Manage Integrations",
"Markdown is disabled": "Markdown is disabled", "Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled", "Markdown is enabled": "Markdown is enabled",
"matrix-react-sdk version:": "matrix-react-sdk version:", "matrix-react-sdk version:": "matrix-react-sdk version:",
"Matrix Apps": "Matrix Apps", "Matrix Apps": "Matrix Apps",
"Members only": "Members only", "Members only": "Members only",
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
"Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", "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 room_id in request": "Missing room_id in request",
"Missing user_id in request": "Missing user_id in request", "Missing user_id in request": "Missing user_id in request",
@ -386,7 +268,6 @@
"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": "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", "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 address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"New Composer & Autocomplete": "New Composer & Autocomplete",
"New password": "New password", "New password": "New password",
"New passwords don't match": "New passwords don't match", "New passwords don't match": "New passwords don't match",
"New passwords must match each other.": "New passwords must match each other.", "New passwords must match each other.": "New passwords must match each other.",
@ -404,7 +285,7 @@
"OK": "OK", "OK": "OK",
"olm version:": "olm version:", "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 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", "Once you've followed the link it contains, click below": "Once you've followed the link it contains, click below",
"Only people who have been invited": "Only people who have been invited", "Only people who have been invited": "Only people who have been invited",
"Operation failed": "Operation failed", "Operation failed": "Operation failed",
"Password": "Password", "Password": "Password",
@ -415,9 +296,7 @@
"Phone": "Phone", "Phone": "Phone",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s placed a %(callType)s call.", "%(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 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.", "Power level must be positive integer.": "Power level must be positive integer.",
"Press": "Press",
"Privacy warning": "Privacy warning", "Privacy warning": "Privacy warning",
"Privileged Users": "Privileged Users", "Privileged Users": "Privileged Users",
"Profile": "Profile", "Profile": "Profile",
@ -490,13 +369,11 @@
"Start Chat": "Start Chat", "Start Chat": "Start Chat",
"Submit": "Submit", "Submit": "Submit",
"Success": "Success", "Success": "Success",
"tag as %(tagName)s": "tag as %(tagName)s",
"tag direct chat": "tag direct chat", "tag direct chat": "tag direct chat",
"Tagged as: ": "Tagged as: ", "Tagged as: ": "Tagged as: ",
"The default role for new room members is": "The default role for new room members is", "The default role for new room members is": "The default role for new room members is",
"The main address for this room is": "The main address for this room is", "The main address for this room is": "The main address for this room is",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.",
"This action cannot be performed by a guest user. Please register to be able to do this.": "This action cannot be performed by a guest user. Please register to be able to do this.",
"This email address is already in use": "This email address is already in use", "This email address is already in use": "This email address is already in use",
"This email address was not found": "This email address was not found", "This email address was not found": "This email address was not found",
"%(actionVerb)s this person?": "%(actionVerb)s this person?", "%(actionVerb)s this person?": "%(actionVerb)s this person?",
@ -505,7 +382,6 @@
"The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload", "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", "The remote side failed to pick up": "The remote side failed to pick up",
"This Home Server does not support login using email address.": "This Home Server does not support login using email address.", "This Home Server does not support login using email address.": "This Home Server does not support login using email address.",
"There was a problem logging in.": "There was a problem logging in.",
"This room has no local addresses": "This room has no local addresses", "This room has no local addresses": "This room has no local addresses",
"This room is not recognised.": "This room is not recognized.", "This room is not recognised.": "This room is not recognized.",
"These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways", "These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways",
@ -533,7 +409,6 @@
"To send events of type": "To send events of type", "To send events of type": "To send events of type",
"To send messages": "To send messages", "To send messages": "To send messages",
"to start a chat with someone": "to start a chat with someone", "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 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.", "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 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.",
@ -543,7 +418,6 @@
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).", "%(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 add email address": "Unable to add email address",
"Unable to remove contact information": "Unable to remove contact information", "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.", "Unable to verify email address.": "Unable to verify email address.",
"Unban": "Unban", "Unban": "Unban",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.", "%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.",
@ -556,7 +430,6 @@
"unknown error code": "unknown error code", "unknown error code": "unknown error code",
"Unknown room %(roomId)s": "Unknown room %(roomId)s", "Unknown room %(roomId)s": "Unknown room %(roomId)s",
"Unknown (user, device) pair:": "Unknown (user, device) pair:", "Unknown (user, device) pair:": "Unknown (user, device) pair:",
"unknown": "unknown",
"Unmute": "Unmute", "Unmute": "Unmute",
"Unrecognised command:": "Unrecognized command:", "Unrecognised command:": "Unrecognized command:",
"Unrecognised room alias:": "Unrecognized room alias:", "Unrecognised room alias:": "Unrecognized room alias:",
@ -605,7 +478,6 @@
"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 been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.", "You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.", "You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "You have entered an invalid contact. Try using their Matrix ID or email address.",
"You have no visible notifications": "You have no visible notifications", "You have no visible notifications": "You have no visible notifications",
"you must be a": "you must be a", "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 able to invite users to do that.": "You need to be able to invite users to do that.",
@ -641,7 +513,6 @@
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)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, %(monthName)s %(day)s %(fullYear)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Set a display name:", "Set a display name:": "Set a display name:",
"Set a Display Name": "Set a Display Name",
"Upload an avatar:": "Upload an avatar:", "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.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.",
"Missing password.": "Missing password.", "Missing password.": "Missing password.",
@ -662,7 +533,6 @@
"Room": "Room", "Room": "Room",
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "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.", "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", "Resend all": "Resend all",
"(~%(searchCount)s results)": "(~%(searchCount)s results)", "(~%(searchCount)s results)": "(~%(searchCount)s results)",
"Cancel": "Cancel", "Cancel": "Cancel",
@ -754,7 +624,6 @@
"You must join the room to see its files": "You must join the room to see its files", "You must join the room to see its files": "You must join the room to see its files",
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Start new chat": "Start new chat", "Start new chat": "Start new chat",
"Guest users can't invite users. Please register.": "Guest users can't invite users. Please register.",
"Failed to invite": "Failed to invite", "Failed to invite": "Failed to invite",
"Failed to invite user": "Failed to invite user", "Failed to invite user": "Failed to invite user",
"Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:",
@ -776,7 +645,6 @@
"Unable to restore session": "Unable to restore session", "Unable to restore session": "Unable to restore session",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.", "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.",
"Continue anyway": "Continue anyway", "Continue anyway": "Continue anyway",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
@ -837,23 +705,21 @@
"Accept": "Accept", "Accept": "Accept",
"a room": "a room", "a room": "a room",
"Add": "Add", "Add": "Add",
"Admin tools": "Admin tools", "Admin Tools": "Admin tools",
"And %(count)s more...": "And %(count)s more...", "And %(count)s more...": "And %(count)s more...",
"Alias (optional)": "Alias (optional)", "Alias (optional)": "Alias (optional)",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.", "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!", "<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
"Close": "Close", "Close": "Close",
"%(count)s new messages.one": "%(count)s new message", "%(count)s new messages|one": "%(count)s new message",
"%(count)s new messages.other": "%(count)s new messages", "%(count)s new messages|other": "%(count)s new messages",
"Custom": "Custom", "Custom": "Custom",
"Decline": "Decline", "Decline": "Decline",
"Disable markdown formatting": "Disable markdown formatting",
"Disable Notifications": "Disable Notifications", "Disable Notifications": "Disable Notifications",
"Enable Notifications": "Enable Notifications", "Enable Notifications": "Enable Notifications",
"Create new room": "Create new room", "Create new room": "Create new room",
"Room directory": "Room directory", "Room directory": "Room directory",
"Start chat": "Start chat", "Start chat": "Start chat",
"Welcome page": "Welcome page",
"Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one", "Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one",
"Drop File Here": "Drop File Here", "Drop File Here": "Drop File Here",
"Encrypted by a verified device": "Encrypted by a verified device", "Encrypted by a verified device": "Encrypted by a verified device",
@ -879,7 +745,6 @@
"Room contains unknown devices": "Room contains unknown devices", "Room contains unknown devices": "Room contains unknown devices",
"%(roomName)s does not exist.": "%(roomName)s does not exist.", "%(roomName)s does not exist.": "%(roomName)s does not exist.",
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
"Searching known users": "Searching known users",
"Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s",
"Send anyway": "Send anyway", "Send anyway": "Send anyway",
"Set": "Set", "Set": "Set",
@ -895,9 +760,9 @@
"unknown caller": "unknown caller", "unknown caller": "unknown caller",
"Unnamed Room": "Unnamed Room", "Unnamed Room": "Unnamed Room",
"Unverified": "Unverified", "Unverified": "Unverified",
"Uploading %(filename)s and %(count)s others.zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
"Uploading %(filename)s and %(count)s others.one": "Uploading %(filename)s and %(count)s other", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
"Uploading %(filename)s and %(count)s others.other": "Uploading %(filename)s and %(count)s others", "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
"Upload new:": "Upload new:", "Upload new:": "Upload new:",
"%(user)s is a": "%(user)s is a", "%(user)s is a": "%(user)s is a",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)",
@ -911,8 +776,8 @@
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality", "You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"Your home server does not support device management.": "Your home server does not support device management.", "Your home server does not support device management.": "Your home server does not support device management.",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.", "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
"(~%(count)s results).one": "(~%(count)s result)", "(~%(count)s results)|one": "(~%(count)s result)",
"(~%(count)s results).other": "(~%(count)s results)", "(~%(count)s results)|other": "(~%(count)s results)",
"New Password": "New Password", "New Password": "New Password",
"Device Name": "Device Name", "Device Name": "Device Name",
"Start chatting": "Start chatting", "Start chatting": "Start chatting",
@ -940,5 +805,53 @@
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
"Encryption key request": "Encryption key request", "Encryption key request": "Encryption key request",
"Updates": "Updates", "Updates": "Updates",
"Check for update": "Check for update" "Check for update": "Check for update",
"Allow": "Allow",
"Cannot add any more widgets": "Cannot add any more widgets",
"Changes colour scheme of current room": "Changes color scheme of current room",
"Define the power level of a user": "Define the power level of a user",
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
"Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
"Hide join/leave messages (invites/kicks/bans unaffected)": "Hide join/leave messages (invites/kicks/bans unaffected)",
"Hide avatar and display name changes": "Hide avatar and display name changes",
"Integrations Error": "Integrations Error",
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
"Sets the room topic": "Sets the room topic",
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
"To get started, please pick a username!": "To get started, please pick a username!",
"Unable to create widget.": "Unable to create widget.",
"Unbans user with given id": "Unbans user with given id",
"You are not in this room.": "You are not in this room.",
"You do not have permission to do that in this room.": "You do not have permission to do that in this room.",
"Autocomplete Delay (ms):": "Autocomplete Delay (ms):",
"This Home server does not support groups": "This Home server does not support groups",
"Loading device info...": "Loading device info...",
"Message removed by %(userId)s": "Message removed by %(userId)s",
"Groups": "Groups",
"Create a new group": "Create a new group",
"Create Group": "Create Group",
"Group Name": "Group Name",
"Example": "Example",
"Create": "Create",
"Group ID": "Group ID",
"+example:%(domain)s": "+example:%(domain)s",
"Group IDs must be of the form +localpart:%(domain)s": "Group IDs must be of the form +localpart:%(domain)s",
"Room creation failed": "Room creation failed",
"You are a member of these groups:": "You are a member of these groups:",
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.",
"Join an existing group": "Join an existing group",
"Featured Rooms:": "Featured Rooms:",
"Error whilst fetching joined groups": "Error while fetching joined groups",
"Featured Users:": "Featured Users:",
"Edit Group": "Edit Group",
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
"Failed to upload image": "Failed to upload image",
"Failed to update group": "Failed to update group",
"Hide avatars in user and room mentions": "Hide avatars in user and room mentions",
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
"Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple",
"It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s": "It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s",
"To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>."
} }

View file

@ -1,15 +1 @@
{ {}
"ar-ae": "la araba (Unuiĝintaj Arabaj Emirlandoj)",
"ar-bh": "la araba (Bareijno)",
"ar-dz": "la araba (Alĝerio)",
"ar-eg": "la araba (Egiptio)",
"ar-iq": "la araba (Irako)",
"ar-jo": "la araba (Jordanio)",
"ar-kw": "la araba (Kuvayto)",
"ar-lb": "la araba (Libano)",
"ar-ly": "la araba (Libio)",
"ar-ma": "la araba (Maroko)",
"ar-om": "la araba (Omano)",
"ar-qa": "la araba (Kataro)",
"ar-sa": "la araba (Sauda Arabio)"
}

View file

@ -1,124 +1,4 @@
{ {
"af": "Afrikáans",
"ar-ae": "Árabe (Emiratos Árabes Unidos)",
"ar-bh": "Árabe (Baréin)",
"ar-dz": "Árabe (Argelia)",
"ar-eg": "Árabe (Egipto)",
"ar-iq": "Árabe (Irak)",
"ar-jo": "Árabe (Jordania)",
"ar-kw": "Árabe (Kuwait)",
"ar-lb": "Árabe (Líbano)",
"ar-ly": "Árabe (Libia)",
"ar-ma": "Árabe (Marruecos)",
"ar-om": "Árabe (Omán)",
"ar-qa": "Árabe (Catar)",
"ar-sa": "Árabe (Arabia Saudita)",
"ar-sy": "Árabe (Siria)",
"ar-tn": "Árabe (Túnez)",
"ar-ye": "Árabe (Yemen)",
"be": "Bielorrusia",
"bg": "Bulgaria",
"ca": "Catalán",
"cs": "Checo",
"da": "Danés",
"de-at": "Alemán (Austria)",
"de-ch": "Alemán (Suiza)",
"de": "Alemán",
"de-li": "Alemán (Liechtenstein)",
"de-lu": "Alemán (Luxemburgo)",
"el": "Griego",
"en-au": "Inglés (Australia)",
"en-bz": "Inglés (Belice)",
"en-ca": "Inglés (Canadá)",
"en": "Inglés",
"en-gb": "Inglés (Reino Unido)",
"en-ie": "Inglés (Irlanda)",
"en-jm": "Inglés (Jamaica)",
"en-nz": "Inglés (Nueva Zelanda)",
"en-tt": "Inglés (Trinidad y Tobago)",
"en-us": "Inglés (Estados Unidos)",
"en-za": "Inglés (Sudáfrica)",
"es-ar": "Español (Argentina)",
"es-bo": "Español (Bolivia)",
"es-cl": "Español (Chile)",
"es-co": "Español (Colombia)",
"es-cr": "Español (Costa Rica)",
"es-do": "Español (República Dominicana)",
"es-ec": "Español (Ecuador)",
"es-gt": "Español (Guatemala)",
"es-hn": "Español (Honduras)",
"es-mx": "Español (México)",
"es-ni": "Español (Nicaragua)",
"es-pa": "Español (Panamá)",
"es-pe": "Español (Perú)",
"es-pr": "Español (Puerto Rico)",
"es-py": "Español (Paraguay)",
"es": "Español (España)",
"es-sv": "Español (El Salvador)",
"es-uy": "Español (Uruguay)",
"es-ve": "Español (Venezuela)",
"et": "Estonio",
"eu": "Vasco",
"fa": "Persa",
"fi": "Finés",
"fo": "Feroés",
"fr-be": "Francés (Bélgica)",
"fr-ca": "Francés (Canadá)",
"fr-ch": "Francés (Suiza)",
"fr": "Francés",
"fr-lu": "Francés (Luxemburgo)",
"ga": "Irlandés",
"gd": "Gaélico (Escocia)",
"he": "Hebreo",
"hi": "Hindi",
"hr": "Croata",
"hu": "Húngaro",
"id": "Indonesio",
"is": "Islandés",
"it-ch": "Italiano (Suiza)",
"it": "Italiano",
"ja": "Japonés",
"ji": "Yidis",
"ko": "Coreano",
"lt": "Lituano",
"lv": "Letón",
"mk": "Macedonio",
"ms": "Malayo",
"mt": "Maltés",
"nl-be": "Holandés (Bélgica)",
"nl": "Holandés",
"no": "Noruego",
"pl": "Polaco",
"pt-br": "Portugués (Brasil)",
"pt": "Portugués",
"rm": "Retorrománico",
"ro-mo": "Rumano (República de Moldavia)",
"ro": "Rumano",
"ru-mo": "Ruso (República de Moldavia)",
"ru": "Ruso",
"sb": "Sorbio",
"sk": "Eslovaco",
"sl": "Esloveno",
"sq": "Albanés",
"sr": "Serbio",
"sv-fi": "Sueco (Finlandia)",
"sv": "Sueco",
"sx": "Sotho",
"sz": "Sami (Lapón)",
"th": "Tailandés",
"tn": "Setsuana",
"tr": "Turco",
"ts": "Songa",
"uk": "Ucraniano",
"ur": "Urdú",
"ve": "Venda",
"vi": "Vietnamita",
"xh": "Josa",
"zh-cn": "Chino Mandarín",
"zh-hk": "Chino (Hong Kong RAE)",
"zh-sg": "Chino (Singapur)",
"zh-tw": "Chino (Taiwanés)",
"zu": "Zulú",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Un mensaje de texto ha sido enviado a +%(msisdn)s. Por favor ingrese el código de verificación que lo contiene", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Un mensaje de texto ha sido enviado a +%(msisdn)s. Por favor ingrese el código de verificación que lo contiene",
"accept": "Aceptar", "accept": "Aceptar",
"%(targetName)s accepted an invitation.": "%(targetName)s ha aceptado una invitación.", "%(targetName)s accepted an invitation.": "%(targetName)s ha aceptado una invitación.",
@ -132,25 +12,19 @@
"Algorithm": "Algoritmo", "Algorithm": "Algoritmo",
"Always show message timestamps": "Siempre mostrar la hora del mensaje", "Always show message timestamps": "Siempre mostrar la hora del mensaje",
"Authentication": "Autenticación", "Authentication": "Autenticación",
"all room members": "Todos los miembros de la sala",
"all room members, from the point they are invited": "Todos los miembros de la sala, desde el momento en que son invitados",
"all room members, from the point they joined": "Todos los miembros de la sala, desde el momento en que se han unido",
"an address": "una dirección", "an address": "una dirección",
"and": "y", "and": "y",
"%(items)s and %(remaining)s others": "%(items)s y %(remaining)s otros", "%(items)s and %(remaining)s others": "%(items)s y %(remaining)s otros",
"%(items)s and one other": "%(items)s y otro", "%(items)s and one other": "%(items)s y otro",
"%(items)s and %(lastItem)s": "%(items)s y %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s y %(lastItem)s",
"and %(count)s others...": { "and %(count)s others...|other": "y %(count)s otros...",
"other": "y %(count)s otros...", "and %(count)s others...|one": "y otro...",
"one": "y otro..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s y %(lastPerson)s están escribiendo", "%(names)s and %(lastPerson)s are typing": "%(names)s y %(lastPerson)s están escribiendo",
"%(names)s and one other are typing": "%(names)s y otro están escribiendo", "%(names)s and one other are typing": "%(names)s y otro están escribiendo",
"%(names)s and %(count)s others are typing": "%(names)s y %(count)s otros están escribiendo", "%(names)s and %(count)s others are typing": "%(names)s y %(count)s otros están escribiendo",
"An email has been sent to": "Un correo ha sido enviado a", "An email has been sent to": "Un correo ha sido enviado a",
"A new password must be entered.": "Una nueva clave debe ser ingresada.", "A new password must be entered.": "Una nueva clave debe ser ingresada.",
"%(senderName)s answered the call.": "%(senderName)s atendió la llamada.", "%(senderName)s answered the call.": "%(senderName)s atendió la llamada.",
"anyone": "nadie",
"An error has occurred.": "Un error ha ocurrido.", "An error has occurred.": "Un error ha ocurrido.",
"Anyone who knows the room's link, apart from guests": "Cualquiera que sepa el enlace de la sala, salvo invitados", "Anyone who knows the room's link, apart from guests": "Cualquiera que sepa el enlace de la sala, salvo invitados",
"Anyone who knows the room's link, including guests": "Cualquiera que sepa del enlace de la sala, incluyendo los invitados", "Anyone who knows the room's link, including guests": "Cualquiera que sepa del enlace de la sala, incluyendo los invitados",
@ -164,7 +38,7 @@
"Banned users": "Usuarios bloqueados", "Banned users": "Usuarios bloqueados",
"Bans user with given id": "Bloquear usuario por ID", "Bans user with given id": "Bloquear usuario por ID",
"Blacklisted": "En lista negra", "Blacklisted": "En lista negra",
"Bug Report": "Reporte de error", "Bug Report": "Reporte de fallo",
"Bulk Options": "Opciones masivas", "Bulk Options": "Opciones masivas",
"Call Timeout": "Tiempo de espera de la llamada", "Call Timeout": "Tiempo de espera de la llamada",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "No se puede conectar con el servidor - Por favor verifique su conexión y asegúrese de que su <a>certificado SSL del servidor</a> sea confiable.", "Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "No se puede conectar con el servidor - Por favor verifique su conexión y asegúrese de que su <a>certificado SSL del servidor</a> sea confiable.",
@ -178,7 +52,6 @@
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s ha cambiado el tema de la sala a \"%(topic)s\".", "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s ha cambiado el tema de la sala a \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Cambios para quien pueda leer el historial solo serán aplicados a futuros mensajes en la sala", "Changes to who can read history will only apply to future messages in this room": "Cambios para quien pueda leer el historial solo serán aplicados a futuros mensajes en la sala",
"Changes your display nickname": "Cambia la visualización de tu apodo", "Changes your display nickname": "Cambia la visualización de tu apodo",
"changing room on a RoomView is not supported": "cambiando la sala en un RoomView no esta soportado",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "El cambio de contraseña restablecerá actualmente todas las claves de cifrado de extremo a extremo de todos los dispositivos, haciendo que el historial de chat cifrado sea ilegible, a menos que primero exporte las claves de la habitación y vuelva a importarlas después. En el futuro esto será mejorado.", "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "El cambio de contraseña restablecerá actualmente todas las claves de cifrado de extremo a extremo de todos los dispositivos, haciendo que el historial de chat cifrado sea ilegible, a menos que primero exporte las claves de la habitación y vuelva a importarlas después. En el futuro esto será mejorado.",
"Claimed Ed25519 fingerprint key": "Clave Ed25519 es necesaria", "Claimed Ed25519 fingerprint key": "Clave Ed25519 es necesaria",
"Clear Cache and Reload": "Borrar caché y recargar", "Clear Cache and Reload": "Borrar caché y recargar",
@ -218,7 +91,6 @@
"Device ID": "ID del dispositivo", "Device ID": "ID del dispositivo",
"Devices": "Dispositivos", "Devices": "Dispositivos",
"Devices will not yet be able to decrypt history from before they joined the room": "Los dispositivos aun no serán capaces de descifrar el historial antes de haberse unido a la sala", "Devices will not yet be able to decrypt history from before they joined the room": "Los dispositivos aun no serán capaces de descifrar el historial antes de haberse unido a la sala",
"Direct Chat": "Conversación directa",
"Direct chats": "Conversaciones directas", "Direct chats": "Conversaciones directas",
"Disable inline URL previews by default": "Desactivar previsualización de enlaces por defecto", "Disable inline URL previews by default": "Desactivar previsualización de enlaces por defecto",
"Disinvite": "Deshacer invitación", "Disinvite": "Deshacer invitación",
@ -251,7 +123,6 @@
"Failed to delete device": "Falló al borrar el dispositivo", "Failed to delete device": "Falló al borrar el dispositivo",
"Failed to forget room %(errCode)s": "Falló al olvidar la sala %(errCode)s", "Failed to forget room %(errCode)s": "Falló al olvidar la sala %(errCode)s",
"Failed to join room": "Falló al unirse a la sala", "Failed to join room": "Falló al unirse a la sala",
"Failed to join the room": "Falló al unirse a la sala",
"Failed to kick": "Falló al expulsar", "Failed to kick": "Falló al expulsar",
"Failed to leave room": "Falló al dejar la sala", "Failed to leave room": "Falló al dejar la sala",
"Failed to load timeline position": "Falló al cargar el historico", "Failed to load timeline position": "Falló al cargar el historico",
@ -281,10 +152,6 @@
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Por seguridad, al cerrar la sesión borrará cualquier clave de encriptación de extremo a extremo en este navegador. Si quieres ser capaz de descifrar tu historial de conversación, para las futuras sesiones en Riot, por favor exporta las claves de la sala para protegerlas.", "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Por seguridad, al cerrar la sesión borrará cualquier clave de encriptación de extremo a extremo en este navegador. Si quieres ser capaz de descifrar tu historial de conversación, para las futuras sesiones en Riot, por favor exporta las claves de la sala para protegerlas.",
"Found a bug?": "¿Encontraste un error?", "Found a bug?": "¿Encontraste un error?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s a %(toPowerLevel)s", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s a %(toPowerLevel)s",
"Guests can't set avatars. Please register.": "Invitados no puedes establecer avatares. Por favor regístrate.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Usuarios invitados no pueden crear nuevas salas. Por favor regístrate para crear la sala y iniciar la conversación.",
"Guest users can't upload files. Please register to upload.": "Usuarios invitados no puedes subir archivos. Por favor regístrate para subir tus archivos.",
"Guests can't use labs features. Please register.": "Invitados no puedes usar las características en desarrollo. Por favor regístrate.",
"Guests cannot join this room even if explicitly invited.": "Invitados no pueden unirse a esta sala aun cuando han sido invitados explícitamente.", "Guests cannot join this room even if explicitly invited.": "Invitados no pueden unirse a esta sala aun cuando han sido invitados explícitamente.",
"had": "tuvo", "had": "tuvo",
"Hangup": "Colgar", "Hangup": "Colgar",
@ -308,7 +175,7 @@
"is a": "es un", "is a": "es un",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' no es un formato válido para una dirección", "'%(alias)s' is not a valid format for an address": "'%(alias)s' no es un formato válido para una dirección",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' no es un formato válido para un alias", "'%(alias)s' is not a valid format for an alias": "'%(alias)s' no es un formato válido para un alias",
"%(displayName)s is typing": "%(displayName)s esta escribiendo", "%(displayName)s is typing": "%(displayName)s está escribiendo",
"Sign in with": "Quiero iniciar sesión con", "Sign in with": "Quiero iniciar sesión con",
"Join Room": "Unirte a la sala", "Join Room": "Unirte a la sala",
"joined and left": "unido y dejado", "joined and left": "unido y dejado",
@ -331,7 +198,7 @@
"Low priority": "Baja prioridad", "Low priority": "Baja prioridad",
"Accept": "Aceptar", "Accept": "Aceptar",
"Add": "Añadir", "Add": "Añadir",
"Admin tools": "Herramientas de administración", "Admin Tools": "Herramientas de administración",
"VoIP": "Voz IP", "VoIP": "Voz IP",
"No Microphones detected": "No se ha detectado micrófono", "No Microphones detected": "No se ha detectado micrófono",
"No Webcams detected": "No se ha detectado cámara", "No Webcams detected": "No se ha detectado cámara",
@ -343,8 +210,8 @@
"Anyone": "Cualquiera", "Anyone": "Cualquiera",
"<a>Click here</a> to join the discussion!": "¡<a>Pulse aquí</a> para unirse a la conversación!", "<a>Click here</a> to join the discussion!": "¡<a>Pulse aquí</a> para unirse a la conversación!",
"Close": "Cerrar", "Close": "Cerrar",
"%(count)s new messages.one": "%(count)s mensaje nuevo", "%(count)s new messages|one": "%(count)s mensaje nuevo",
"%(count)s new messages.other": "%(count)s mensajes nuevos", "%(count)s new messages|other": "%(count)s mensajes nuevos",
"Create a new chat or reuse an existing one": "Cree una nueva conversación o reutilice una existente", "Create a new chat or reuse an existing one": "Cree una nueva conversación o reutilice una existente",
"Custom": "Personalizado", "Custom": "Personalizado",
"Custom level": "Nivel personalizado", "Custom level": "Nivel personalizado",
@ -377,7 +244,11 @@
"Jump to first unread message.": "Ir al primer mensaje sin leer.", "Jump to first unread message.": "Ir al primer mensaje sin leer.",
"Last seen": "Visto por última vez", "Last seen": "Visto por última vez",
"Level:": "Nivel:", "Level:": "Nivel:",
"%(senderName)s made future room history visible to": "%(senderName)s ha configurado el historial de la sala visible para", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s ha configurado el historial de la sala visible para Todos los miembros de la sala, desde el momento en que son invitados.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s ha configurado el historial de la sala visible para Todos los miembros de la sala, desde el momento en que se han unido.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s ha configurado el historial de la sala visible para Todos los miembros de la sala.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s ha configurado el historial de la sala visible para nadie.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s ha configurado el historial de la sala visible para desconocido (%(visibility)s).",
"a room": "una sala", "a room": "una sala",
"Something went wrong!": "¡Algo ha fallado!", "Something went wrong!": "¡Algo ha fallado!",
"were banned": "fueron expulsados", "were banned": "fueron expulsados",
@ -400,7 +271,6 @@
"%(oneUser)schanged their avatar": "%(oneUser)s cambió su avatar", "%(oneUser)schanged their avatar": "%(oneUser)s cambió su avatar",
"Please select the destination room for this message": "Por favor, seleccione la sala destino para este mensaje", "Please select the destination room for this message": "Por favor, seleccione la sala destino para este mensaje",
"Create new room": "Crear nueva sala", "Create new room": "Crear nueva sala",
"Welcome page": "Página de bienvenida",
"Start chat": "Comenzar chat", "Start chat": "Comenzar chat",
"New Password": "Nueva contraseña", "New Password": "Nueva contraseña",
"Analytics": "Analíticas", "Analytics": "Analíticas",
@ -415,7 +285,6 @@
"You must join the room to see its files": "Debe unirse a la sala para ver los ficheros", "You must join the room to see its files": "Debe unirse a la sala para ver los ficheros",
"Reject all %(invitedRooms)s invites": "Rechazar todas las invitaciones a %(invitedRooms)s", "Reject all %(invitedRooms)s invites": "Rechazar todas las invitaciones a %(invitedRooms)s",
"Start new chat": "Iniciar una nueva conversación", "Start new chat": "Iniciar una nueva conversación",
"Guest users can't invite users. Please register.": "Los invitados no pueden invitar a otros usuarios. Por favor, regístrese.",
"Failed to invite": "Fallo en la invitación", "Failed to invite": "Fallo en la invitación",
"Failed to invite user": "No se pudo invitar al usuario", "Failed to invite user": "No se pudo invitar al usuario",
"Failed to invite the following users to the %(roomName)s room:": "No se pudo invitar a los siguientes usuarios a la sala %(roomName)s:", "Failed to invite the following users to the %(roomName)s room:": "No se pudo invitar a los siguientes usuarios a la sala %(roomName)s:",
@ -442,7 +311,6 @@
"Scroll to unread messages": "Ir al primer mensaje sin leer", "Scroll to unread messages": "Ir al primer mensaje sin leer",
"Search": "Búsqueda", "Search": "Búsqueda",
"Search failed": "Falló la búsqueda", "Search failed": "Falló la búsqueda",
"Searching known users": "Buscando usuarios conocidos",
"Seen by %(userName)s at %(dateTime)s": "Visto por %(userName)s el %(dateTime)s", "Seen by %(userName)s at %(dateTime)s": "Visto por %(userName)s el %(dateTime)s",
"Send a message (unencrypted)": "Enviar un mensaje (sin cifrar)", "Send a message (unencrypted)": "Enviar un mensaje (sin cifrar)",
"Send an encrypted message": "Enviar un mensaje cifrado", "Send an encrypted message": "Enviar un mensaje cifrado",
@ -480,7 +348,6 @@
"Start Chat": "Comenzar la conversación", "Start Chat": "Comenzar la conversación",
"Submit": "Enviar", "Submit": "Enviar",
"Success": "Éxito", "Success": "Éxito",
"tag as %(tagName)s": "etiquetar como %(tagName)s",
"tag direct chat": "etiquetar la conversación directa", "tag direct chat": "etiquetar la conversación directa",
"Tagged as: ": "Etiquetado como: ", "Tagged as: ": "Etiquetado como: ",
"The default role for new room members is": "El nivel por defecto para los nuevos miembros de esta sala es", "The default role for new room members is": "El nivel por defecto para los nuevos miembros de esta sala es",
@ -496,11 +363,9 @@
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "No se puede conectar al servidor - compruebe su conexión, asegúrese de que el <a>certificado SSL del servidor</a> es de confiaza, y compruebe que no hay extensiones del navegador bloqueando las peticiones.", "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "No se puede conectar al servidor - compruebe su conexión, asegúrese de que el <a>certificado SSL del servidor</a> es de confiaza, y compruebe que no hay extensiones del navegador bloqueando las peticiones.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s ha quitado el nombre de la sala.", "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s ha quitado el nombre de la sala.",
"Device key:": "Clave del dispositivo:", "Device key:": "Clave del dispositivo:",
"Disable markdown formatting": "Desactivar el formato Markdown",
"Drop File Here": "Deje el fichero aquí", "Drop File Here": "Deje el fichero aquí",
"Guest access is disabled on this Home Server.": "El acceso de invitados está desactivado en este servidor.", "Guest access is disabled on this Home Server.": "El acceso de invitados está desactivado en este servidor.",
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Conecte con <voiceText>voz</voiceText> o <videoText>vídeo</videoText>.", "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Conecte con <voiceText>voz</voiceText> o <videoText>vídeo</videoText>.",
"List this room in %(domain)s's room directory?": "¿Mostrar esta sala en el directorio de %(domain)s?",
"Manage Integrations": "Gestionar integraciones", "Manage Integrations": "Gestionar integraciones",
"Markdown is disabled": "Markdown está desactivado", "Markdown is disabled": "Markdown está desactivado",
"Markdown is enabled": "Markdown está activado", "Markdown is enabled": "Markdown está activado",
@ -549,9 +414,7 @@
"Phone": "Teléfono", "Phone": "Teléfono",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s ha hecho una llamada de tipo %(callType)s.", "%(senderName)s placed a %(callType)s call.": "%(senderName)s ha hecho una llamada de tipo %(callType)s.",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Por favor, compruebe su e-mail y pulse el enlace que contiene. Una vez esté hecho, pulse continuar.", "Please check your email and click on the link it contains. Once this is done, click continue.": "Por favor, compruebe su e-mail y pulse el enlace que contiene. Una vez esté hecho, pulse continuar.",
"Please Register": "Por favor, regístrese",
"Power level must be positive integer.": "El nivel debe ser un entero positivo.", "Power level must be positive integer.": "El nivel debe ser un entero positivo.",
"Press": "Pulse",
"Privacy warning": "Alerta de privacidad", "Privacy warning": "Alerta de privacidad",
"Private Chat": "Conversación privada", "Private Chat": "Conversación privada",
"Privileged Users": "Usuarios con privilegios", "Privileged Users": "Usuarios con privilegios",
@ -587,7 +450,6 @@
"Server may be unavailable or overloaded": "El servidor podría estar saturado o desconectado", "Server may be unavailable or overloaded": "El servidor podría estar saturado o desconectado",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Mostrar el tiempo en formato 12h (am/pm)", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Mostrar el tiempo en formato 12h (am/pm)",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "La clave de firma que usted ha proporcionado coincide con la recibida del dispositivo %(deviceId)s de %(userId)s. Dispositivo verificado.", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "La clave de firma que usted ha proporcionado coincide con la recibida del dispositivo %(deviceId)s de %(userId)s. Dispositivo verificado.",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Esto no puede ser hecho por un invitado. Por favor, regístrese para poder hacerlo.",
"This email address is already in use": "Dirección e-mail en uso", "This email address is already in use": "Dirección e-mail en uso",
"This email address was not found": "Dirección e-mail no encontrada", "This email address was not found": "Dirección e-mail no encontrada",
"%(actionVerb)s this person?": "¿%(actionVerb)s a esta persona?", "%(actionVerb)s this person?": "¿%(actionVerb)s a esta persona?",
@ -597,7 +459,6 @@
"The remote side failed to pick up": "El sitio remoto falló al sincronizar", "The remote side failed to pick up": "El sitio remoto falló al sincronizar",
"This Home Server does not support login using email address.": "Este servidor no permite identificarse con direcciones e-mail.", "This Home Server does not support login using email address.": "Este servidor no permite identificarse con direcciones e-mail.",
"This invitation was sent to an email address which is not associated with this account:": "Se envió la invitación a un e-mail no asociado con esta cuenta:", "This invitation was sent to an email address which is not associated with this account:": "Se envió la invitación a un e-mail no asociado con esta cuenta:",
"There was a problem logging in.": "Hubo un problema identificándose.",
"This room has no local addresses": "Esta sala no tiene direcciones locales", "This room has no local addresses": "Esta sala no tiene direcciones locales",
"This room is not recognised.": "Esta sala no se reconoce.", "This room is not recognised.": "Esta sala no se reconoce.",
"These are experimental features that may break in unexpected ways": "Estas son funcionalidades experimentales, podrían fallar de formas imprevistas", "These are experimental features that may break in unexpected ways": "Estas son funcionalidades experimentales, podrían fallar de formas imprevistas",
@ -634,7 +495,6 @@
"Thursday": "Jueves", "Thursday": "Jueves",
"Friday": "Viernes", "Friday": "Viernes",
"Saturday": "Sábado", "Saturday": "Sábado",
"New Composer & Autocomplete": "Nuevo compositor & Autocompletar",
"Start verification": "Comenzar la verificación", "Start verification": "Comenzar la verificación",
"Skip": "Saltar", "Skip": "Saltar",
"To return to your account in future you need to set a password": "Para volver a usar su cuenta en el futuro es necesario que establezca una contraseña", "To return to your account in future you need to set a password": "Para volver a usar su cuenta en el futuro es necesario que establezca una contraseña",
@ -648,6 +508,147 @@
"To send events of type": "Para enviar eventos de tipo", "To send events of type": "Para enviar eventos de tipo",
"To send messages": "Para enviar mensajes", "To send messages": "Para enviar mensajes",
"to start a chat with someone": "para empezar a charlar con alguien", "to start a chat with someone": "para empezar a charlar con alguien",
"to tag as %(tagName)s": "para etiquetar como %(tagName)s", "to tag direct chat": "para etiquetar como charla directa",
"to tag direct chat": "para etiquetar como charla directa" "Add a widget": "Añadir widget",
"Allow": "Permitir",
"Changes colour scheme of current room": "Cambia el esquema de colores de esta sala",
"Delete widget": "Eliminar widget",
"Define the power level of a user": "Definir el nivel de poder de los usuarios",
"Edit": "Editar",
"Enable automatic language detection for syntax highlighting": "Activar la detección automática del lenguaje para resaltar la sintaxis",
"Hide Apps": "Ocultar aplicaciones",
"Hide join/leave messages (invites/kicks/bans unaffected)": "Ocultar mensajes de entrada/salida (no afecta invitaciones/kicks/bans)",
"Hide avatar and display name changes": "Ocultar cambios de avatar y nombre visible",
"Matrix Apps": "Aplicaciones Matrix",
"Once you've followed the link it contains, click below": "Cuando haya seguido el enlace que contiene, haga click debajo",
"Sets the room topic": "Configura el tema de la sala",
"Show Apps": "Mostrar aplicaciones",
"To get started, please pick a username!": "Para empezar, ¡por favor elija un nombre de usuario!",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Se ha intentado cargar cierto punto en la cronología de esta sala, pero no tiene permiso para ver el mensaje solicitado.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Se ha intentado cargar cierto punto en la cronología de esta sala, pero no se ha podido encontrarlo.",
"Turn Markdown off": "Desactivar markdown",
"Turn Markdown on": "Activar markdown",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s ha activado el cifrado de extremo-a-extremo (algorithm %(algorithm)s).",
"Unable to add email address": "No se ha podido añadir la dirección de correo electrónico",
"Unable to create widget.": "No se ha podido crear el widget.",
"Unable to remove contact information": "No se ha podido eliminar la información de contacto",
"Unable to verify email address.": "No se ha podido verificar la dirección de correo electrónico.",
"Unban": "Revocar bloqueo",
"Unbans user with given id": "Revoca el bloqueo del usuario con la identificación dada",
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "No se ha podido asegurar que la dirección a la que se envió esta invitación, coincide con una asociada a su cuenta.",
"Unable to capture screen": "No se ha podido capturar la pantalla",
"Unable to enable Notifications": "No se ha podido activar las notificaciones",
"Unable to load device list": "No se ha podido cargar la lista de dispositivos",
"Undecryptable": "No se puede descifrar",
"Unencrypted room": "Sala sin cifrado",
"Unencrypted message": "Mensaje no cifrado",
"unknown caller": "Persona que llama desconocida",
"unknown device": "dispositivo desconocido",
"Unknown room %(roomId)s": "Sala desconocida %(roomId)s",
"Unknown (user, device) pair:": "Pareja desconocida (usuario, dispositivo):",
"Unnamed Room": "Sala sin nombre",
"Unverified": "Sin verificar",
"Uploading %(filename)s and %(count)s others|zero": "Subiendo %(filename)s",
"Uploading %(filename)s and %(count)s others|one": "Subiendo %(filename)s y %(count)s otros",
"Uploading %(filename)s and %(count)s others|other": "Subiendo %(filename)s y %(count)s otros",
"Upload avatar": "Subir avatar",
"Upload Failed": "Error al subir",
"Upload Files": "Subir archivos",
"Upload file": "Subir archivo",
"Upload new:": "Subir nuevo:",
"Usage": "Uso",
"Use compact timeline layout": "Usar diseño de cronología compacto",
"Use with caution": "Usar con precaución",
"User ID": "Identificación de usuario",
"User Interface": "Interfaz de usuario",
"%(user)s is a": "%(user)s es un",
"User name": "Nombre de usuario",
"Username invalid: %(errMessage)s": "Nombre de usuario no válido: %(errMessage)s",
"Users": "Usuarios",
"User": "Usuario",
"Verification Pending": "Verificación pendiente",
"Verification": "Verificación",
"verified": "verificado",
"Verified": "Verificado",
"Verified key": "Clave verificada",
"Video call": "Llamada de vídeo",
"Voice call": "Llamada de voz",
"VoIP conference finished.": "Conferencia VoIP terminada.",
"VoIP conference started.": "Conferencia de VoIP iniciada.",
"VoIP is unsupported": "No hay soporte para VoIP",
"(could not connect media)": "(no se ha podido conectar medio)",
"(no answer)": "(sin respuesta)",
"(unknown failure: %(reason)s)": "(error desconocido: %(reason)s)",
"(warning: cannot be disabled again!)": "(aviso: ¡no se puede volver a desactivar!)",
"Warning!": "¡Advertencia!",
"WARNING: Device already verified, but keys do NOT MATCH!": "AVISO: Dispositivo ya verificado, ¡pero las claves NO COINCIDEN!",
"Who can access this room?": "¿Quién puede acceder a esta sala?",
"Who can read history?": "¿Quién puede leer el historial?",
"Who would you like to add to this room?": "¿A quién quiere añadir a esta sala?",
"Who would you like to communicate with?": "¿Con quién quiere comunicar?",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s ha retirado la invitación de %(targetName)s.",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "¿Quiere <acceptText>aceptar</acceptText> o <declineText>rechazar</declineText> esta invitación?",
"You already have existing direct chats with this user:": "Ya tiene chats directos con este usuario:",
"You are already in a call.": "Ya está participando en una llamada.",
"You are not in this room.": "Usted no está en esta sala.",
"You do not have permission to do that in this room.": "No tiene permiso para hacer esto en esta sala.",
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "¡Todavía no participa en ninguna sala! Pulsa <CreateRoomButton> para crear una sala o <RoomDirectoryButton> para explorar el directorio",
"You are trying to access %(roomName)s.": "Está tratando de acceder a %(roomName)s.",
"You cannot place a call with yourself.": "No puede iniciar una llamada con usted mismo.",
"Cannot add any more widgets": "no es posible agregar mas widgets",
"Do you want to load widget from URL:": "desea cargar widget desde URL:",
"Integrations Error": "error de integracion",
"Publish this room to the public in %(domain)s's room directory?": "Desea publicar esta sala al publico en el directorio de sala de %(domain)s?",
"AM": "AM",
"PM": "PM",
"NOTE: Apps are not end-to-end encrypted": "NOTA: Las Apps no son cifradas de extremo a extremo",
"Revoke widget access": "Revocar acceso del widget",
"The maximum permitted number of widgets have already been added to this room.": "La cantidad máxima de widgets permitida ha sido alcanzada en esta sala.",
"To use it, just wait for autocomplete results to load and tab through them.": "Para usar, solo espere a que carguen los resultados de auto-completar y navegue entre ellos.",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s levanto la suspensión de %(targetName)s.",
"unencrypted": "no cifrado",
"Unmute": "desactivar el silencio",
"Unrecognised command:": "comando no reconocido:",
"Unrecognised room alias:": "alias de sala no reconocido:",
"uploaded a file": "cargo un archivo",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (nivel de permisos %(powerLevelNumber)s)",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "Atención: VERIFICACIÓN DE CLAVE FALLO\" La clave de firma para %(userId)s y el dispositivo %(deviceId)s es \"%(fprint)s\" la cual no concuerda con la clave provista por \"%(fingerprint)s\". Esto puede significar que sus comunicaciones están siendo interceptadas!",
"You cannot place VoIP calls in this browser.": "no puede realizar llamadas de voz en este navegador.",
"You do not have permission to post to this room": "no tiene permiso para publicar en esta sala",
"You have been banned from %(roomName)s by %(userName)s.": "Ha sido expulsado de %(roomName)s por %(userName)s.",
"You have been invited to join this room by %(inviterName)s": "Ha sido invitado a entrar a esta sala por %(inviterName)s",
"You have been kicked from %(roomName)s by %(userName)s.": "Ha sido removido de %(roomName)s por %(userName)s.",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "Ha sido desconectado de todos los dispositivos y no continuara recibiendo notificaciones. Para volver a habilitar las notificaciones, vuelva a conectarse en cada dispositivo",
"You have <a>disabled</a> URL previews by default.": "Ha <a>deshabilitado</a> la vista previa de URL por defecto.",
"You have <a>enabled</a> URL previews by default.": "Ha <a>habilitado</a> vista previa de URL por defecto.",
"You have no visible notifications": "No tiene notificaciones visibles",
"You may wish to login with a different account, or add this email to this account.": "Puede ingresar con una cuenta diferente, o agregar este e-mail a esta cuenta.",
"you must be a": "usted debe ser un",
"You must <a>register</a> to use this functionality": "Usted debe ser un <a>registrar</a> para usar esta funcionalidad",
"You need to be able to invite users to do that.": "Usted debe ser capaz de invitar usuarios para hacer eso.",
"You need to be logged in.": "Necesita estar autenticado.",
"You need to enter a user name.": "Tiene que ingresar un nombre de usuario.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Su e-mail parece no estar asociado con una Id Matrix en este Homeserver.",
"Your password has been reset": "Su contraseña ha sido restablecida",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Su contraseña a sido cambiada exitosamente. No recibirá notificaciones en otros dispositivos hasta que ingrese de nuevo en ellos",
"You seem to be in a call, are you sure you want to quit?": "Parece estar en medio de una llamada, ¿esta seguro que desea salir?",
"You seem to be uploading files, are you sure you want to quit?": "Parece estar cargando archivos, ¿esta seguro que desea salir?",
"You should not yet trust it to secure data": "No debería confiarle aun para asegurar su información",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "No podrá revertir este cambio ya que esta promoviendo al usuario para tener el mismo nivel de autoridad que usted.",
"Your home server does not support device management.": "Su servidor privado no suporta la gestión de dispositivos.",
"Sun": "Dom",
"Mon": "Lun",
"Tue": "Mar",
"Wed": "Mie",
"Thu": "Jue",
"Fri": "Vie",
"Sat": "Sab",
"Jan": "Ene",
"Feb": "Feb",
"Mar": "Mar",
"Apr": "Abr",
"May": "May",
"Jun": "Jun",
"Jul": "Jul",
"Aug": "August"
} }

View file

@ -1,145 +1,20 @@
{ {
"af": "Afrikaans",
"ar-ae": "Arabiera (Arabiar Emirerri Batuak)",
"ar-bh": "Arabiera (Bahrain)",
"ar-dz": "Arabiera (Algeria)",
"ar-eg": "Arabiera (Egipto)",
"ar-iq": "Arabiera (Irak)",
"ar-jo": "Arabiera (Jordania)",
"ar-kw": "Arabiera (Kuwait)",
"ar-lb": "Arabiera (Libano)",
"ar-ly": "Arabiera (Libia)",
"ar-ma": "Arabiera (Maroko)",
"ar-om": "Arabiera (Oman)",
"ar-qa": "Arabiera (Qatar)",
"ar-sa": "Arabiera (Saudi Arabia)",
"Cancel": "Utzi",
"ar-sy": "Arabiera (Siria)",
"ar-tn": "Arabiera (Tunisia)",
"ar-ye": "Arabiera (Yemen)",
"be": "Bielorrusiera",
"bg": "Bulgariera",
"ca": "Katalana",
"cs": "Txekiera",
"da": "Daniera",
"de-at": "Alemana (Austria)",
"de-ch": "Alemana (Suitza)",
"de": "Alemana",
"de-li": "Alemana (Liechtenstein)",
"de-lu": "Alemana (Luxenburgo)",
"el": "Greziera",
"en-au": "Ingelesa (Australia)",
"en-bz": "Ingelesa (Belize)",
"en-ca": "Ingelesa (Kanada)",
"en": "Ingelesa",
"en-gb": "Ingelesa (Erresuma batua)",
"en-ie": "Ingelesa (Irlanda)",
"en-jm": "Ingelesa (Jamaika)",
"en-nz": "Ingelesa (Zeelanda Berria)",
"en-tt": "Ingelesa (Trinidad)",
"en-us": "Ingelesa (Estatu Batuak)",
"en-za": "Ingelesa (Hego Afrika)",
"es-ar": "Espainiera (Argentina)",
"es-bo": "Espainiera (Bolivia)",
"es-cl": "Espainiera (Txile)",
"es-co": "Espainiera (Kolonbia)",
"es-cr": "Espainiera (Costa Rica)",
"es-do": "Espainiera (Dominikar Errepublika)",
"es-ec": "Espainiera (Ekuador)",
"es-gt": "Espainiera (Guatemala)",
"es-hn": "Espainiera (Honduras)",
"es-mx": "Espainiera (Mexiko)",
"es-ni": "Espainiera (Nikaragua)",
"es-pa": "Espainiera (Panama)",
"es-pe": "Espainiera (Peru)",
"es-pr": "Espainiera (Puerto Rico)",
"es-py": "Espainiera (Paraguay)",
"es": "Espainiera (Espainia)",
"es-sv": "Espainiera (El Salvador)",
"es-uy": "Espainiera (Uruguai)",
"es-ve": "Espainiera (Venezuela)",
"et": "Estoniera",
"eu": "Euskara",
"fa": "Farsiera",
"fi": "Finlandiera",
"fo": "Faroera",
"fr-be": "Frantsesa (Belgika)",
"fr-ca": "Frantsesa (Kanada)",
"fr-ch": "Frantsesa (Suitza)",
"fr": "Frantsesa",
"fr-lu": "Frantsesa (Luxenburgo)",
"ga": "Irlandera",
"gd": "Gaelikoa (Eskozia)",
"he": "Hebreera",
"hi": "Hindi",
"hr": "Kroaziera",
"hu": "Hungariera",
"id": "Indonesiera",
"is": "Islandiera",
"it-ch": "Italiera (Suitza)",
"it": "Italiera",
"ja": "Japoniera",
"ji": "Yiddish",
"ko": "Korearra",
"lt": "Lituaniera",
"lv": "Letoniera",
"mk": "Mazedoniera (FYROM)",
"ms": "Malaysiera",
"mt": "Maltera",
"nl-be": "Nederlandera (Belgika)",
"nl": "Nederlandera",
"no": "Norvegiera",
"pl": "Poloniera",
"pt-br": "Brasilgo portugalera",
"pt": "Portugalera",
"rm": "Erretorromaniera",
"ro-mo": "Errumaniera (Moldavia)",
"ro": "Errumaniera",
"ru-mo": "Errusiera (Moldavia)",
"ru": "Errusiera",
"sb": "Sorbiera",
"sk": "Eslovakiera",
"sl": "Esloveniera",
"sq": "Albaniera",
"sr": "Serbiera",
"sv-fi": "Suediera (Finlandia)",
"sv": "Suediera",
"sx": "Sutu",
"sz": "Sami (Laponiera)",
"th": "Thailandiera",
"tn": "Tswanera",
"tr": "Turkiera",
"ts": "Tsonga",
"uk": "Ukrainera",
"ur": "Urdu",
"ve": "Vendera",
"vi": "Vietnamera",
"xh": "Xhosera",
"zh-cn": "Txinera (PRC)",
"zh-hk": "Txinera (Hong Kong)",
"zh-sg": "Txinera (Singapur)",
"zh-tw": "Txinera (Taiwan)",
"zu": "Zulu",
"a room": "gela bat", "a room": "gela bat",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Mezu bat bidali da +%(msisdn)s zenbakira. Sartu hemen mezuko egiaztaketa kodea", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Mezu bat bidali da +%(msisdn)s zenbakira. Sartu hemen mezuko egiaztaketa kodea",
"Accept": "Onartu", "Accept": "Onartu",
"%(targetName)s accepted an invitation.": "%(targetName)s erabiltzaileak gonbidapena onartu du.", "%(targetName)s accepted an invitation.": "%(targetName)s erabiltzaileak gonbidapena onartu du.",
"Close": "Itxi", "Close": "Itxi",
"Create new room": "Sortu gela berria", "Create new room": "Sortu gela berria",
"Welcome page": "Ongi etorri orria",
"Continue": "Jarraitu", "Continue": "Jarraitu",
"Direct Chat": "Txat zuzena",
"Drop here %(toAction)s": "Jaregin hona %(toAction)s", "Drop here %(toAction)s": "Jaregin hona %(toAction)s",
"Error": "Errorea", "Error": "Errorea",
"Failed to change password. Is your password correct?": "Pasahitza aldatzean huts egin du. Zuzena da pasahitza?", "Failed to change password. Is your password correct?": "Pasahitza aldatzean huts egin du. Zuzena da pasahitza?",
"Failed to forget room %(errCode)s": "Huts egin du %(errCode)s gela ahaztean", "Failed to forget room %(errCode)s": "Huts egin du %(errCode)s gela ahaztean",
"Failed to join the room": "Huts egin du gelara elkartzean",
"Favourite": "Gogokoa", "Favourite": "Gogokoa",
"Mute": "Mututu", "Mute": "Mututu",
"Notifications": "Jakinarazpenak", "Notifications": "Jakinarazpenak",
"OK": "Ados", "OK": "Ados",
"Operation failed": "Eragiketak huts egin du", "Operation failed": "Eragiketak huts egin du",
"Please Register": "Erregistratu",
"Remove": "Kendu", "Remove": "Kendu",
"Search": "Bilatu", "Search": "Bilatu",
"Settings": "Ezarpenak", "Settings": "Ezarpenak",
@ -212,7 +87,6 @@
"Hide read receipts": "Ezkutatu irakurtze-agiria", "Hide read receipts": "Ezkutatu irakurtze-agiria",
"Don't send typing notifications": "Ez bidali idatzi bitarteko jakinarazpenak", "Don't send typing notifications": "Ez bidali idatzi bitarteko jakinarazpenak",
"Always show message timestamps": "Erakutsi beti mezuen denbora-zigilua", "Always show message timestamps": "Erakutsi beti mezuen denbora-zigilua",
"Disable markdown formatting": "Desgaitu markdown formatua",
"Name": "Izena", "Name": "Izena",
"Device Name": "Gailuaren izena", "Device Name": "Gailuaren izena",
"Last seen": "Azkenekoz ikusia", "Last seen": "Azkenekoz ikusia",
@ -274,7 +148,6 @@
"Success": "Arrakasta", "Success": "Arrakasta",
"For security, this session has been signed out. Please sign in again.": "Segurtasunagatik saio hau amaitu da. Hasi saioa berriro.", "For security, this session has been signed out. Please sign in again.": "Segurtasunagatik saio hau amaitu da. Hasi saioa berriro.",
"Found a bug?": "Akats bat aurkitu duzu?", "Found a bug?": "Akats bat aurkitu duzu?",
"Guests can't use labs features. Please register.": "Bisitariek ezin dituzte laborategiko ezaugarriak erabili. Erregistratu.",
"Guests cannot join this room even if explicitly invited.": "Bisitariak ezin dira gela honetara elkartu ez bazaie zuzenean gonbidatu.", "Guests cannot join this room even if explicitly invited.": "Bisitariak ezin dira gela honetara elkartu ez bazaie zuzenean gonbidatu.",
"Hangup": "Eseki", "Hangup": "Eseki",
"Homeserver is": "Hasiera zerbitzaria:", "Homeserver is": "Hasiera zerbitzaria:",
@ -288,7 +161,7 @@
"Add": "Gehitu", "Add": "Gehitu",
"Add a topic": "Gehitu gai bat", "Add a topic": "Gehitu gai bat",
"Admin": "Kudeatzailea", "Admin": "Kudeatzailea",
"Admin tools": "Kudeaketa tresnak", "Admin Tools": "Kudeaketa tresnak",
"And %(count)s more...": "Eta %(count)s gehiago...", "And %(count)s more...": "Eta %(count)s gehiago...",
"VoIP": "VoIP", "VoIP": "VoIP",
"Missing Media Permissions, click here to request.": "Media baimenak falta dira, egin klik eskatzeko.", "Missing Media Permissions, click here to request.": "Media baimenak falta dira, egin klik eskatzeko.",
@ -301,15 +174,11 @@
"Camera": "Kamera", "Camera": "Kamera",
"Hide removed messages": "Ezkutatu kendutako mezuak", "Hide removed messages": "Ezkutatu kendutako mezuak",
"Alias (optional)": "Ezizena (aukerazkoa)", "Alias (optional)": "Ezizena (aukerazkoa)",
"all room members": "gelako kide guztiak",
"all room members, from the point they are invited": "gelako kide guztiak, gonbidapena egiten zaienetik",
"all room members, from the point they joined": "gelako kide guztiak, elkartzen direnetik",
"and one other...": "eta beste bat...", "and one other...": "eta beste bat...",
"%(names)s and %(lastPerson)s are typing": "%(names)s eta %(lastPerson)s idazten ari dira", "%(names)s and %(lastPerson)s are typing": "%(names)s eta %(lastPerson)s idazten ari dira",
"%(names)s and one other are typing": "%(names)s eta beste inor idazten ari dira", "%(names)s and one other are typing": "%(names)s eta beste inor idazten ari dira",
"%(names)s and %(count)s others are typing": "%(names)s eta beste %(count)s idazten ari dira", "%(names)s and %(count)s others are typing": "%(names)s eta beste %(count)s idazten ari dira",
"An email has been sent to": "E-mail bat bidali da hona:", "An email has been sent to": "E-mail bat bidali da hona:",
"anyone": "edonor",
"An error has occurred.": "Errore bat gertatu da.", "An error has occurred.": "Errore bat gertatu da.",
"Are you sure?": "Ziur zaude?", "Are you sure?": "Ziur zaude?",
"Are you sure you want to leave the room '%(roomName)s'?": "Ziur '%(roomName)s' gela utzi nahi duzula?", "Are you sure you want to leave the room '%(roomName)s'?": "Ziur '%(roomName)s' gela utzi nahi duzula?",
@ -337,8 +206,8 @@
"Confirm password": "Berretsi pasahitza", "Confirm password": "Berretsi pasahitza",
"Conference calls are not supported in this client": "Bezero honek ez ditu konferentzia deiak onartzen", "Conference calls are not supported in this client": "Bezero honek ez ditu konferentzia deiak onartzen",
"Could not connect to the integration server": "Ezin izan da integrazio zerbitzarira konektatu", "Could not connect to the integration server": "Ezin izan da integrazio zerbitzarira konektatu",
"%(count)s new messages.one": "mezu berri %(count)s", "%(count)s new messages|one": "mezu berri %(count)s",
"%(count)s new messages.other": "%(count)s mezu berri", "%(count)s new messages|other": "%(count)s mezu berri",
"Create a new chat or reuse an existing one": "Sortu txat berria edo berrerabili aurreko bat", "Create a new chat or reuse an existing one": "Sortu txat berria edo berrerabili aurreko bat",
"Create an account": "Sortu kontua", "Create an account": "Sortu kontua",
"Create Room": "Sortu gela", "Create Room": "Sortu gela",
@ -370,7 +239,7 @@
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s erabiltzaileak gelaren izena kendu du.", "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s erabiltzaileak gelaren izena kendu du.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s erabiltzaileak gaia aldatu du beste honetara: \"%(topic)s\".", "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s erabiltzaileak gaia aldatu du beste honetara: \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Historiala irakurtzeko baimenen aldaketak gela honetara hemendik aurrera heldutako mezuei aplikatuko zaizkie", "Changes to who can read history will only apply to future messages in this room": "Historiala irakurtzeko baimenen aldaketak gela honetara hemendik aurrera heldutako mezuei aplikatuko zaizkie",
"Clear Cache and Reload": "Garbitu cache eta birkargatu", "Clear Cache and Reload": "Garbitu cachea eta birkargatu",
"Devices will not yet be able to decrypt history from before they joined the room": "Gailuek ezin izango dute taldera elkartu aurretiko historiala deszifratu", "Devices will not yet be able to decrypt history from before they joined the room": "Gailuek ezin izango dute taldera elkartu aurretiko historiala deszifratu",
"Disable inline URL previews by default": "Desgaitu URLen aurrebista lehenetsita", "Disable inline URL previews by default": "Desgaitu URLen aurrebista lehenetsita",
"Disinvite": "Kendu gonbidapena", "Disinvite": "Kendu gonbidapena",
@ -421,9 +290,6 @@
"Forgot your password?": "Pasahitza ahaztu duzu?", "Forgot your password?": "Pasahitza ahaztu duzu?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s %(fromPowerLevel)s mailatik %(toPowerLevel)s mailara", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s %(fromPowerLevel)s mailatik %(toPowerLevel)s mailara",
"Guest access is disabled on this Home Server.": "Bisitarien sarbidea desgaituta dago hasiera zerbitzari honetan.", "Guest access is disabled on this Home Server.": "Bisitarien sarbidea desgaituta dago hasiera zerbitzari honetan.",
"Guests can't set avatars. Please register.": "Bisitariek ezin dituzte abatarrak ezarri. Erregistratu zaitez.",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Bisitariek ezin dituzte gela berriak sortu. Erregistratu gela sortu eta txat bat hasteko.",
"Guest users can't upload files. Please register to upload.": "Bisitariek ezin dituzte fitxategiak igo. Erregistratu igotzeko.",
"Hide Text Formatting Toolbar": "Ezkutatu testu-formatuaren tresna-barra", "Hide Text Formatting Toolbar": "Ezkutatu testu-formatuaren tresna-barra",
"Incoming call from %(name)s": "%(name)s erabiltzailearen deia jasotzen", "Incoming call from %(name)s": "%(name)s erabiltzailearen deia jasotzen",
"Incoming video call from %(name)s": "%(name)s erabiltzailearen bideo deia jasotzen", "Incoming video call from %(name)s": "%(name)s erabiltzailearen bideo deia jasotzen",
@ -436,7 +302,6 @@
"%(senderName)s changed their profile picture.": "%(senderName)s erabiltzaileak bere profileko argazkia aldatu du.", "%(senderName)s changed their profile picture.": "%(senderName)s erabiltzaileak bere profileko argazkia aldatu du.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s erabiltzaileak %(powerLevelDiffText)s erabiltzailearen botere maila aldatu du.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s erabiltzaileak %(powerLevelDiffText)s erabiltzailearen botere maila aldatu du.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s erabiltzaileak gelaren izena aldatu du, orain %(roomName)s da.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s erabiltzaileak gelaren izena aldatu du, orain %(roomName)s da.",
"changing room on a RoomView is not supported": "ez da onartzen gela ikuspegi batean gelaz aldatzea",
"Drop here to tag %(section)s": "Jaregin hona %(section)s atalari etiketa jartzeko", "Drop here to tag %(section)s": "Jaregin hona %(section)s atalari etiketa jartzeko",
"Incoming voice call from %(name)s": "%(name)s erabiltzailearen deia jasotzen", "Incoming voice call from %(name)s": "%(name)s erabiltzailearen deia jasotzen",
"Incorrect username and/or password.": "Erabiltzaile-izen edo pasahitz okerra.", "Incorrect username and/or password.": "Erabiltzaile-izen edo pasahitz okerra.",
@ -464,11 +329,14 @@
"left": "atera da", "left": "atera da",
"%(targetName)s left the room.": "%(targetName)s erabiltzailea gelatik atera da.", "%(targetName)s left the room.": "%(targetName)s erabiltzailea gelatik atera da.",
"Level:": "Maila:", "Level:": "Maila:",
"List this room in %(domain)s's room directory?": "Gela hau %(domain)s's domeinuko gelen direktorioan zerrendatu?",
"Local addresses for this room:": "Gela honen tokiko helbideak:", "Local addresses for this room:": "Gela honen tokiko helbideak:",
"Logged in as:": "Saioa hasteko erabiltzailea:", "Logged in as:": "Saioa hasteko erabiltzailea:",
"Login as guest": "Hasi saioa bisitari gisa", "Login as guest": "Hasi saioa bisitari gisa",
"%(senderName)s made future room history visible to": "%(senderName)s erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du hauentzat:", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du hauentzat gelako kide guztiak, gonbidapena egiten zaienetik.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du hauentzat gelako kide guztiak, elkartzen direnetik.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du hauentzat gelako kide guztiak.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du hauentzat edonor.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du hauentzat ezezaguna (%(visibility)s).",
"Manage Integrations": "Kudeatu interakzioak", "Manage Integrations": "Kudeatu interakzioak",
"Markdown is disabled": "Markdown desgaituta dago", "Markdown is disabled": "Markdown desgaituta dago",
"Markdown is enabled": "Markdown gaituta dago", "Markdown is enabled": "Markdown gaituta dago",
@ -482,7 +350,6 @@
"Never send encrypted messages to unverified devices in this room": "Ez bidali inoiz zifratutako mezuak egiaztatu gabeko gailuetara gela honetan", "Never send encrypted messages to unverified devices in this room": "Ez bidali inoiz zifratutako mezuak egiaztatu gabeko gailuetara gela honetan",
"Never send encrypted messages to unverified devices in this room from this device": "Ez bidali inoiz zifratutako mezuak egiaztatu gabeko gailuetara gela honetan gailu honetatik", "Never send encrypted messages to unverified devices in this room from this device": "Ez bidali inoiz zifratutako mezuak egiaztatu gabeko gailuetara gela honetan gailu honetatik",
"New address (e.g. #foo:%(localDomain)s)": "Helbide berria (adib. #foo:%(localDomain)s)", "New address (e.g. #foo:%(localDomain)s)": "Helbide berria (adib. #foo:%(localDomain)s)",
"New Composer & Autocomplete": "Konposatzaile berria eta osatze automatikoa",
"New passwords don't match": "Pasahitz berriak ez datoz bat", "New passwords don't match": "Pasahitz berriak ez datoz bat",
"New passwords must match each other.": "Pasahitz berriak berdinak izan behar dira.", "New passwords must match each other.": "Pasahitz berriak berdinak izan behar dira.",
"not set": "ezarri gabe", "not set": "ezarri gabe",
@ -496,7 +363,7 @@
"No users have specific privileges in this room": "Ez dago gela honetan baimen zehatzik duen erabiltzailerik", "No users have specific privileges in this room": "Ez dago gela honetan baimen zehatzik duen erabiltzailerik",
"olm version:": "olm bertsioa:", "olm version:": "olm bertsioa:",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Behin gela batean zifratzea gaituta ezin da gero desgaitu (oraingoz)", "Once encryption is enabled for a room it cannot be turned off again (for now)": "Behin gela batean zifratzea gaituta ezin da gero desgaitu (oraingoz)",
"Once you&#39;ve followed the link it contains, click below": "Behin dakarren esteka jarraitu duzula, egin klik azpian", "Once you've followed the link it contains, click below": "Behin dakarren esteka jarraitu duzula, egin klik azpian",
"Otherwise, <a>click here</a> to send a bug report.": "Bestela, <a>bidali arazte-txosten bat</a>.", "Otherwise, <a>click here</a> to send a bug report.": "Bestela, <a>bidali arazte-txosten bat</a>.",
"Server may be unavailable, overloaded, or you hit a bug.": "Agian zerbitzaria ez dago eskuragarri, edo gainezka dago, edo akats bat aurkitu duzu.", "Server may be unavailable, overloaded, or you hit a bug.": "Agian zerbitzaria ez dago eskuragarri, edo gainezka dago, edo akats bat aurkitu duzu.",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Oraingoz pasahitza aldatzeak gailu guztietako muturretik muturrerako zifratze-gakoak berrezarriko ditu, eta ezin izango dituzu zifratutako txatetako historialak irakurri ez badituzu aurretik zure gelako gakoak esportatzen eta aldaketa eta gero berriro inportatzen. Etorkizunean hau hobetuko da.", "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Oraingoz pasahitza aldatzeak gailu guztietako muturretik muturrerako zifratze-gakoak berrezarriko ditu, eta ezin izango dituzu zifratutako txatetako historialak irakurri ez badituzu aurretik zure gelako gakoak esportatzen eta aldaketa eta gero berriro inportatzen. Etorkizunean hau hobetuko da.",
@ -505,7 +372,6 @@
"Permissions": "Baimenak", "Permissions": "Baimenak",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s erabiltzaileak %(callType)s dei bat hasi du.", "%(senderName)s placed a %(callType)s call.": "%(senderName)s erabiltzaileak %(callType)s dei bat hasi du.",
"Power level must be positive integer.": "Botere maila osoko zenbaki positibo bat izan behar da.", "Power level must be positive integer.": "Botere maila osoko zenbaki positibo bat izan behar da.",
"Press": "Sakatu",
"Press <StartChatButton> to start a chat with someone": "Sakatu <StartChatButton> norbaitekin txat bat hasteko", "Press <StartChatButton> to start a chat with someone": "Sakatu <StartChatButton> norbaitekin txat bat hasteko",
"Privacy warning": "Pribatutasun abisua", "Privacy warning": "Pribatutasun abisua",
"Private Chat": "Txat pribatua", "Private Chat": "Txat pribatua",
@ -531,7 +397,7 @@
"%(senderName)s removed their profile picture.": "%(senderName)s erabiltzaileak bere profileko argazkia kendu du.", "%(senderName)s removed their profile picture.": "%(senderName)s erabiltzaileak bere profileko argazkia kendu du.",
"Remove %(threePid)s?": "Kendu %(threePid)s?", "Remove %(threePid)s?": "Kendu %(threePid)s?",
"%(senderName)s requested a VoIP conference.": "%(senderName)s erabiltzaileak VoIP konferentzia bat eskatu du.", "%(senderName)s requested a VoIP conference.": "%(senderName)s erabiltzaileak VoIP konferentzia bat eskatu du.",
"Report it": "Salatu", "Report it": "Eman berri",
"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.": "Oraingoz pasahitza aldatzeak gailu guztietako muturretik muturrerako zifratze-gakoak berrezarriko ditu, eta ezin izango dituzu zifratutako txatetako historialak irakurri ez badituzu aurretik zure gelako gakoak esportatzen eta aldaketa eta gero berriro inportatzen.", "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.": "Oraingoz pasahitza aldatzeak gailu guztietako muturretik muturrerako zifratze-gakoak berrezarriko ditu, eta ezin izango dituzu zifratutako txatetako historialak irakurri ez badituzu aurretik zure gelako gakoak esportatzen eta aldaketa eta gero berriro inportatzen.",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Une honetan egiaztatu gabeko gailuak blokeatzen ari zara, gailu hauetara mezuak bidali ahal izateko egiaztatu behar dituzu.", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Une honetan egiaztatu gabeko gailuak blokeatzen ari zara, gailu hauetara mezuak bidali ahal izateko egiaztatu behar dituzu.",
"restore": "berreskuratu", "restore": "berreskuratu",
@ -549,7 +415,6 @@
"Scroll to unread messages": "Korritu irakurri gabeko mezuetara", "Scroll to unread messages": "Korritu irakurri gabeko mezuetara",
"Search failed": "Bilaketak huts egin du", "Search failed": "Bilaketak huts egin du",
"Searches DuckDuckGo for results": "DuckDuckGo-n bilatzen ditu emaitzak", "Searches DuckDuckGo for results": "DuckDuckGo-n bilatzen ditu emaitzak",
"Searching known users": "Erabiltzaile ezagunen bila",
"Seen by %(userName)s at %(dateTime)s": "%(userName)s erabiltzaileak ikusia %(dateTime)s(e)an", "Seen by %(userName)s at %(dateTime)s": "%(userName)s erabiltzaileak ikusia %(dateTime)s(e)an",
"Send a message (unencrypted)": "Bidali mezua (zifratu gabea)", "Send a message (unencrypted)": "Bidali mezua (zifratu gabea)",
"Send an encrypted message": "Bidali mezu zifratua", "Send an encrypted message": "Bidali mezu zifratua",
@ -577,14 +442,12 @@
"since they were invited": "gonbidatu zaienetik", "since they were invited": "gonbidatu zaienetik",
"Some of your messages have not been sent.": "Zure mezu batzuk ez dira bidali.", "Some of your messages have not been sent.": "Zure mezu batzuk ez dira bidali.",
"Sorry, this homeserver is using a login which is not recognised ": "Hasiera zerbitzari honek ezagutzen ez den saio bat erabiltzen du ", "Sorry, this homeserver is using a login which is not recognised ": "Hasiera zerbitzari honek ezagutzen ez den saio bat erabiltzen du ",
"tag as %(tagName)s": "jarri %(tagName)s etiketa",
"tag direct chat": "jarri etiketa txat zuzenari", "tag direct chat": "jarri etiketa txat zuzenari",
"Tagged as: ": "Jarritako etiketa: ", "Tagged as: ": "Jarritako etiketa: ",
"The default role for new room members is": "Gelako kide berrien lehenetsitako rola:", "The default role for new room members is": "Gelako kide berrien lehenetsitako rola:",
"The main address for this room is": "Gela honen helbide nagusia:", "The main address for this room is": "Gela honen helbide nagusia:",
"The phone number entered looks invalid": "Sartutako telefono zenbakia ez dirudi baliozkoa", "The phone number entered looks invalid": "Sartutako telefono zenbakia ez dirudi baliozkoa",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Eman duzun sinadura-gakoa %(userId)s erabiltzailearen %(deviceId)s gailutik jasotako bera da. Gailua egiaztatuta gisa markatu da.", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Eman duzun sinadura-gakoa %(userId)s erabiltzailearen %(deviceId)s gailutik jasotako bera da. Gailua egiaztatuta gisa markatu da.",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Bisitari batek ezin du ekintza hau burutu. Erregistratu hau egin ahal izateko.",
"This email address was not found": "Ez da e-mail helbide hau aurkitu", "This email address was not found": "Ez da e-mail helbide hau aurkitu",
"%(actionVerb)s this person?": "%(actionVerb)s pertsona hau?", "%(actionVerb)s this person?": "%(actionVerb)s pertsona hau?",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "'%(fileName)s' fitxategiak hasiera zerbitzarian igoerei ezarritako tamaina-muga gainditzen du", "The file '%(fileName)s' exceeds this home server's size limit for uploads": "'%(fileName)s' fitxategiak hasiera zerbitzarian igoerei ezarritako tamaina-muga gainditzen du",
@ -592,7 +455,6 @@
"The remote side failed to pick up": "Urruneko aldeak hartzean huts egin du", "The remote side failed to pick up": "Urruneko aldeak hartzean huts egin du",
"This Home Server does not support login using email address.": "Hasiera zerbitzari honek ez du e-mail helbidearekin saioa hastea onartzen.", "This Home Server does not support login using email address.": "Hasiera zerbitzari honek ez du e-mail helbidearekin saioa hastea onartzen.",
"This invitation was sent to an email address which is not associated with this account:": "Gonbidapen hau kontu honekin lotuta ez dagoen e-mail helbide batera bidali da:", "This invitation was sent to an email address which is not associated with this account:": "Gonbidapen hau kontu honekin lotuta ez dagoen e-mail helbide batera bidali da:",
"There was a problem logging in.": "Arazo bat egon da saioa hastean.",
"This room is not recognised.": "Ez da gela hau ezagutzen.", "This room is not recognised.": "Ez da gela hau ezagutzen.",
"These are experimental features that may break in unexpected ways": "Hauek ezaugarri esperimentalak dira eta agian ez dabiltza behar bezala", "These are experimental features that may break in unexpected ways": "Hauek ezaugarri esperimentalak dira eta agian ez dabiltza behar bezala",
"The visibility of existing history will be unchanged": "Aurreko historialaren ikusgaitasuna ez da aldatuko", "The visibility of existing history will be unchanged": "Aurreko historialaren ikusgaitasuna ez da aldatuko",
@ -617,7 +479,6 @@
"To send events of type": "Mota honetako gertaerak bidaltzea:", "To send events of type": "Mota honetako gertaerak bidaltzea:",
"To send messages": "Mezuak bidaltzea", "To send messages": "Mezuak bidaltzea",
"to start a chat with someone": "norbaitekin txat bat hastea", "to start a chat with someone": "norbaitekin txat bat hastea",
"to tag as %(tagName)s": "%(tagName)s gisa etiketatzea",
"to tag direct chat": "txat zuzena etiketatzea", "to tag direct chat": "txat zuzena etiketatzea",
"To use it, just wait for autocomplete results to load and tab through them.": "Erabiltzeko, itxaron osatze automatikoaren emaitzak kargatu arte eta gero tabuladorearekin hautatu.", "To use it, just wait for autocomplete results to load and tab through them.": "Erabiltzeko, itxaron osatze automatikoaren emaitzak kargatu arte eta gero tabuladorearekin hautatu.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Gela honen denbora-lerroko puntu zehatz bat kargatzen saiatu zara, baina ez duzu mezu zehatz hori ikusteko baimenik.", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Gela honen denbora-lerroko puntu zehatz bat kargatzen saiatu zara, baina ez duzu mezu zehatz hori ikusteko baimenik.",
@ -627,7 +488,6 @@
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s erabiltzaileak muturretik muturrerako (algorithm %(algorithm)s) zifratzea aktibatu du.", "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s erabiltzaileak muturretik muturrerako (algorithm %(algorithm)s) zifratzea aktibatu du.",
"Unable to add email address": "Ezin izan da e-mail helbidea gehitu", "Unable to add email address": "Ezin izan da e-mail helbidea gehitu",
"Unable to remove contact information": "Ezin izan da kontaktuaren informazioa kendu", "Unable to remove contact information": "Ezin izan da kontaktuaren informazioa kendu",
"Unable to restore previous session": "Ezin izan da aurreko saioa berreskuratu",
"Unable to verify email address.": "Ezin izan da e-mail helbidea egiaztatu.", "Unable to verify email address.": "Ezin izan da e-mail helbidea egiaztatu.",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s erabiltzaileak debekua kendu dio %(targetName)s erabiltzaileari.", "%(senderName)s unbanned %(targetName)s.": "%(senderName)s erabiltzaileak debekua kendu dio %(targetName)s erabiltzaileari.",
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Ezin izan da ziurtatu gonbidapen hau zure kontuarekin lotutako helbide batera bidali zela.", "Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Ezin izan da ziurtatu gonbidapen hau zure kontuarekin lotutako helbide batera bidali zela.",
@ -642,15 +502,14 @@
"Unknown command": "Agindu ezezaguna", "Unknown command": "Agindu ezezaguna",
"Unknown room %(roomId)s": "%(roomId)s gela ezezaguna da", "Unknown room %(roomId)s": "%(roomId)s gela ezezaguna da",
"Unknown (user, device) pair:": "Erabiltzaile eta gailu bikote ezezaguna:", "Unknown (user, device) pair:": "Erabiltzaile eta gailu bikote ezezaguna:",
"unknown": "ezezaguna",
"Unmute": "Audioa aktibatu", "Unmute": "Audioa aktibatu",
"Unnamed Room": "Izen gabeko gela", "Unnamed Room": "Izen gabeko gela",
"Unrecognised command:": "Agindu ezezaguna:", "Unrecognised command:": "Agindu ezezaguna:",
"Unrecognised room alias:": "Gelaren ezizen ezezaguna:", "Unrecognised room alias:": "Gelaren ezizen ezezaguna:",
"Unverified": "Egiaztatu gabea", "Unverified": "Egiaztatu gabea",
"Uploading %(filename)s and %(count)s others.zero": "%(filename)s igotzen", "Uploading %(filename)s and %(count)s others|zero": "%(filename)s igotzen",
"Uploading %(filename)s and %(count)s others.one": "%(filename)s eta beste %(count)s igotzen", "Uploading %(filename)s and %(count)s others|one": "%(filename)s eta beste %(count)s igotzen",
"Uploading %(filename)s and %(count)s others.other": "%(filename)s eta beste %(count)s igotzen", "Uploading %(filename)s and %(count)s others|other": "%(filename)s eta beste %(count)s igotzen",
"uploaded a file": "fitxategi bat igo du", "uploaded a file": "fitxategi bat igo du",
"Upload avatar": "Igo abatarra", "Upload avatar": "Igo abatarra",
"Upload Failed": "Igoerak huts egin du", "Upload Failed": "Igoerak huts egin du",
@ -696,7 +555,6 @@
"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": "Saioa amaitu duzu eta ez dituzu jakinarazpenak jasoko. Jakinarazpenak jaso nahi badituzu hasi saioa berriro gailu bakoitzean", "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": "Saioa amaitu duzu eta ez dituzu jakinarazpenak jasoko. Jakinarazpenak jaso nahi badituzu hasi saioa berriro gailu bakoitzean",
"You have <a>disabled</a> URL previews by default.": "Lehenetsita URLak aurreikustea <a>desgaitu</a> duzu.", "You have <a>disabled</a> URL previews by default.": "Lehenetsita URLak aurreikustea <a>desgaitu</a> duzu.",
"You have <a>enabled</a> URL previews by default.": "Lehenetsita URLak aurreikustea <a>gaitu</a> duzu.", "You have <a>enabled</a> URL previews by default.": "Lehenetsita URLak aurreikustea <a>gaitu</a> duzu.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Kontaktu baliogabea sartu duzu. Saiatu bere Matrix ID-a edo e-mail helbidea erabiltzen.",
"You have no visible notifications": "Ez daukazu jakinarazpen ikusgairik", "You have no visible notifications": "Ez daukazu jakinarazpen ikusgairik",
"You may wish to login with a different account, or add this email to this account.": "Agian beste kontu batekin hasi nahi duzu saioa, edo e-mail hau kontu honetara gehitu.", "You may wish to login with a different account, or add this email to this account.": "Agian beste kontu batekin hasi nahi duzu saioa, edo e-mail hau kontu honetara gehitu.",
"you must be a": "hau izan behar duzu:", "you must be a": "hau izan behar duzu:",
@ -735,7 +593,6 @@
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(fullYear)sko %(monthName)sk %(day)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(fullYear)sko %(monthName)sk %(day)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "Ezarri pantaila-izena:", "Set a display name:": "Ezarri pantaila-izena:",
"Set a Display Name": "Ezarri pantaila-izena",
"Upload an avatar:": "Igo abatarra:", "Upload an avatar:": "Igo abatarra:",
"This server does not support authentication with a phone number.": "Zerbitzari honek ez du telefono zenbakia erabiliz autentifikatzea onartzen.", "This server does not support authentication with a phone number.": "Zerbitzari honek ez du telefono zenbakia erabiliz autentifikatzea onartzen.",
"Missing password.": "Pasahitza falta da.", "Missing password.": "Pasahitza falta da.",
@ -753,10 +610,9 @@
"Encrypt room": "Zifratu gela", "Encrypt room": "Zifratu gela",
"There are no visible files in this room": "Ez dago fitxategi ikusgairik gela honetan", "There are no visible files in this room": "Ez dago fitxategi ikusgairik gela honetan",
"Sent messages will be stored until your connection has returned.": "Bidalitako mezuak zure konexioa berreskuratu arte gordeko dira.", "Sent messages will be stored until your connection has returned.": "Bidalitako mezuak zure konexioa berreskuratu arte gordeko dira.",
"Auto-complete": "Osatze automatikoa",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Birbidali guztiak</a> edo <a>baztertu guztiak</a> orain. Mezuak banaka aukeratu ditzakezu ere birbidali ala baztertzeko.", "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Birbidali guztiak</a> edo <a>baztertu guztiak</a> orain. Mezuak banaka aukeratu ditzakezu ere birbidali ala baztertzeko.",
"(~%(count)s results).one": "(~%(count)s emaitza)", "(~%(count)s results)|one": "(~%(count)s emaitza)",
"(~%(count)s results).other": "(~%(count)s emaitza)", "(~%(count)s results)|other": "(~%(count)s emaitza)",
"bold": "lodia", "bold": "lodia",
"italic": "etzana", "italic": "etzana",
"strike": "marratua", "strike": "marratua",
@ -781,7 +637,7 @@
"%(oneUser)sleft and rejoined %(repeats)s times": "Erabiltzaile %(oneUser)s %(repeats)s aldiz atera eta berriro elkartu da", "%(oneUser)sleft and rejoined %(repeats)s times": "Erabiltzaile %(oneUser)s %(repeats)s aldiz atera eta berriro elkartu da",
"%(severalUsers)sleft and rejoined": "%(severalUsers)s erabiltzaile atera eta berriro elkartu dira", "%(severalUsers)sleft and rejoined": "%(severalUsers)s erabiltzaile atera eta berriro elkartu dira",
"%(oneUser)sleft and rejoined": "Erabiltzaile %(oneUser)s atera eta berriro sartu da", "%(oneUser)sleft and rejoined": "Erabiltzaile %(oneUser)s atera eta berriro sartu da",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers) erabiltzaileen gonbidapenak %(repeats)s aldiz atzera bota dira", "%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)s erabiltzaileen gonbidapenak %(repeats)s aldiz atzera bota dira",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "Erabiltzaile %(oneUser)sen gonbidapena %(repeats)s aldiz bota da atzera", "%(oneUser)shad their invitation withdrawn %(repeats)s times": "Erabiltzaile %(oneUser)sen gonbidapena %(repeats)s aldiz bota da atzera",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)s erabiltzaileen gonbidapena atzera bota da", "%(severalUsers)shad their invitations withdrawn": "%(severalUsers)s erabiltzaileen gonbidapena atzera bota da",
"%(oneUser)shad their invitation withdrawn": "Erabiltzaile %(oneUser)sen gonbidapena atzera bota da", "%(oneUser)shad their invitation withdrawn": "Erabiltzaile %(oneUser)sen gonbidapena atzera bota da",
@ -826,7 +682,6 @@
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Esportatutako fitxategia pasaesaldi batez babestuko da. Pasaesaldia bertan idatzi behar duzu, fitxategia deszifratzeko.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Esportatutako fitxategia pasaesaldi batez babestuko da. Pasaesaldia bertan idatzi behar duzu, fitxategia deszifratzeko.",
"You must join the room to see its files": "Gelara elkartu behar zara bertako fitxategiak ikusteko", "You must join the room to see its files": "Gelara elkartu behar zara bertako fitxategiak ikusteko",
"Start new chat": "Hasi txat berria", "Start new chat": "Hasi txat berria",
"Guest users can't invite users. Please register.": "Bisitariek ezin dituzte erabiltzaileak gonbidatu, erregistratu zaitez.",
"Failed to invite": "Huts egin du ganbidapenak", "Failed to invite": "Huts egin du ganbidapenak",
"Failed to invite user": "Huts egin du erabiltzailea gonbidatzean", "Failed to invite user": "Huts egin du erabiltzailea gonbidatzean",
"Failed to invite the following users to the %(roomName)s room:": "Huts egin du honako erabiltzaile hauek %(roomName)s gelara gonbidatzean:", "Failed to invite the following users to the %(roomName)s room:": "Huts egin du honako erabiltzaile hauek %(roomName)s gelara gonbidatzean:",
@ -844,7 +699,6 @@
"Unable to restore session": "Ezin izan da saioa berreskuratu", "Unable to restore session": "Ezin izan da saioa berreskuratu",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Aurretik Riot bertsio berriago bat erabili baduzu, zure saioa bertsio honekin bateraezina izan daiteke. Itxi leiho hau eta itzuli bertsio berriagora.", "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Aurretik Riot bertsio berriago bat erabili baduzu, zure saioa bertsio honekin bateraezina izan daiteke. Itxi leiho hau eta itzuli bertsio berriagora.",
"Continue anyway": "Jarraitu hala ere", "Continue anyway": "Jarraitu hala ere",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Zure pantaila izena geletan hitz egiten duzunean besteek ikusten dutena da. Zein nahi duzu?",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Gailu bakoitzaren egiaztaketa prozesua jarraitzea aholkatzen dizugu, benetako jabeari dagozkiela baieztatzeko, baina mezua egiaztatu gabe birbidali dezakezu ere.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Gailu bakoitzaren egiaztaketa prozesua jarraitzea aholkatzen dizugu, benetako jabeari dagozkiela baieztatzeko, baina mezua egiaztatu gabe birbidali dezakezu ere.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" gelan aurretik ikusi ez dituzun gailuak daude.", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" gelan aurretik ikusi ez dituzun gailuak daude.",
"Unknown devices": "Gailu ezezagunak", "Unknown devices": "Gailu ezezagunak",
@ -886,7 +740,7 @@
"for %(amount)sh": "%(amount)sh", "for %(amount)sh": "%(amount)sh",
"for %(amount)sd": "%(amount)se", "for %(amount)sd": "%(amount)se",
"Updates": "Eguneraketak", "Updates": "Eguneraketak",
"Check for update": "Egiaztatu eguneraketa", "Check for update": "Bilatu ekuneraketa",
"Start chatting": "Hasi txateatzen", "Start chatting": "Hasi txateatzen",
"Start Chatting": "Hasi txateatzen", "Start Chatting": "Hasi txateatzen",
"Click on the button below to start chatting!": "Egin klik beheko botoian txateatzen hasteko!", "Click on the button below to start chatting!": "Egin klik beheko botoian txateatzen hasteko!",
@ -914,5 +768,70 @@
"Encryption key request": "Zifratze-gakoa eskatuta", "Encryption key request": "Zifratze-gakoa eskatuta",
"Deops user with given id": "Emandako ID-a duen erabiltzailea mailaz jaisten du", "Deops user with given id": "Emandako ID-a duen erabiltzailea mailaz jaisten du",
"had": "zuen", "had": "zuen",
"Disable Peer-to-Peer for 1:1 calls": "Desgaitu P2P biren arteko deietan" "Disable Peer-to-Peer for 1:1 calls": "Desgaitu P2P biren arteko deietan",
"Add a widget": "Gehitu trepeta bat",
"Allow": "Baimendu",
"and %(count)s others...|other": "eta beste %(count)s...",
"and %(count)s others...|one": "eta beste bat...",
"Cannot add any more widgets": "Ezin dira trepeta gehiago gehitu",
"Changes colour scheme of current room": "Gela honen kolore eskema aldatzen du",
"Delete widget": "Ezabatu trepeta",
"Define the power level of a user": "Zehaztu erabiltzaile baten botere maila",
"Do you want to load widget from URL:": "Trepeta bat kargatu nahi duzu URL honetatik:",
"Edit": "Editatu",
"Enable automatic language detection for syntax highlighting": "Gaitu hizkuntza antzemate automatikoa sintaxia nabarmentzeko",
"Hide Apps": "Ezkutatu aplikazioak",
"Hide join/leave messages (invites/kicks/bans unaffected)": "Ezkutatu elkartze/irtete mezuak (gonbidapenak/ateratzeak/debekuak ez dira aldatzen)",
"Hide avatar and display name changes": "Ezkutatu abatarra eta pantaila-izen aldaketak",
"Integrations Error": "Integrazio errorea",
"Publish this room to the public in %(domain)s's room directory?": "Argitaratu gela hau publikora %(domain)s domeinuko gelen direktorioan?",
"Matrix Apps": "Matrix Aplikazioak",
"AM": "AM",
"PM": "PM",
"NOTE: Apps are not end-to-end encrypted": "OHARRA: Aplikazioek ez dute muturretik muturrerako zifratzea",
"Revoke widget access": "Indargabetu trepetaren sarbidea",
"Sets the room topic": "Gelaren gaia ezartzen du",
"Show Apps": "Erakutsi aplikazioak",
"The maximum permitted number of widgets have already been added to this room.": "Gehienez onartzen diren trepeta kopurua gehitu da gela honetara.",
"To get started, please pick a username!": "Hasteko, hautatu erabiltzaile-izen bat!",
"Unable to create widget.": "Ezin izan da trepeta sortu.",
"Unbans user with given id": "ID zehatz bat duen erabiltzaileari debekua kentzen dio",
"You are not in this room.": "Ez zaude gela honetan.",
"You do not have permission to do that in this room.": "Ez duzu gela honetan hori egiteko baimenik.",
"Autocomplete Delay (ms):": "Osatze automatikoaren atzerapena (ms):",
"This Home server does not support groups": "Hasiera zerbitzari honek ez ditu taldeak onartzen",
"Loading device info...": "Gailuaren informazioa kargatzen...",
"Groups": "Taldeak",
"Create a new group": "Sortu talde berria",
"Create Group": "Sortu taldea",
"Group Name": "Taldearen izena",
"Example": "Adibidea",
"Create": "Sortu",
"Group ID": "Taldearen IDa",
"+example:%(domain)s": "+adibidea:%(domain)s",
"Room creation failed": "Taldea sortzeak huts egin du",
"You are a member of these groups:": "Talde hauetako kidea zara:",
"Join an existing group": "Elkartu badagoen talde batetara",
"Featured Rooms:": "Nabarmendutako gelak:",
"Featured Users:": "Nabarmendutako erabiltzaileak:",
"Edit Group": "Editatu taldea",
"Failed to update group": "Taldea eguneratzeak huts egin du",
"Group IDs must be of the form +localpart:%(domain)s": "Taldeen IDek forma hau dute +partelokala:%(domain)s",
"It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s": "Une honetan zure hasiera zerbitzarian besterik ezin dituzu sortu taldeak: erabili %(domain)s bukaera duen talde ID bat",
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Sortu talde bat zure komunitatea adierazteko! Zehaztu talde multzo bat eta zure hasiera horri pertsonalizatua zure espazioa markatzeko Matrix unibertsoan.",
"Error whilst fetching joined groups": "Errorea elkartutako taldeak eskuratzean",
"Automatically replace plain text Emoji": "Automatikoki ordezkatu Emoji testu soila",
"Failed to upload image": "Irudia igotzeak huts egin du",
"Hide avatars in user and room mentions": "Ezkutatu abatarrak erabiltzaile eta gelen aipamenetan",
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s trepeta gehitu du %(senderName)s erabiltzaileak",
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s trepeta kendu du %(senderName)s erabiltzaileak",
"Verifies a user, device, and pubkey tuple": "Erabiltzaile, gailu eta gako publiko multzoa egiaztatzen du",
"To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "Dagoen talde batetara elkartzeko taldearen identifikatzailea ezagutu behar duzu, honen antza du: <i>+adibidea:matrix.org</i>.",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot egiaztaketa orain ez dago eskuragarri mahaigainean - erabili <a>web nabigatzailea</a>",
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s trepeta aldatu du %(senderName)s erabiltzaileak",
"%(weekDayName)s, %(monthName)s %(day)s": "%(weekDayName)s, %(monthName)sk %(day)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(fullYear)sko %(monthName)sk %(day)s",
"Copied!": "Kopiatuta!",
"Failed to copy": "Kopiak huts egin du",
"Cancel": "Utzi"
} }

341
src/i18n/strings/fi.json Normal file
View file

@ -0,0 +1,341 @@
{
"a room": "huone",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Numeroon +%(msisdn)s on lähetetty tekstiviesti. Ole hyvä ja syötä sen sisältämä varmennuskoodi",
"Accept": "Hyväksy",
"Cancel": "Peruuta",
"Close": "Sulje",
"Create new room": "Luo uusi huone",
"Custom Server Options": "Omat palvelinasetukset",
"Dismiss": "Hylkää",
"Drop here %(toAction)s": "Pudota tänne %(toAction)s",
"Error": "Virhe",
"Failed to forget room %(errCode)s": "Huoneen unohtaminen epäonnistui %(errCode)s",
"Favourite": "Suosikki",
"Mute": "Vaimenna",
"Notifications": "Ilmoitukset",
"Operation failed": "Toiminto epäonnistui",
"Remove": "Poista",
"Room directory": "Huonehakemisto",
"Search": "Haku",
"Settings": "Asetukset",
"Start chat": "Aloita keskustelu",
"unknown error code": "tuntematon virhekoodi",
"Sunday": "Sunnuntai",
"Monday": "Maanantai",
"Tuesday": "Tiistai",
"Wednesday": "Keskiviikko",
"Thursday": "Torstai",
"Friday": "Perjantai",
"Saturday": "Lauantai",
"Failed to change password. Is your password correct?": "Salasanan muuttaminen epäonnistui. Onko salasanasi oikein?",
"Continue": "Jatka",
"powered by Matrix": "Matrix-pohjainen",
"Active call (%(roomName)s)": "Aktivoi puhelu (%(roomName)s)",
"Add": "Lisää",
"Add a topic": "Lisää aihe",
"Add email address": "Lisää sähköpostiosoite",
"Add phone number": "Lisää puhelinnumero",
"Admin": "Ylläpitäjä",
"Admin tools": "Ylläpitotyökalut",
"Allow": "Salli",
"And %(count)s more...": "Ja %(count)s lisää...",
"VoIP": "VoIP",
"Missing Media Permissions, click here to request.": "Mediaoikeudet puuttuvat. Klikkaa tästä pyytääksesi oikeudet.",
"No Microphones detected": "Mikrofonia ei löytynyt",
"No Webcams detected": "Webkameraa ei löytynyt",
"No media permissions": "Ei mediaoikeuksia",
"You may need to manually permit Riot to access your microphone/webcam": "Sinun täytyy ehkä manuaalisesti sallia mikrofonin/webkameran käyttö",
"Default Device": "Oletuslaite",
"Microphone": "Mikrofoni",
"Camera": "Kamera",
"Advanced": "Kehittyneet",
"Algorithm": "Algoritmi",
"Hide removed messages": "Piilota poistetut viestit",
"Always show message timestamps": "Näytä aina viestien aikaleimat",
"Authentication": "Autentikointi",
"Alias (optional)": "Alias (valinnainen)",
"and": "ja",
"%(items)s and %(remaining)s others": "%(items)s ja %(remaining)s lisää",
"%(items)s and one other": "%(items)s ja yksi lisää",
"%(items)s and %(lastItem)s": "%(items)s ja %(lastItem)s",
"and %(count)s others....other": "ja %(count)s lisää...",
"and %(count)s others....one": "ja yksi lisää...",
"%(names)s and %(lastPerson)s are typing": "%(names)s ja %(lastPerson)s kirjoittavat",
"%(names)s and one other are typing": "%(names)s ja yksi muu kirjoittavat",
"%(names)s and %(count)s others are typing": "%(names)s ja %(count)s muuta kirjoittavat",
"An email has been sent to": "Sähköposti on lähetetty osoitteeseen",
"A new password must be entered.": "Sinun täytyy syöttää uusi salasana.",
"%(senderName)s answered the call.": "%(senderName)s vastasi puheluun.",
"An error has occurred.": "Virhe.",
"Anyone": "Kaikki",
"Anyone who knows the room's link, apart from guests": "Kaikki jotka tietävät huoneen osoitteen, paitsi vieraat",
"Anyone who knows the room's link, including guests": "Kaikki jotka tietävät huoneen osoitteen, mukaanlukien vieraat",
"Are you sure?": "Oletko varma?",
"Are you sure you want to leave the room '%(roomName)s'?": "Oletko varma että haluat poistua huoneesta '%(roomName)s'?",
"Are you sure you want to reject the invitation?": "Oletko varma että haluat hylätä kutsun?",
"Are you sure you want to upload the following files?": "Oletko varma että haluat ladata seuraavat tiedostot?",
"Attachment": "Liite",
"Autoplay GIFs and videos": "Toista automaattisesti GIF-animaatiot ja videot",
"%(senderName)s banned %(targetName)s.": "%(senderName)s antoi porttikiellon käyttäjälle %(targetName)s.",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Yhdistäminen kotipalvelimeen epäonnistui. Ole hyvä ja tarkista verkkoyhteytesi ja varmista että <a>kotipalvelimen SSL-sertifikaatti</a> on luotettu, ja että jokin selaimen lisäosa ei estä pyyntöjen lähettämisen.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Yhdistäminen kotipalveluun HTTP:n avulla ei ole mahdollista kun selaimen osoitepalkissa on HTTPS URL. Käytä joko HTTPS tai <a>salli turvattomat skriptit</a>.",
"Can't load user settings": "Käyttäjäasetusten lataaminen epäonnistui",
"Change Password": "Muuta salasana",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s muutti näyttönimensä %(oldDisplayName)s -> %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s muutti profiilikuvansa.",
"%(targetName)s accepted an invitation.": "%(targetName)s hyväksyi kutsun.",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s hyväksyi kutsun käyttäjän %(displayName)s puolesta.",
"Account": "Tili",
"and %(count)s others...|other": "ja %(count)s lisää...",
"and %(count)s others...|one": "ja yksi lisää...",
"Ban": "Anna porttikielto",
"Banned users": "Porttikiellon saanneet käyttäjät",
"Bans user with given id": "Antaa porttikiellon käyttäjälle jolla on annettu tunniste",
"Bug Report": "Virheraportti",
"Bulk Options": "Bulkkiasetukset",
"Changes your display nickname": "Muuttaa näyttönimesi",
"Changes colour scheme of current room": "Muuttaa tamänhetkisen huoneen väritystä",
"Clear Cache and Reload": "Puhdista välimuisti ja lataa uudelleen",
"Clear Cache": "Puhdista välimuisti",
"Click here to fix": "Paina tästä korjataksesi",
"Click to mute audio": "Paina mykistääksesi äänet",
"Click to mute video": "Paina mykistääksesi video",
"click to reveal": "paina näyttääksesi",
"Click to unmute video": "Paina poistaaksesi videomykistyksen",
"Click to unmute audio": "Paina poistaaksesi äänimykistyksen",
"Command error": "Komentovirhe",
"Commands": "Komennot",
"Conference call failed.": "Konferenssipuhelu epäonnistui",
"Conference calling is in development and may not be reliable.": "Konferenssipuhelut ovat vielä kehityksen alla ja saattavat toimia epäluotettavasti",
"Conference calls are not supported in encrypted rooms": "Konferenssipuhelut eivät ole mahdollisia salatuissa huoneissa",
"Conference calls are not supported in this client": "Tämä asiakasohjelma ei tue konferenssipuheluja",
"Confirm password": "Varmista salasana",
"Confirm your new password": "Varmista uusi salasanasi",
"Could not connect to the integration server": "Yhteys integraatiopalvelimeen epäonnistui",
"Create a new chat or reuse an existing one": "Luo uusi keskustelu tai uudelleenkäytä vanha",
"Create an account": "Luo tili",
"Create Room": "Luo huone",
"Cryptography": "Salaus",
"Current password": "Nykyinen salasana",
"Custom": "Mukautettu",
"Custom level": "Mukautettu taso",
"/ddg is not a command": "/ddg ei ole komento",
"Deactivate Account": "Deaktivoi tili",
"Deactivate my account": "Deaktivoi tilini",
"Decline": "Hylkää",
"Decryption error": "Virhe salauksen purkamisessa",
"Delete": "Poista",
"demote": "alenna",
"Default": "Oletusarvo",
"Device already verified!": "Laite on jo varmennettu!",
"Device ID": "Laitetunniste",
"Device ID:": "Laitetunniste:",
"device id: ": "laitetunniste:",
"Device key:": "Laiteavain:",
"Devices": "Laitteet",
"Direct chats": "Suorat viestittelyt",
"Disable Notifications": "Ota ilmoitukset pois käytöstä",
"disabled": "pois käytöstä",
"Disinvite": "Peru kutsu",
"Display name": "Näyttönimi",
"Download %(text)s": "Lataa %(text)s",
"Drop File Here": "Pudota tiedosto tähän",
"Ed25519 fingerprint": "Ed25519 sormenjälki",
"Edit": "Muokkaa",
"Email": "Sähköposti",
"Email address": "Sähköpostiosoite",
"Email address (optional)": "Sähköpostiosoite (valinnainen)",
"Email, name or matrix ID": "Sähköpostiosoite, nimi, tai Matrix tunniste",
"Emoji": "Emoji",
"Enable encryption": "Ota salaus käyttöön",
"Enable Notifications": "Ota ilmoitukset käyttöön",
"enabled": "käytössä",
"Encrypted by a verified device": "Varmennetun laitteen salaama",
"Encrypted by an unverified device": "Varmentamattoman laiteen salaama",
"Encrypted room": "Salattu huone",
"Encryption is enabled in this room": "Salaus on kytketty päälle tässä huoneessa",
"Encryption is not enabled in this room": "Salaus ei ole kytketty päälle tässä huoneessa",
"End-to-end encryption information": "Päästä päähän-salauksen tiedot",
"Enter Code": "Syötä koodi",
"Enter passphrase": "Syötä salasana",
"Error decrypting attachment": "Liitteen salauksen purku epäonnistui",
"Event information": "Tapahtumatiedot",
"Export": "Vie",
"Export E2E room keys": "Vie huoneen päästä päähän-salauksen (E2E) avaimet ",
"Failed to ban user": "Porttikiellon antaminen epäonnistui",
"Failed to delete device": "Laitten poistamine epäonnistui",
"Failed to fetch avatar URL": "Avatar URL:n haku epäonnistui",
"Failed to join room": "Huoneeseen liittyminen epäonnistui",
"Failed to kick": "Huoneesta poistaminen epäonnistui",
"Failed to leave room": "Huoneesta poistuminen epäonnistui",
"Failed to load timeline position": "Aikajanapaikan lataaminen epäonnistui",
"Failed to mute user": "Käyttäjän mykistäminen epäonnistui",
"Failed to register as guest:": "Vieraana rekisteröityminen epäonnistui",
"Failed to reject invite": "Kutsun hylkääminen epäonnistui",
"Failed to reject invitation": "Kutsun hylkääminen epäonnistui",
"Failed to save settings": "Asetusten tallentaminen epäonnistui",
"Failed to send email": "Sähköpostin lähettäminen epäonnistui",
"Failed to send request.": "Pyynnön lähettäminen epäonnistui",
"Failed to set display name": "Näyttönimen asettaminen epäonnistui",
"Failed to set up conference call": "Konferenssipuhelun alustus epäonnistui",
"Failed to toggle moderator status": "Moderaattoriasetuksen muuttaminen epäonnistui",
"Failed to unban": "Porttikiellon poistaminen epäonnistui",
"Failed to upload file": "Tiedoston lataaminen epäonnistui",
"Failed to upload profile picture!": "Profiilikuvan lataaminen epäonnistui",
"Failed to verify email address: make sure you clicked the link in the email": "Varmenna sähköpostiosoitteesi: varmista että klikkasit sähköpostissa olevaa linkkiä",
"Failure to create room": "Huoneen luominen epäonnistui",
"favourite": "suosikki",
"Favourites": "Suosikit",
"Fill screen": "Täytä näyttö",
"Filter room members": "Suodata huoneen jäsenet",
"Forget room": "Unohda huone",
"Forgot your password?": "Unohditko salasanasi?",
"For security, this session has been signed out. Please sign in again.": "Turvallisuussyistä tämä istunto on vanhentunut. Ole hyvä ja kirjaudu uudestaan.",
"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.": "Turvallusuussyistä uloskirjautuminen poistaa kaikki päästä päähän-salausavaimet tästä selaimesta. Jos haluat purkaa keskustelujen salaukset tulevaisuudessa pitää sinun viedä purkuavaimet ja pitää ne turvallisesti tallessa.",
"Found a bug?": "Löysitkö virheen?",
"had": "oli",
"Hide Apps": "Piilota sovellukset",
"Hide read receipts": "Piilota lukukuittaukset",
"Hide Text Formatting Toolbar": "Piilota tekstinmuotoilutyökalupalkki",
"Homeserver is": "Kotipalvelin on",
"Identity Server is": "Identiteettipalvelin on",
"I have verified my email address": "Olen varmistanut sähköpostiosoitteeni",
"Import": "Tuo",
"Import E2E room keys": "Tuo päästä päähän-salaus (E2E) huoneavaimet",
"Incoming call from %(name)s": "Saapuva puhelu käyttäjältä %(name)s",
"Incoming video call from %(name)s": "Saapuva videopuhelu käyttäjältä %(name)s",
"Incoming voice call from %(name)s": "Saapuva äänipuhelu käyttäjältä %(name)s",
"Incorrect username and/or password.": "Virheellinen käyttäjänimi ja/tai salasana",
"Incorrect verification code": "Virheellinen varmennuskoodi",
"Integrations Error": "Integraatiovirhe",
"Interface Language": "Käyttöliittymän kieli",
"Invalid alias format": "Aliaksen muoto on virheellinen",
"Invalid address format": "Osoitteen muoto on virheellinen",
"Invalid Email Address": "Virheellinen sähköpostiosoite",
"Invite new room members": "Kutsu lisää jäseniä huoneeseen",
"Invited": "Kutsuttu",
"Invites": "Kutsuu",
"Invites user with given id to current room": "Kutsuu annetun käyttäjätunnisteen mukaisen käyttäjän huoneeseen",
"Sign in with": "Kirjaudu käyttäen",
"Join Room": "Liity huoneeseen",
"joined and left": "liittyi ja poistui",
"joined": "liittyi",
"Joins room with given alias": "Liittyy huoneeseen jolla on annettu alias",
"Jump to first unread message.": "Hyppää ensimmäiseen lukemattomaan viestiin",
"Kick": "Poista huoneesta",
"Kicks user with given id": "Poistaa käyttäjätunnisteen mukaisen käyttäjän huoneesta",
"Labs": "Laboratorio",
"Last seen": "Viimeksi nähty",
"Leave room": "Poistu huoneesta",
"left and rejoined": "poistui ja liittyi jälleen",
"left": "poistui",
"Level:": "Taso:",
"Local addresses for this room:": "Tämän huoneen paikalliset osoitteet:",
"Logged in as:": "Kirjautunut käyttäjänä:",
"Login as guest": "Kirjaudu vieraana",
"Logout": "Kirjaudu ulos",
"Low priority": "Alhainen prioriteetti",
"Manage Integrations": "Hallinoi integraatioita",
"Markdown is disabled": "Markdown on pois päältä",
"Markdown is enabled": "Mardown on päällä",
"matrix-react-sdk version:": "Matrix-react-sdk versio:",
"Matrix Apps": "Matrix ohjelmat",
"Members only": "Vain jäsenet",
"Message not sent due to unknown devices being present": "Viestiä ei lähetetty koska paikalla on tuntemattomia laitteita",
"Mobile phone number": "Matkapuhelinnumero",
"Mobile phone number (optional)": "Matkapuhelinnumero (valinnainen)",
"Moderator": "Moderaattori",
"my Matrix ID": "minun Matrix tunniste",
"Name": "Nimi",
"New password": "Uusi salasana",
"New passwords don't match": "Uudet salasanat eivät täsmää",
"New passwords must match each other.": "Uusien salasanojen on vastattava toisiaan",
"not set": "ei asetettu",
"not specified": "ei määritetty",
"(not supported by this browser)": "(ei tuettu tässä selaimessa)",
"<not supported>": "<ei tuettu>",
"AM": "AM",
"PM": "PM",
"NOT verified": "EI varmennettu",
"NOTE: Apps are not end-to-end encrypted": "Huom: Ohjelmat eivät ole päästä päähän-salattuja",
"No display name": "Ei näyttönimeä",
"No more results": "Ei enempää tuloksia",
"No results": "Ei tuloksia",
"OK": "OK",
"olm version:": "olm versio:",
"Once encryption is enabled for a room it cannot be turned off again (for now)": "Kun salaus on kytketty päälle sitä ei enää voi kytkeä pois (toistaiseksi)",
"Only people who have been invited": "Vain kutsun saanneet käyttäjät",
"Password": "Salasana",
"Password:": "Salasana:",
"Passwords can't be empty": "Salasanat eivät voi olla tyhjiä",
"People": "Henkilöt",
"Permissions": "Oikeudet",
"Phone": "Puhelin",
"Privacy warning": "Yksityisyysvaroitus",
"Private Chat": "Yksityinen keskustelu",
"Profile": "Profiili",
"Public Chat": "Julkinen keskustelu",
"Reason": "Syy",
"Reason: %(reasonText)s": "Syy: %(reasonText)s",
"Register": "Rekisteröi",
"rejected": "hylätty",
"Reject invitation": "Hylkää kutsu",
"Rejoin": "Liity uudestaan",
"Remove Contact Information?": "Poista yhteystiedot?",
"Results from DuckDuckGo": "DuckDuckGo:n tulokset",
"Return to login screen": "Palaa kirjautumissivulle",
"riot-web version:": "Riot-web versio:",
"Room Colour": "Huoneen väri",
"Room contains unknown devices": "Huone sisältää tuntemattomia laitteita",
"Room name (optional)": "Huoneen nimi (valinnainen)",
"Rooms": "Huoneet",
"Save": "Tallenna",
"Scroll to bottom of page": "Vieritä sivun loppuun",
"Scroll to unread messages": "Vieritä lukemattomiin viesteihin",
"Search failed": "Haku epäonnistui",
"Searches DuckDuckGo for results": "Hakee DuckDuckGo:n avulla",
"Send a message (unencrypted)": "Lähetä viesti (salaamaton)",
"Send an encrypted message": "Lähetä salattu viesti",
"Send anyway": "Lähetä kuitenkin",
"Sender device information": "Lähettäjän laitteen tiedot",
"Send Invites": "Lähetä kutsu",
"sent an image": "lähetti kuvan",
"sent a video": "lähetti videon",
"Server error": "Palvelinvirhe",
"Session ID": "Istuntotunniste",
"Set": "Aseta",
"Sets the room topic": "Asettaa huoneen aiheen",
"Show panel": "Näytä paneeli",
"Sign in": "Kirjaudu sisään",
"Sign out": "Kirjaudu ulos",
"since they joined": "liittymisestä lähtien",
"since they were invited": "kutsusta lähtien",
"Some of your messages have not been sent.": "Jotkut viesteistäsi ei ole lähetetty",
"Someone": "Joku",
"Start a chat": "Aloita keskustelu",
"Start Chat": "Aloita keskustelu",
"Submit": "Lähetä",
"This email address is already in use": "Tämä sähköpostiosoite on jo käytössä",
"This email address was not found": "Sähköpostiosoitetta ei löytynyt",
"The remote side failed to pick up": "Toinen osapuoli ei vastannut",
"This room has no local addresses": "Tällä huoneella ei ole paikallista osoitetta",
"This room": "Tämä huone",
"This room is not accessible by remote Matrix servers": "Tähän huoneeseen ei voi päästä ulkopuolisilta Matrix-palvelimilta",
"This room's internal ID is": "Huoneen sisäinen tunniste on",
"Unban": "Poista porttikielto",
"Undecryptable": "Salauksen purku ei ole mahdollista",
"Unencrypted room": "Salaamaton huone",
"unencrypted": "salaamaton",
"Unencrypted message": "Salaamaton viesti",
"unknown caller": "tuntematon soittaja",
"unknown device": "tuntematon laite",
"Unknown room %(roomId)s": "Tuntematon huone %(roomId)s",
"Unknown (user, device) pair:": "Tuntematon (käyttäjä,laite) -pari.",
"Unmute": "Poista mykistys",
"Unnamed Room": "Nimeämätön huone",
"Unrecognised command:": "Tuntematon komento:",
"Unrecognised room alias:": "Tuntematon huonealias:",
"Unverified": "Varmentamaton",
"Uploading %(filename)s and %(count)s others|zero": "Ladataan %(filename)s",
"Uploading %(filename)s and %(count)s others|one": "Ladataan %(filename)s ja %(count)s muuta"
}

View file

@ -1,126 +1,4 @@
{ {
"af": "Afrikaans",
"ar-ae": "Arabic (U.A.E.)",
"ar-bh": "Arabic (Bahrain)",
"ar-dz": "Arabic (Algeria)",
"ar-eg": "Arabic (Egypt)",
"ar-iq": "Arabic (Iraq)",
"ar-jo": "Arabic (Jordan)",
"ar-kw": "Arabic (Kuwait)",
"ar-lb": "Arabic (Lebanon)",
"ar-ly": "Arabic (Libya)",
"ar-ma": "Arabic (Morocco)",
"ar-om": "Arabic (Oman)",
"ar-qa": "Arabic (Qatar)",
"ar-sa": "Arabic (Saudi Arabia)",
"ar-sy": "Arabic (Syria)",
"ar-tn": "Arabic (Tunisia)",
"ar-ye": "Arabic (Yemen)",
"be": "Belarusian",
"bg": "Bulgarian",
"ca": "Catalan",
"cs": "Czech",
"da": "Danish",
"de-at": "German (Austria)",
"de-ch": "German (Switzerland)",
"de": "German",
"de-li": "German (Liechtenstein)",
"de-lu": "German (Luxembourg)",
"el": "Greek",
"en-au": "English (Australia)",
"en-bz": "English (Belize)",
"en-ca": "English (Canada)",
"en": "English",
"en-gb": "English (United Kingdom)",
"en-ie": "English (Ireland)",
"en-jm": "English (Jamaica)",
"en-nz": "English (New Zealand)",
"en-tt": "English (Trinidad)",
"en-us": "English (United States)",
"en-za": "English (South Africa)",
"es-ar": "Spanish (Argentina)",
"es-bo": "Spanish (Bolivia)",
"es-cl": "Spanish (Chile)",
"es-co": "Spanish (Colombia)",
"es-cr": "Spanish (Costa Rica)",
"es-do": "Spanish (Dominican Republic)",
"es-ec": "Spanish (Ecuador)",
"es-gt": "Spanish (Guatemala)",
"es-hn": "Spanish (Honduras)",
"es-mx": "Spanish (Mexico)",
"es-ni": "Spanish (Nicaragua)",
"es-pa": "Spanish (Panama)",
"es-pe": "Spanish (Peru)",
"es-pr": "Spanish (Puerto Rico)",
"es-py": "Spanish (Paraguay)",
"es": "Spanish (Spain)",
"es-sv": "Spanish (El Salvador)",
"es-uy": "Spanish (Uruguay)",
"es-ve": "Spanish (Venezuela)",
"et": "Estonian",
"eu": "Basque (Basque)",
"fa": "Farsi",
"fi": "Finnish",
"fo": "Faeroese",
"fr-be": "French (Belgium)",
"fr-ca": "French (Canada)",
"fr-ch": "French (Switzerland)",
"fr": "French",
"fr-lu": "French (Luxembourg)",
"ga": "Irish",
"gd": "Gaelic (Scotland)",
"he": "Hebrew",
"hi": "Hindi",
"hr": "Croatian",
"hu": "Hungarian",
"id": "Indonesian",
"is": "Icelandic",
"it-ch": "Italian (Switzerland)",
"it": "Italian",
"ja": "Japanese",
"ji": "Yiddish",
"ko": "Coréen",
"lt": "Lithuanian",
"lv": "Latvian",
"mk": "Macedonian (FYROM)",
"ms": "Malaysian",
"mt": "Maltese",
"nl-be": "Dutch (Belgium)",
"nl": "Dutch",
"no": "Norwegian",
"pl": "Polish",
"pt-br": "Brazilian Portuguese",
"pt": "Portuguese",
"rm": "Rhaeto-Romanic",
"ro-mo": "Romanian (Republic of Moldova)",
"ro": "Romanian",
"ru-mo": "Russian (Republic of Moldova)",
"ru": "Russian",
"sb": "Sorbian",
"sk": "Slovak",
"sl": "Slovenian",
"sq": "Albanian",
"sr": "Serbe",
"sv-fi": "Swedish (Finland)",
"sv": "Swedish",
"sx": "Sutu",
"sz": "Sami (Lappish)",
"th": "Thai",
"tn": "Tswana",
"tr": "Turkish",
"ts": "Tsonga",
"uk": "Ukrainian",
"ur": "Urdu",
"ve": "Venda",
"vi": "Vietnamese",
"xh": "Xhosa",
"zh-cn": "Chinese (PRC)",
"zh-hk": "Chinese (Hong Kong SAR)",
"zh-sg": "Chinese (Singapore)",
"zh-tw": "Chinese (Taiwan)",
"zu": "Zulu",
"anyone": "n'importe qui",
"Direct Chat": "Discussion Directe",
"Direct chats": "Conversations directes", "Direct chats": "Conversations directes",
"Disable inline URL previews by default": "Désactiver laperçu des URLs", "Disable inline URL previews by default": "Désactiver laperçu des URLs",
"Disinvite": "Désinviter", "Disinvite": "Désinviter",
@ -150,7 +28,6 @@
"Failed to change power level": "Échec du changement de niveau d'autorité", "Failed to change power level": "Échec du changement de niveau d'autorité",
"Failed to delete device": "Échec de la suppression de l'appareil", "Failed to delete device": "Échec de la suppression de l'appareil",
"Failed to forget room %(errCode)s": "Échec lors de l'oubli du salon %(errCode)s", "Failed to forget room %(errCode)s": "Échec lors de l'oubli du salon %(errCode)s",
"Please Register": "Veuillez vous enregistrer",
"Remove": "Supprimer", "Remove": "Supprimer",
"was banned": "a été banni(e)", "was banned": "a été banni(e)",
"was invited": "a été invité(e)", "was invited": "a été invité(e)",
@ -170,7 +47,6 @@
"Favourite": "Favoris", "Favourite": "Favoris",
"Notifications": "Notifications", "Notifications": "Notifications",
"Settings": "Paramètres", "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", "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", "accept": "Accepter",
"%(targetName)s accepted an invitation.": "%(targetName)s a accepté une invitation.", "%(targetName)s accepted an invitation.": "%(targetName)s a accepté une invitation.",
@ -180,9 +56,6 @@
"Admin": "Admin", "Admin": "Admin",
"Advanced": "Avancé", "Advanced": "Avancé",
"Algorithm": "Algorithme", "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", "an address": "une adresse",
"and": "et", "and": "et",
"%(items)s and %(remaining)s others": "%(items)s et %(remaining)s autres", "%(items)s and %(remaining)s others": "%(items)s et %(remaining)s autres",
@ -191,10 +64,8 @@
"%(names)s and %(lastPerson)s are typing": "%(names)s et %(lastPerson)s sont en train d'écrire", "%(names)s and %(lastPerson)s are typing": "%(names)s et %(lastPerson)s sont en train d'écrire",
"%(names)s and one other are typing": "%(names)s et un autre sont en train d'écrire", "%(names)s and one other are typing": "%(names)s et un autre sont en train d'écrire",
"%(names)s and %(count)s others are typing": "%(names)s et %(count)s d'autres sont en train d'écrire", "%(names)s and %(count)s others are typing": "%(names)s et %(count)s d'autres sont en train d'écrire",
"and %(count)s others...": { "and %(count)s others...|other": "et %(count)s autres...",
"other": "et %(count)s autres...", "and %(count)s others...|one": "et un autre...",
"one": "et un autre..."
},
"An email has been sent to": "Un e-mail a été envoyé à", "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é.", "A new password must be entered.": "Un nouveau mot de passe doit être entré.",
"Anyone who knows the room's link, apart from guests": "Tout ceux qui connaissent le lien du salon, à part les visiteurs", "Anyone who knows the room's link, apart from guests": "Tout ceux qui connaissent le lien du salon, à part les visiteurs",
@ -273,7 +144,7 @@
"Failed to send request.": "Erreur lors de l'envoi de la requête.", "Failed to send request.": "Erreur lors de l'envoi de la requête.",
"Failed to set display name": "Échec lors de l'enregistrement du nom d'affichage", "Failed to set 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 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", "Failed to toggle moderator status": "Échec lors de lactivation du statut de modérateur",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s a accepté linvitation de %(displayName)s.", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s a accepté linvitation de %(displayName)s.",
"Access Token:": "Jeton daccès :", "Access Token:": "Jeton daccès :",
"Always show message timestamps": "Toujours afficher l'heure des messages", "Always show message timestamps": "Toujours afficher l'heure des messages",
@ -288,17 +159,15 @@
"favourite": "favoris", "favourite": "favoris",
"Favourites": "Favoris", "Favourites": "Favoris",
"Fill screen": "Plein écran", "Fill screen": "Plein écran",
"Filter room members": "Filtrer les membres par nom", "Filter room members": "Filtrer les membres du salon",
"Forget room": "Oublier le salon", "Forget room": "Oublier le salon",
"Forgot your password?": "Mot de passe perdu ?", "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 authentifier à nouveau.", "For security, this session has been signed out. Please sign in again.": "Par sécurité, la session a expiré. Merci de vous authentifier à nouveau.",
"Found a bug?": "Trouvé un problème ?", "Found a bug?": "Trouvé un problème ?",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s à %(toPowerLevel)s", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s à %(toPowerLevel)s",
"Guest users can't create new rooms. Please register to create room and start a chat.": "Les visiteurs ne peuvent créer de nouveaux salons. Merci de vous enregistrer pour commencer une discussion.",
"Guest users can't upload files. Please register to upload.": "Les visiteurs ne peuvent pas télécharger de fichier. Veuillez vous enregistrer pour télécharger.",
"had": "avait", "had": "avait",
"Hangup": "Raccrocher", "Hangup": "Raccrocher",
"Hide read receipts": "Cacher les accusés de réception", "Hide read receipts": "Cacher les accusés de lecture",
"Hide Text Formatting Toolbar": "Cacher la barre de formatage de texte", "Hide Text Formatting Toolbar": "Cacher la barre de formatage de texte",
"Historical": "Historique", "Historical": "Historique",
"Homeserver is": "Le homeserver est", "Homeserver is": "Le homeserver est",
@ -321,10 +190,10 @@
"%(displayName)s is typing": "%(displayName)s est en train d'écrire", "%(displayName)s is typing": "%(displayName)s est en train d'écrire",
"Sign in with": "Je veux m'identifier avec", "Sign in with": "Je veux m'identifier avec",
"Join Room": "Rejoindre le salon", "Join Room": "Rejoindre le salon",
"joined and left": "a joint et quitté", "joined and left": "a rejoint et quitté",
"joined": "a joint", "joined": "a rejoint",
"%(targetName)s joined the room.": "%(targetName)s a joint le salon.", "%(targetName)s joined the room.": "%(targetName)s a rejoint le salon.",
"Joins room with given alias": "Joint le salon avec l'alias défini", "Joins room with given alias": "Rejoint le salon avec l'alias défini",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s a expulsé %(targetName)s.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s a expulsé %(targetName)s.",
"Kick": "Expulser", "Kick": "Expulser",
"Kicks user with given id": "Expulse l'utilisateur avec l'ID donné", "Kicks user with given id": "Expulse l'utilisateur avec l'ID donné",
@ -339,7 +208,11 @@
"Login as guest": "S'identifier en tant que visiteur", "Login as guest": "S'identifier en tant que visiteur",
"Logout": "Se déconnecter", "Logout": "Se déconnecter",
"Low priority": "Priorité basse", "Low priority": "Priorité basse",
"%(senderName)s made future room history visible to": "%(senderName)s a rendu l'historique visible de", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s a rendu l'historique visible à tous les membres du salon, depuis le moment où ils ont été invités.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s a rendu l'historique visible à tous les membres du salon, depuis le moment où ils ont rejoint.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s a rendu l'historique visible à tous les membres du salon.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s a rendu l'historique visible à n'importe qui.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s a rendu l'historique visible à inconnu (%(visibility)s).",
"Manage Integrations": "Gestion des intégrations", "Manage Integrations": "Gestion des intégrations",
"Markdown is disabled": "Le formatage \"Markdown\" est désactivé", "Markdown is disabled": "Le formatage \"Markdown\" est désactivé",
"Markdown is enabled": "Le formatage “Markdown” est activé", "Markdown is enabled": "Le formatage “Markdown” est activé",
@ -357,14 +230,13 @@
"Never send encrypted messages to unverified devices in this room": "Ne jamais envoyer de message chiffré aux appareils non-vérifiés dans ce salon", "Never send encrypted messages to unverified devices in this room": "Ne jamais envoyer de message chiffré 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 chiffré aux appareils non-vérifiés dans ce salon depuis cet appareil", "Never send encrypted messages to unverified devices in this room from this device": "Ne jamais envoyer de message chiffré 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 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 password": "Nouveau mot de passe",
"New passwords don't match": "Les mots de passe ne correspondent pas", "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.", "New passwords must match each other.": "Les nouveaux mots de passe doivent être identiques.",
"none": "aucun", "none": "aucun",
"not set": "non défini", "not set": "non défini",
"not specified": "non spécifié", "not specified": "non spécifié",
"(not supported by this browser)": "(non supporté par cet explorateur)", "(not supported by this browser)": "(non supporté par ce navigateur)",
"<not supported>": "<non supporté>", "<not supported>": "<non supporté>",
"NOT verified": "NON vérifié", "NOT verified": "NON vérifié",
"No devices with registered encryption keys": "Pas dappareil avec des clés de chiffrement enregistrées", "No devices with registered encryption keys": "Pas dappareil avec des clés de chiffrement enregistrées",
@ -383,28 +255,25 @@
"Operation failed": "L'opération a échoué", "Operation failed": "L'opération a échoué",
"Bulk Options": "Options de masse", "Bulk Options": "Options de masse",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changer le mot de passe réinitialise actuellement les clés de chiffrement sur tous les appareils, rendant lhistorique chiffré illisible, à moins dexporter les clés du salon en avance de phase puis de les ré-importer. Ceci sera amélioré prochainement.", "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changer le mot de passe réinitialise actuellement les clés de chiffrement sur tous les appareils, rendant lhistorique chiffré illisible, à moins dexporter les clés du salon en avance de phase puis de les ré-importer. Ceci sera amélioré prochainement.",
"Default": "Défaut", "Default": "Par défaut",
"Email address": "Adresse e-mail", "Email address": "Adresse e-mail",
"Error decrypting attachment": "Erreur lors du déchiffrement de la pièce jointe", "Error decrypting attachment": "Erreur lors du déchiffrement de la pièce jointe",
"Failed to set avatar.": "Erreur lors de la définition de la photo de profil.", "Failed to set avatar.": "Erreur lors de la définition de la photo de profil.",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Par sécurité une déconnexion supprimera toutes les clés de chiffrement de ce navigateur. Si vous voulez être capable de déchiffrer lhistorique de votre conversation lors de sessions futures de Riot, merci dexporter les clés pour le salon.", "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "Par sécurité une déconnexion supprimera toutes les clés de chiffrement de ce navigateur. Si vous voulez être capable de déchiffrer lhistorique de votre conversation lors de sessions futures de Riot, merci dexporter les clés pour le salon.",
"Guests can't set avatars. Please register.": "Les visiteurs ne peuvent définir de photo de profil. Merci de vous enregistrer.",
"Guests can't use labs features. Please register.": "Les visiteurs ne peuvent utiliser les fonctionalités du laboratoire. Merci de vous enregistrer.",
"Guests cannot join this room even if explicitly invited.": "Les visiteurs ne peuvent rejoindre ce salon, même si explicitement invités.", "Guests cannot join this room even if explicitly invited.": "Les visiteurs ne peuvent rejoindre ce salon, même si explicitement invités.",
"Invalid file%(extra)s": "Fichier %(extra)s invalide", "Invalid file%(extra)s": "Fichier %(extra)s invalide",
"Mute": "Couper le son", "Mute": "Couper le son",
"No users have specific privileges in this room": "Aucun utilisateur na de privilège spécifique dans ce salon", "No users have specific privileges in this room": "Aucun utilisateur na de privilège spécifique dans ce salon",
"olm version:": "version de olm :", "olm version:": "version de olm :",
"Once you&#39;ve followed the link it contains, click below": "Une fois que vous aurez suivi le lien quil contient, cliquez ci-dessous", "Once you've followed the link it contains, click below": "Une fois que vous aurez suivi le lien quil contient, cliquez ci-dessous",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s a placé un appel %(callType)s.", "%(senderName)s placed a %(callType)s call.": "%(senderName)s a placé un appel %(callType)s.",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Veuillez vérifier vos e-mails et cliquer sur le lien que vous avez reçu. Puis cliquez sur continuer.", "Please check your email and click on the link it contains. Once this is done, click continue.": "Veuillez vérifier vos e-mails et cliquer sur le lien que vous avez reçu. Puis cliquez sur continuer.",
"Power level must be positive integer.": "Le niveau d'autorité doit être un entier positif.", "Power level must be positive integer.": "Le niveau d'autorité doit être un entier positif.",
"Press": "Cliquer",
"Privacy warning": "Alerte de confidentialité", "Privacy warning": "Alerte de confidentialité",
"Privileged Users": "Utilisateur Privilégié", "Privileged Users": "Utilisateur privilégié",
"Profile": "Profil", "Profile": "Profil",
"Reason": "Raison", "Reason": "Raison",
"Revoke Moderator": "Révoquer le Modérateur", "Revoke Moderator": "Révoquer le modérateur",
"Refer a friend to Riot:": "Recommander Riot à un ami :", "Refer a friend to Riot:": "Recommander Riot à un ami :",
"rejected": "rejeté", "rejected": "rejeté",
"%(targetName)s rejected the invitation.": "%(targetName)s a rejeté linvitation.", "%(targetName)s rejected the invitation.": "%(targetName)s a rejeté linvitation.",
@ -414,17 +283,17 @@
"%(senderName)s removed their profile picture.": "%(senderName)s a supprimé sa photo de profil.", "%(senderName)s removed their profile picture.": "%(senderName)s a supprimé sa photo de profil.",
"Remove %(threePid)s?": "Supprimer %(threePid)s ?", "Remove %(threePid)s?": "Supprimer %(threePid)s ?",
"%(senderName)s requested a VoIP conference.": "%(senderName)s a demandé une conférence audio.", "%(senderName)s requested a VoIP conference.": "%(senderName)s a demandé une conférence audio.",
"Report it": "Le rapporter", "Report it": "Le signaler",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Réinitialiser le mot de passe va réinitialiser les clés de chiffrement sur tous les appareils, rendant lhistorique chiffré illisible, à moins que vous ayez exporté les clés du salon en avance de phase puis que vous les ré-importiez. Cela sera amélioré prochainement.", "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Réinitialiser le mot de passe va réinitialiser les clés de chiffrement sur tous les appareils, rendant lhistorique chiffré illisible, à moins que vous ayez exporté les clés du salon en avance de phase puis que vous les ré-importiez. Cela sera amélioré prochainement.",
"restore": "restorer", "restore": "restaurer",
"Return to app": "Retourner à lapplication", "Return to app": "Retourner à lapplication",
"Return to login screen": "Retourner à lécran didentification", "Return to login screen": "Retourner à lécran didentification",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot na pas la permission de vous envoyer des notifications - Merci de vérifier les paramètres de votre explorateur", "Riot does not have permission to send you notifications - please check your browser settings": "Riot na pas la permission de vous envoyer des notifications - merci de vérifier les paramètres de votre navigateur",
"Riot was not given permission to send notifications - please try again": "Riot na pas reçu la permission de vous envoyer des notifications - Merci dessayer à nouveau", "Riot was not given permission to send notifications - please try again": "Riot na pas reçu la permission de vous envoyer des notifications - merci dessayer à nouveau",
"riot-web version:": "Version de riot-web :", "riot-web version:": "Version de riot-web :",
"Room %(roomId)s not visible": "Le salon %(roomId)s n'est pas visible", "Room %(roomId)s not visible": "Le salon %(roomId)s n'est pas visible",
"Room Colour": "Couleur du salon", "Room Colour": "Couleur du salon",
"Room name (optional)": "Nom du salon (optionnel)", "Room name (optional)": "Nom du salon (facultatif)",
"Rooms": "Salons", "Rooms": "Salons",
"Scroll to bottom of page": "Aller en bas de la page", "Scroll to bottom of page": "Aller en bas de la page",
"Scroll to unread messages": "Aller aux messages non-lus", "Scroll to unread messages": "Aller aux messages non-lus",
@ -433,7 +302,7 @@
"Searches DuckDuckGo for results": "Recherche des résultats dans DuckDuckGo", "Searches DuckDuckGo for results": "Recherche des résultats dans DuckDuckGo",
"Send a message (unencrypted)": "Envoyer un message (non chiffré)", "Send a message (unencrypted)": "Envoyer un message (non chiffré)",
"Send an encrypted message": "Envoyer un message chiffré", "Send an encrypted message": "Envoyer un message chiffré",
"Sender device information": "Information de l'appareil de l'expéditeur", "Sender device information": "Informations de l'appareil de l'expéditeur",
"Send Invites": "Envoyer les invitations", "Send Invites": "Envoyer les invitations",
"Send Reset Email": "Envoyer l'e-mail de réinitialisation", "Send Reset Email": "Envoyer l'e-mail de réinitialisation",
"sent an image": "a envoyé une image", "sent an image": "a envoyé une image",
@ -443,7 +312,7 @@
"Server error": "Erreur du serveur", "Server error": "Erreur du serveur",
"Server may be unavailable or overloaded": "Le serveur semble être inaccessible ou surchargé", "Server may be unavailable or overloaded": "Le serveur semble être inaccessible ou surchargé",
"Server may be unavailable, overloaded, or search timed out :(": "Le serveur semble être inaccessible, surchargé ou la recherche a expiré :(", "Server may be unavailable, overloaded, or search timed out :(": "Le serveur semble être inaccessible, surchargé ou la recherche a expiré :(",
"Server may be unavailable, overloaded, or the file too big": "Le serveur semble être inaccessible, surchargé ou le fichier trop important", "Server may be unavailable, overloaded, or the file too big": "Le serveur semble être inaccessible, surchargé ou le fichier est trop volumineux",
"Server may be unavailable, overloaded, or you hit a bug.": "Le serveur semble être indisponible, surchargé, ou vous avez rencontré un problème.", "Server may be unavailable, overloaded, or you hit a bug.": "Le serveur semble être indisponible, surchargé, ou vous avez rencontré un problème.",
"Server unavailable, overloaded, or something else went wrong.": "Le serveur semble être inaccessible, surchargé ou quelque chose s'est mal passé.", "Server unavailable, overloaded, or something else went wrong.": "Le serveur semble être inaccessible, surchargé ou quelque chose s'est mal passé.",
"Session ID": "Identifiant de session", "Session ID": "Identifiant de session",
@ -460,15 +329,13 @@
"Some of your messages have not been sent.": "Certains de vos messages nont pas été envoyés.", "Some of your messages have not been sent.": "Certains de vos messages nont pas été envoyés.",
"Someone": "Quelqu'un", "Someone": "Quelqu'un",
"Sorry, this homeserver is using a login which is not recognised ": "Désolé, ce homeserver utilise un identifiant qui nest pas reconnu ", "Sorry, this homeserver is using a login which is not recognised ": "Désolé, ce homeserver utilise un identifiant qui nest pas reconnu ",
"Start a chat": "Démarrer une conversation", "Start a chat": "Démarrer une discussion",
"Start Chat": "Démarrer une conversation", "Start Chat": "Démarrer une discussion",
"Submit": "Soumettre", "Submit": "Soumettre",
"Success": "Succès", "Success": "Succès",
"tag as %(tagName)s": "marquer comme %(tagName)s", "tag direct chat": "marquer comme discussion directe",
"tag direct chat": "marquer comme conversation directe",
"The default role for new room members is": "Le rôle par défaut des nouveaux membres est", "The default role for new room members is": "Le rôle par défaut des nouveaux membres est",
"The main address for this room is": "L'adresse principale pour ce salon est", "The main address for this room is": "L'adresse principale pour ce salon est",
"This action cannot be performed by a guest user. Please register to be able to do this.": "Cette action ne peut être effectuée par un visiteur. Merci de vous enregistrer afin de pouvoir effectuer cette action.",
"This email address is already in use": "Cette adresse e-mail est déjà utilisée", "This email address is already in use": "Cette adresse e-mail est déjà utilisée",
"This email address was not found": "Cette adresse e-mail na pas été trouvée", "This email address was not found": "Cette adresse e-mail na pas été trouvée",
"%(actionVerb)s this person?": "%(actionVerb)s cette personne ?", "%(actionVerb)s this person?": "%(actionVerb)s cette personne ?",
@ -502,8 +369,7 @@
"to restore": "pour restaurer", "to restore": "pour restaurer",
"To send events of type": "Pour envoyer des évènements du type", "To send events of type": "Pour envoyer des évènements du type",
"To send messages": "Pour envoyer des messages", "To send messages": "Pour envoyer des messages",
"to start a chat with someone": "pour démarrer une conversation avec quelquun", "to start a chat with someone": "pour démarrer une discussion avec quelquun",
"to tag as %(tagName)s": "pour marquer comme %(tagName)s",
"to tag direct chat": "pour marquer comme conversation directe", "to tag direct chat": "pour marquer comme conversation directe",
"To use it, just wait for autocomplete results to load and tab through them.": "Pour lutiliser, attendez simplement que les résultats de lauto-complétion saffichent et défilez avec la touche Tab.", "To use it, just wait for autocomplete results to load and tab through them.": "Pour lutiliser, attendez simplement que les résultats de lauto-complétion saffichent et défilez avec la touche Tab.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Un instant donné de la chronologie na pu être chargé car vous navez pas la permission de le visualiser.", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Un instant donné de la chronologie na pu être chargé car vous navez pas la permission de le visualiser.",
@ -513,7 +379,6 @@
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s a activé le chiffrement de bout-en-bout (algorithme %(algorithm)s).", "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s a activé le chiffrement de bout-en-bout (algorithme %(algorithm)s).",
"Unable to add email address": "Impossible d'ajouter l'adresse e-mail", "Unable to add email address": "Impossible d'ajouter l'adresse e-mail",
"Unable to remove contact information": "Impossible de supprimer les informations du contact", "Unable to remove contact information": "Impossible de supprimer les informations du contact",
"Unable to restore previous session": "Impossible de rétablir la session précédente",
"Unable to verify email address.": "Impossible de vérifier ladresse e-mail.", "Unable to verify email address.": "Impossible de vérifier ladresse e-mail.",
"Unban": "Amnistier (annuler le bannissement)", "Unban": "Amnistier (annuler le bannissement)",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s a amnistié %(targetName)s.", "%(senderName)s unbanned %(targetName)s.": "%(senderName)s a amnistié %(targetName)s.",
@ -524,7 +389,6 @@
"unencrypted": "non chiffré", "unencrypted": "non chiffré",
"unknown device": "appareil inconnu", "unknown device": "appareil inconnu",
"Unknown room %(roomId)s": "Salon inconnu %(roomId)s", "Unknown room %(roomId)s": "Salon inconnu %(roomId)s",
"unknown": "inconnu",
"Unmute": "Activer le son", "Unmute": "Activer le son",
"uploaded a file": "téléchargé un fichier", "uploaded a file": "téléchargé un fichier",
"Upload avatar": "Télécharger une photo de profil", "Upload avatar": "Télécharger une photo de profil",
@ -565,7 +429,7 @@
"You have no visible notifications": "Vous n'avez pas de notifications visibles", "You have no visible notifications": "Vous n'avez pas de notifications visibles",
"you must be a": "vous devez être un", "you must be a": "vous devez être un",
"You need to be able to invite users to do that.": "Vous devez être capable dinviter des utilisateurs pour faire ça.", "You need to be able to invite users to do that.": "Vous devez être capable dinviter des utilisateurs pour faire ça.",
"You need to be logged in.": "Vous devez être connecté.", "You need to be logged in.": "Vous devez être identifié.",
"You need to enter a user name.": "Vous devez entrer un nom dutilisateur.", "You need to enter a user name.": "Vous devez entrer un nom dutilisateur.",
"You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "Vous devez vous connecter à nouveau pour générer les clés de chiffrement pour cet appareil, et soumettre la clé publique à votre homeserver. Cette action ne se reproduira pas ; veuillez nous excuser pour la gêne occasionnée.", "You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.": "Vous devez vous connecter à nouveau pour générer les clés de chiffrement pour cet appareil, et soumettre la clé publique à votre homeserver. Cette action ne se reproduira pas ; veuillez nous excuser pour la gêne occasionnée.",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Votre adresse e-mail ne semble pas associée à un identifiant Matrix sur ce homeserver.", "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Votre adresse e-mail ne semble pas associée à un identifiant Matrix sur ce homeserver.",
@ -574,8 +438,7 @@
"You seem to be in a call, are you sure you want to quit?": "Vous semblez avoir un appel en cours, êtes-vous sûr(e) de vouloir quitter ?", "You seem to be in a call, are you sure you want to quit?": "Vous semblez avoir un appel en cours, êtes-vous sûr(e) de vouloir quitter ?",
"You seem to be uploading files, are you sure you want to quit?": "Vous semblez être en train de télécharger des fichiers, êtes-vous sûr(e) de vouloir quitter ?", "You seem to be uploading files, are you sure you want to quit?": "Vous semblez être en train de télécharger des fichiers, êtes-vous sûr(e) de vouloir quitter ?",
"You should not yet trust it to secure data": "Vous ne pouvez pas encore lui faire confiance pour sécuriser vos données", "You should not yet trust it to secure data": "Vous ne pouvez pas encore lui faire confiance pour sécuriser vos données",
"changing room on a RoomView is not supported": "changer de salon sur un RoomView n'est pas supporté", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Vous ne pourrez pas annuler ce changement car vous promouvez lutilisateur au même niveau d'autorité que le vôtre.",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Vous ne pourrez pas défaire ce changement car vous promouvez lutilisateur aux mêmes pouvoirs que vous.",
"Sun": "Dim", "Sun": "Dim",
"Mon": "Lun", "Mon": "Lun",
"Tue": "Mar", "Tue": "Mar",
@ -619,7 +482,6 @@
"Room": "Salon", "Room": "Salon",
"Connectivity to the server has been lost.": "La connectivité au serveur a été perdue.", "Connectivity to the server has been lost.": "La connectivité au serveur a été perdue.",
"Sent messages will be stored until your connection has returned.": "Les messages envoyés seront stockés jusquà ce que votre connection revienne.", "Sent messages will be stored until your connection has returned.": "Les messages envoyés seront stockés jusquà ce que votre connection revienne.",
"Auto-complete": "Auto-complétion",
"Resend all": "Tout renvoyer", "Resend all": "Tout renvoyer",
"(~%(searchCount)s results)": "(~%(searchCount)s résultats)", "(~%(searchCount)s results)": "(~%(searchCount)s résultats)",
"Cancel": "Annuler", "Cancel": "Annuler",
@ -648,11 +510,11 @@
"%(oneUser)sleft and rejoined": "%(oneUser)sa quitté et à nouveau joint le salon", "%(oneUser)sleft and rejoined": "%(oneUser)sa quitté et à nouveau joint le salon",
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)sont rejeté leurs invitations %(repeats)s fois", "%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)sont rejeté leurs invitations %(repeats)s fois",
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)sa rejeté son invitation %(repeats)s fois", "%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)sa rejeté son invitation %(repeats)s fois",
"%(severalUsers)srejected their invitations": "%(severalUsers)sont rejeté leurs invitations", "%(severalUsers)srejected their invitations": "%(severalUsers)sont rejeté leur invitation",
"%(oneUser)srejected their invitation": "%(oneUser)sa rejeté son invitation", "%(oneUser)srejected their invitation": "%(oneUser)sa rejeté son invitation",
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)sont vu leurs invitations rétractées %(repeats)s fois", "%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)sont vu leur invitation rétractée %(repeats)s fois",
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)sa vu son invitation rétractée %(repeats)s fois", "%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)sa vu son invitation rétractée %(repeats)s fois",
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)sont vu leurs invitations rétractées", "%(severalUsers)shad their invitations withdrawn": "%(severalUsers)sont vu leur invitation rétractée",
"%(oneUser)shad their invitation withdrawn": "%(oneUser)sa vu son invitation rétractée", "%(oneUser)shad their invitation withdrawn": "%(oneUser)sa vu son invitation rétractée",
"were invited %(repeats)s times": "ont été invité(e)s %(repeats)s fois", "were invited %(repeats)s times": "ont été invité(e)s %(repeats)s fois",
"was invited %(repeats)s times": "a été invité(e) %(repeats)s fois", "was invited %(repeats)s times": "a été invité(e) %(repeats)s fois",
@ -692,8 +554,7 @@
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Le fichier exporté est protégé par une phrase secrète. Vous devez entrer cette phrase secrète ici pour déchiffrer le fichier.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Le fichier exporté est protégé par une phrase secrète. Vous devez entrer cette phrase secrète ici pour déchiffrer le fichier.",
"You must join the room to see its files": "Vous devez joindre le salon pour voir ses fichiers", "You must join the room to see its files": "Vous devez joindre le salon pour voir ses fichiers",
"Reject all %(invitedRooms)s invites": "Rejeter la totalité des %(invitedRooms)s invitations", "Reject all %(invitedRooms)s invites": "Rejeter la totalité des %(invitedRooms)s invitations",
"Start new chat": "Démarrer une nouvelle conversation", "Start new chat": "Démarrer une nouvelle discussion",
"Guest users can't invite users. Please register.": "Les visiteurs ne peuvent inviter dautres utilisateurs. Merci de vous enregistrer.",
"Failed to invite": "Echec de l'invitation", "Failed to invite": "Echec de l'invitation",
"Failed to invite user": "Echec lors de l'invitation de l'utilisateur", "Failed to invite user": "Echec lors de l'invitation de l'utilisateur",
"Failed to invite the following users to the %(roomName)s room:": "Echec lors de linvitation des utilisateurs suivants dans le salon %(roomName)s :", "Failed to invite the following users to the %(roomName)s room:": "Echec lors de linvitation des utilisateurs suivants dans le salon %(roomName)s :",
@ -702,7 +563,7 @@
"Unknown error": "Erreur inconnue", "Unknown error": "Erreur inconnue",
"Incorrect password": "Mot de passe incorrect", "Incorrect password": "Mot de passe incorrect",
"This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Ceci rendra votre compte inutilisable de manière permanente. Vous ne pourrez pas enregistrer à nouveau le même identifiant utilisateur.", "This will make your account permanently unusable. You will not be able to re-register the same user ID.": "Ceci rendra votre compte inutilisable de manière permanente. Vous ne pourrez pas enregistrer à nouveau le même identifiant utilisateur.",
"This action is irreversible.": "Cette action est irreversible.", "This action is irreversible.": "Cette action est irréversible.",
"To continue, please enter your password.": "Pour continuer, merci d'entrer votre mot de passe.", "To continue, please enter your password.": "Pour continuer, merci d'entrer votre mot de passe.",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "Pour vérifier que vous pouvez faire confiance à cet appareil, merci de contacter son propriétaire par un autre moyen (par ex. en personne ou par téléphone) et demandez lui si la clé quil/elle voit dans ses Paramètres Utilisateur pour cet appareil correspond à la clé ci-dessous :", "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "Pour vérifier que vous pouvez faire confiance à cet appareil, merci de contacter son propriétaire par un autre moyen (par ex. en personne ou par téléphone) et demandez lui si la clé quil/elle voit dans ses Paramètres Utilisateur pour cet appareil correspond à la clé ci-dessous :",
"Device name": "Nom de l'appareil", "Device name": "Nom de l'appareil",
@ -715,9 +576,8 @@
"Unable to restore session": "Impossible de restaurer la session", "Unable to restore session": "Impossible de restaurer la session",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Si vous avez utilisé une version plus récente de Riot précédemment, votre session risque dêtre incompatible avec cette version. Fermez cette fenêtre et retournez à la version plus récente.", "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Si vous avez utilisé une version plus récente de Riot précédemment, votre session risque dêtre incompatible avec cette version. Fermez cette fenêtre et retournez à la version plus récente.",
"Continue anyway": "Continuer quand même", "Continue anyway": "Continuer quand même",
"Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?": "Votre nom daffichage est la manière dont vous allez apparaître pour les autres quand vous parlerez dans les salons. Que voulez-vous quil soit ?",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Vous êtes en train dajouter à la liste noire des appareils non-vérifiés ; pour envoyer des messages à ces appareils vous devez les vérifier.", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Vous êtes en train dajouter à la liste noire des appareils non-vérifiés ; pour envoyer des messages à ces appareils vous devez les vérifier.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Nous vous recommandons deffectuer le process de vérification pour tous les appareils afin de confirmer quils appartiennent à leurs propriétaires légitimes, mais vous pouvez renvoyer le(s) message(s) sans vérifier si vous préférez.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Nous vous recommandons deffectuer le processus de vérification pour tous les appareils afin de confirmer quils appartiennent à leurs propriétaires légitimes, mais vous pouvez renvoyer le(s) message(s) sans vérifier si vous préférez.",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contient des appareils que vous n'avez encore jamais vus.", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contient des appareils que vous n'avez encore jamais vus.",
"Unknown devices": "Appareils inconnus", "Unknown devices": "Appareils inconnus",
"Unknown Address": "Adresse inconnue", "Unknown Address": "Adresse inconnue",
@ -771,7 +631,6 @@
"Removed or unknown message type": "Type de message inconnu ou supprimé", "Removed or unknown message type": "Type de message inconnu ou supprimé",
"disabled": "désactivé", "disabled": "désactivé",
"enabled": "activé", "enabled": "activé",
"Set a Display Name": "Définir un nom daffichage",
"for %(amount)ss": "depuis %(amount)ss", "for %(amount)ss": "depuis %(amount)ss",
"for %(amount)sm": "depuis %(amount)sm", "for %(amount)sm": "depuis %(amount)sm",
"for %(amount)sh": "depuis %(amount)sh", "for %(amount)sh": "depuis %(amount)sh",
@ -782,19 +641,18 @@
"Device already verified!": "Appareil déjà vérifié !", "Device already verified!": "Appareil déjà vérifié !",
"Export": "Exporter", "Export": "Exporter",
"Failed to register as guest:": "Échec de linscription en tant que visiteur :", "Failed to register as guest:": "Échec de linscription en tant que visiteur :",
"Guest access is disabled on this Home Server.": "Laccès en tant que visiteur est désactivé sur ce serveur.", "Guest access is disabled on this Home Server.": "Laccès en tant que visiteur est désactivé sur ce homeserver.",
"Import": "Importer", "Import": "Importer",
"Incorrect username and/or password.": "Nom dutilisateur et/ou mot de passe incorrect.", "Incorrect username and/or password.": "Nom dutilisateur et/ou mot de passe incorrect.",
"Results from DuckDuckGo": "Résultats de DuckDuckGo", "Results from DuckDuckGo": "Résultats de DuckDuckGo",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Les clés de signature que vous avez transmises correspondent aux clés que vous avez reçues de lappareil %(deviceId)s de %(userId)s. Lappareil est vérifié.", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Les clés de signature que vous avez transmises correspondent aux clés que vous avez reçues de lappareil %(deviceId)s de %(userId)s. Lappareil est vérifié.",
"This Home Server does not support login using email address.": "Ce serveur ne supporte pas lidentification par e-mail.", "This Home Server does not support login using email address.": "Ce serveur ne supporte pas lidentification par e-mail.",
"There was a problem logging in.": "Un problème a été rencontré lors de lidentification.",
"Unknown (user, device) pair:": "Couple (utilisateur, appareil) inconnu :", "Unknown (user, device) pair:": "Couple (utilisateur, appareil) inconnu :",
"Unrecognised command:": "Commande non-reconnue :", "Unrecognised command:": "Commande non-reconnue :",
"Unrecognised room alias:": "Alias de salon non-reconnu :", "Unrecognised room alias:": "Alias de salon non-reconnu :",
"Use compact timeline layout": "Utiliser l'affichage compact", "Use compact timeline layout": "Utiliser l'affichage compact",
"Verified key": "Clé vérifiée", "Verified key": "Clé vérifiée",
"WARNING: Device already verified, but keys do NOT MATCH!": "ATTENTION : Appareil déjà vérifié mais les clés NE CORRESPONDENT PAS !", "WARNING: Device already verified, but keys do NOT MATCH!": "ATTENTION : appareil déjà vérifié mais les clés NE CORRESPONDENT PAS !",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "ATTENTION : ERREUR DE VÉRIFICATION DES CLÉS ! La clé de signature pour %(userId)s et l'appareil %(deviceId)s est “%(fprint)s” et ne correspond pas à la clé “%(fingerprint)s” qui a été fournie. Cela peut signifier que vos communications sont interceptées !", "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "ATTENTION : ERREUR DE VÉRIFICATION DES CLÉS ! La clé de signature pour %(userId)s et l'appareil %(deviceId)s est “%(fprint)s” et ne correspond pas à la clé “%(fingerprint)s” qui a été fournie. Cela peut signifier que vos communications sont interceptées !",
"VoIP": "Voix sur IP", "VoIP": "Voix sur IP",
"Missing Media Permissions, click here to request.": "Manque de permissions pour les médias, cliquer ici pour les demander.", "Missing Media Permissions, click here to request.": "Manque de permissions pour les médias, cliquer ici pour les demander.",
@ -810,10 +668,9 @@
"Are you sure you want to leave the room '%(roomName)s'?": "Êtes-vous sûr de vouloir quitter le salon '%(roomName)s' ?", "Are you sure you want to leave the room '%(roomName)s'?": "Êtes-vous sûr de vouloir quitter le salon '%(roomName)s' ?",
"Custom level": "Niveau personnalisé", "Custom level": "Niveau personnalisé",
"Device ID:": "Identifiant de l'appareil :", "Device ID:": "Identifiant de l'appareil :",
"device id: ": "Identifiant appareil : ", "device id: ": "identifiant appareil : ",
"Device key:": "Clé de lappareil :", "Device key:": "Clé de lappareil :",
"Email address (optional)": "Adresse e-mail (facultatif)", "Email address (optional)": "Adresse e-mail (facultatif)",
"List this room in %(domain)s's room directory?": "Lister ce salon dans le répertoire de %(domain)s ?",
"Mobile phone number (optional)": "Numéro de téléphone (facultatif)", "Mobile phone number (optional)": "Numéro de téléphone (facultatif)",
"Password:": "Mot de passe :", "Password:": "Mot de passe :",
"Register": "S'inscrire", "Register": "S'inscrire",
@ -822,23 +679,20 @@
"Tagged as: ": "Étiquetter comme : ", "Tagged as: ": "Étiquetter comme : ",
"You have <a>disabled</a> URL previews by default.": "Vous avez <a>désactivé</a> les aperçus dURL par défaut.", "You have <a>disabled</a> URL previews by default.": "Vous avez <a>désactivé</a> les aperçus dURL par défaut.",
"You have <a>enabled</a> URL previews by default.": "Vous avez <a>activé</a> les aperçus dURL par défaut.", "You have <a>enabled</a> URL previews by default.": "Vous avez <a>activé</a> les aperçus dURL par défaut.",
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Vous avez entré un contact invalide. Essayez dutiliser leur identifiant Matrix ou leur adresse email.",
"Hide removed messages": "Cacher les messages supprimés", "Hide removed messages": "Cacher les messages supprimés",
"Add": "Ajouter", "Add": "Ajouter",
"%(count)s new messages.one": "%(count)s nouveau message", "%(count)s new messages|one": "%(count)s nouveau message",
"%(count)s new messages.other": "%(count)s nouveaux messages", "%(count)s new messages|other": "%(count)s nouveaux messages",
"Disable markdown formatting": "Désactiver le formattage markdown", "Error: Problem communicating with the given homeserver.": "Erreur : problème de communication avec le homeserver.",
"Error: Problem communicating with the given homeserver.": "Erreur : Problème de communication avec le homeserveur.",
"Failed to fetch avatar URL": "Échec lors de la récupération de lURL de lavatar", "Failed to fetch avatar URL": "Échec lors de la récupération de lURL de lavatar",
"The phone number entered looks invalid": "Le numéro de téléphone entré semble être invalide", "The phone number entered looks invalid": "Le numéro de téléphone entré semble être invalide",
"This room is private or inaccessible to guests. You may be able to join if you register.": "Ce salon est privé ou interdits aux visiteurs. Vous pourrez peut-être le joindre si vous vous enregistrez.", "This room is private or inaccessible to guests. You may be able to join if you register.": "Ce salon est privé ou interdits aux visiteurs. Vous pourrez peut-être le joindre si vous vous enregistrez.",
"Uploading %(filename)s and %(count)s others.zero": "Téléchargement de %(filename)s", "Uploading %(filename)s and %(count)s others|zero": "Téléchargement de %(filename)s",
"Uploading %(filename)s and %(count)s others.one": "Téléchargement de %(filename)s et %(count)s autre", "Uploading %(filename)s and %(count)s others|one": "Téléchargement de %(filename)s et %(count)s autre",
"Uploading %(filename)s and %(count)s others.other": "Téléchargement de %(filename)s et %(count)s autres", "Uploading %(filename)s and %(count)s others|other": "Téléchargement de %(filename)s et %(count)s autres",
"You must <a>register</a> to use this functionality": "Vous devez vous <a>inscrire</a> pour utiliser cette fonctionnalité", "You must <a>register</a> to use this functionality": "Vous devez vous <a>inscrire</a> pour utiliser cette fonctionnalité",
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Tout renvoyer</a> or <a>tout annuler</a> maintenant. Vous pouvez aussi sélectionner des messages individuels à envoyer ou annuler.", "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Tout renvoyer</a> ou <a>tout annuler</a> maintenant. Vous pouvez aussi sélectionner des messages individuels à envoyer ou annuler.",
"Create new room": "Créer un nouveau salon", "Create new room": "Créer un nouveau salon",
"Welcome page": "Page d'accueil",
"Room directory": "Répertoire des salons", "Room directory": "Répertoire des salons",
"Start chat": "Démarrer une discussion", "Start chat": "Démarrer une discussion",
"New Password": "Nouveau mot de passe", "New Password": "Nouveau mot de passe",
@ -856,23 +710,23 @@
"Accept": "Accepter", "Accept": "Accepter",
"Active call (%(roomName)s)": "Appel en cours (%(roomName)s)", "Active call (%(roomName)s)": "Appel en cours (%(roomName)s)",
"Admin tools": "Outils d'administration", "Admin tools": "Outils d'administration",
"Alias (optional)": "Alias (optionnel)", "Alias (optional)": "Alias (facultatif)",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Impossible de se connecter au homeserver - veuillez vérifier votre connexion, assurez vous que vous faites confiance au <a>certificat SSL de votre homeserver</a>, et qu'aucune extension de navigateur ne bloque les requêtes.", "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Impossible de se connecter au homeserver - veuillez vérifier votre connexion, assurez vous que vous faites confiance au <a>certificat SSL de votre homeserver</a>, et qu'aucune extension de navigateur ne bloque les requêtes.",
"<a>Click here</a> to join the discussion!": "<a>Cliquer ici</a> pour joindre la discussion !", "<a>Click here</a> to join the discussion!": "<a>Cliquer ici</a> pour joindre la discussion !",
"Close": "Fermer", "Close": "Fermer",
"Custom": "Personnaliser", "Custom": "Personnaliser",
"Decline": "Décliner", "Decline": "Refuser",
"Disable Notifications": "Désactiver les Notifications", "Disable Notifications": "Désactiver les notifications",
"Drop File Here": "Déposer le Fichier Ici", "Drop File Here": "Déposer le fichier Ici",
"Enable Notifications": "Activer les Notifications", "Enable Notifications": "Activer les notifications",
"Failed to upload profile picture!": "Échec du téléchargement de la photo de profil !", "Failed to upload profile picture!": "Échec du téléchargement de la photo de profil !",
"Incoming call from %(name)s": "Appel entrant de %(name)s", "Incoming call from %(name)s": "Appel entrant de %(name)s",
"Incoming video call from %(name)s": "Appel vidéo entrant de %(name)s", "Incoming video call from %(name)s": "Appel vidéo entrant de %(name)s",
"Incoming voice call from %(name)s": "Appel vocal entrant de %(name)s", "Incoming voice call from %(name)s": "Appel vocal entrant de %(name)s",
"No display name": "Pas de nom d'affichage", "No display name": "Pas de nom d'affichage",
"Otherwise, <a>click here</a> to send a bug report.": "Sinon, <a>cliquer ici</a> pour envoyer un rapport d'erreur.", "Otherwise, <a>click here</a> to send a bug report.": "Sinon, <a>cliquer ici</a> pour envoyer un rapport d'erreur.",
"Private Chat": "Conversation Privée", "Private Chat": "Conversation privée",
"Public Chat": "Conversation Publique", "Public Chat": "Conversation publique",
"Reason: %(reasonText)s": "Raison: %(reasonText)s", "Reason: %(reasonText)s": "Raison: %(reasonText)s",
"Rejoin": "Rejoindre", "Rejoin": "Rejoindre",
"Room contains unknown devices": "Le salon contient des appareils inconnus", "Room contains unknown devices": "Le salon contient des appareils inconnus",
@ -898,8 +752,8 @@
"You have been kicked from %(roomName)s by %(userName)s.": "Vous avez été expulsé de %(roomName)s by %(userName)s.", "You have been kicked from %(roomName)s by %(userName)s.": "Vous avez été expulsé de %(roomName)s by %(userName)s.",
"You may wish to login with a different account, or add this email to this account.": "Vous souhaiteriez peut-être vous identifier avec un autre compte, ou ajouter cette e-mail à votre compte.", "You may wish to login with a different account, or add this email to this account.": "Vous souhaiteriez peut-être vous identifier avec un autre compte, ou ajouter cette e-mail à votre compte.",
"Your home server does not support device management.": "Votre homeserver ne supporte pas la gestion d'appareils.", "Your home server does not support device management.": "Votre homeserver ne supporte pas la gestion d'appareils.",
"(~%(count)s results).one": "(~%(count)s résultat)", "(~%(count)s results)|one": "(~%(count)s résultat)",
"(~%(count)s results).other": "(~%(count)s résultats)", "(~%(count)s results)|other": "(~%(count)s résultats)",
"Device Name": "Nom de l'appareil", "Device Name": "Nom de l'appareil",
"Encrypted by a verified device": "Chiffré par un appareil vérifié", "Encrypted by a verified device": "Chiffré par un appareil vérifié",
"Encrypted by an unverified device": "Chiffré par un appareil non vérifié", "Encrypted by an unverified device": "Chiffré par un appareil non vérifié",
@ -912,7 +766,6 @@
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Joindre avec <voiceText>audio</voiceText> ou <videoText>vidéo</videoText>.", "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Joindre avec <voiceText>audio</voiceText> ou <videoText>vidéo</videoText>.",
"Last seen": "Vu pour la dernière fois", "Last seen": "Vu pour la dernière fois",
"Level:": "Niveau :", "Level:": "Niveau :",
"Searching known users": "Recherche d'utilisateurs connus",
"Set": "Défini", "Set": "Défini",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (pouvoir %(powerLevelNumber)s)", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (pouvoir %(powerLevelNumber)s)",
"(could not connect media)": "(impossible de se connecter au média)", "(could not connect media)": "(impossible de se connecter au média)",
@ -925,7 +778,7 @@
"Do you want to set an email address?": "Souhaitez-vous configurer une adresse e-mail ?", "Do you want to set an email address?": "Souhaitez-vous configurer une adresse e-mail ?",
"This will allow you to reset your password and receive notifications.": "Ceci va vous permettre de réinitialiser votre mot de passe et de recevoir des notifications.", "This will allow you to reset your password and receive notifications.": "Ceci va vous permettre de réinitialiser votre mot de passe et de recevoir des notifications.",
"Press <StartChatButton> to start a chat with someone": "Cliquez sur <StartChatButton> pour entamer une discussion avec quelqu'un", "Press <StartChatButton> to start a chat with someone": "Cliquez sur <StartChatButton> pour entamer une discussion avec quelqu'un",
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "Vous n'avez pas encore rejoint de salle ! Cliquez sur <CreateRoomButton> pour créer une salle ou sur <RoomDirectoryButton> pour explorer l'annuaire", "You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "Vous n'avez pas encore rejoint de salon ! Cliquez sur <CreateRoomButton> pour créer un salon ou sur <RoomDirectoryButton> pour explorer le répertoire",
"To return to your account in future you need to set a password": "Pour pouvoir accéder à votre compte dans le futur, vous devez enregistrer un mot de passe", "To return to your account in future you need to set a password": "Pour pouvoir accéder à votre compte dans le futur, vous devez enregistrer un mot de passe",
"Skip": "Passer", "Skip": "Passer",
"Start verification": "Commencer la vérification", "Start verification": "Commencer la vérification",
@ -935,5 +788,58 @@
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Votre appareil non vérifié '%(displayName)s' demande des clés de chiffrement.", "Your unverified device '%(displayName)s' is requesting encryption keys.": "Votre appareil non vérifié '%(displayName)s' demande des clés de chiffrement.",
"Encryption key request": "Requête de clé de chiffrement", "Encryption key request": "Requête de clé de chiffrement",
"Updates": "Mises à jour", "Updates": "Mises à jour",
"Check for update": "Rechercher une mise à jour" "Check for update": "Rechercher une mise à jour",
"Add a widget": "Ajouter un widget",
"Allow": "Autoriser",
"Changes colour scheme of current room": "Change le jeu de couleur du salon",
"Delete widget": "Supprimer le widget",
"Define the power level of a user": "Définir le niveau de privilèges d'un utilisateur",
"Edit": "Modifier",
"Enable automatic language detection for syntax highlighting": "Activer la détection automatique de langue pour la correction orthographique",
"Hide Apps": "Masquer les applications",
"Hide join/leave messages (invites/kicks/bans unaffected)": "Masquer les messages d'arrivée/départ (n'affecte pas les invitations/exclusions/bannissements)",
"Hide avatar and display name changes": "Masquer les changements d'avatar et de nom",
"Matrix Apps": "Matrix Apps",
"Revoke widget access": "Désactiver les accès du widget",
"Sets the room topic": "Configure le sujet du salon",
"Show Apps": "Afficher les applications",
"To get started, please pick a username!": "Pour débuter, choisissez un nom d'utilisateur !",
"Unable to create widget.": "Impossible de créer le widget.",
"Unbans user with given id": "Amnistie l'utilisateur à partir de son identifiant",
"You are not in this room.": "Vous n'êtes pas dans ce salon.",
"You do not have permission to do that in this room.": "Vous n'avez pas la permission d'effectuer cette action dans ce salon.",
"Autocomplete Delay (ms):": "Délai pour l'autocomplétion (ms) :",
"This Home server does not support groups": "Ce homeserver ne supporte pas les groupes",
"Loading device info...": "Chargement des informations sur l'appareil...",
"Groups": "Groupes",
"Create a new group": "Créer un nouveau groupe",
"Create Group": "Créer le groupe",
"Group Name": "Nom du groupe",
"Example": "Exemple",
"Create": "Créer",
"Group ID": "Identifiant du groupe",
"+example:%(domain)s": "+exemple:%(domain)s",
"Group IDs must be of the form +localpart:%(domain)s": "Les identifiants de groupe doivent être au format +localpart:%(domain)s",
"It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s": "Il n'est pas encore possible de créer des groupes sur votre propre homeserver : utilisez un identifiant de groupe terminant par %(domain)s",
"Room creation failed": "Impossible de créer le salon",
"You are a member of these groups:": "Vous êtes membre des groupes suivants :",
"Create a group to represent your community! Define a set of rooms and your own custom homepage to mark out your space in the Matrix universe.": "Créez un groupe pour représenter votre communauté ! Définissez un jeu de salons et votre propre page d'accueil pour marquer votre espace dans l'univers Matrix.",
"Join an existing group": "Rejoindre un groupe existant",
"To join an existing group you'll have to know its group identifier; this will look something like <i>+example:matrix.org</i>.": "Pour rejoindre un groupe existant, vous devez connaître l'identifiant de ce groupe ; il ressemblera à <i>+exemple:matrix.org</i>.",
"Featured Rooms:": "Salons mis en avant :",
"Error whilst fetching joined groups": "Erreur en récupérant la liste des groupes",
"Featured Users:": "Utilisateurs mis en avant :",
"Edit Group": "Modifier le groupe",
"Automatically replace plain text Emoji": "Remplacer automatiquement le texte par des Emoji",
"Failed to upload image": "Impossible de télécharger l'image",
"Failed to update group": "Impossible de modifier le groupe",
"Hide avatars in user and room mentions": "Masquer les avatars dans les mentions d'utilisateur et de salon",
"Do you want to load widget from URL:": "Charger un widget venant de lURL :",
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget ajouté par %(senderName)s",
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget supprimé par %(senderName)s",
"Publish this room to the public in %(domain)s's room directory?": "Publier ce salon dans le l'annuaire public de %(domain)s ?",
"Integrations Error": "Erreur d'intégration",
"Cannot add any more widgets": "Impossible d'ajouter plus de widgets",
"The maximum permitted number of widgets have already been added to this room.": "Le nombre maximum de widgets autorisés a déjà été atteint pour ce salon.",
"NOTE: Apps are not end-to-end encrypted": "NOTE : Les applications ne sont pas chiffrées de bout en bout"
} }

View file

@ -1,21 +1 @@
{ {}
"ar-ae": "ערבית (U.A.E)",
"ar-bh": "ערבית (בחריין)",
"ar-dz": "ערבית (אלג'יריה)",
"ar-eg": "ערבית (מצריים)",
"ar-iq": "ערבית (עיראק)",
"ar-jo": "ערבית (ירדן)",
"af": "אפריקאית",
"ar-kw": "ערבית (כווית)",
"ar-lb": "ערבית (לבנון)",
"ar-ly": "ערבית (לוב)",
"ar-ma": "ערבית (מרוקו)",
"ar-om": "ערבית (אומן)",
"ar-qa": "ערבית (קטאר)",
"ar-sa": "ערבית (ערב הסעודית)",
"ar-sy": "ערבית (סוריה)",
"ar-tn": "ערבית (תוניסיה)",
"ar-ye": "ערבית (תימן)",
"be": "בלארוסית",
"bg": "בולגרית"
}

Some files were not shown because too many files have changed in this diff Show more