diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 1faffbbdf7..2e2a404338 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -2,7 +2,6 @@ src/components/structures/RoomDirectory.js src/components/structures/RoomStatusBar.js -src/components/structures/RoomView.js src/components/structures/ScrollPanel.js src/components/structures/SearchBox.js src/components/structures/UploadBar.js diff --git a/package.json b/package.json index 9b7d80ca73..b85191dc22 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "react-focus-lock": "^2.4.1", "react-transition-group": "^4.4.1", "resize-observer-polyfill": "^1.5.1", + "rfc4648": "^1.4.0", "sanitize-html": "^1.27.1", "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.2", diff --git a/res/css/_components.scss b/res/css/_components.scss index 45ed6b3300..27ec1088c3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -155,9 +155,12 @@ @import "./views/messages/_UnknownBody.scss"; @import "./views/messages/_ViewSourceEvent.scss"; @import "./views/messages/_common_CryptoEvent.scss"; +@import "./views/right_panel/_BaseCard.scss"; @import "./views/right_panel/_EncryptionInfo.scss"; +@import "./views/right_panel/_RoomSummaryCard.scss"; @import "./views/right_panel/_UserInfo.scss"; @import "./views/right_panel/_VerificationPanel.scss"; +@import "./views/right_panel/_WidgetCard.scss"; @import "./views/room_settings/_AliasSettings.scss"; @import "./views/rooms/_AppsDrawer.scss"; @import "./views/rooms/_Autocomplete.scss"; diff --git a/res/css/structures/_HeaderButtons.scss b/res/css/structures/_HeaderButtons.scss index 9ef40e9d6a..72b663ef0e 100644 --- a/res/css/structures/_HeaderButtons.scss +++ b/res/css/structures/_HeaderButtons.scss @@ -18,6 +18,14 @@ limitations under the License. display: flex; } +.mx_RoomHeader_buttons + .mx_HeaderButtons { + // remove the | separator line for when next to RoomHeaderButtons + // TODO: remove this once when we redo communities and make the right panel similar to the new rooms one + &::before { + content: unset; + } +} + .mx_HeaderButtons::before { content: ""; background-color: $header-divider-color; diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index dc62cb8218..ad1656efbb 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -25,6 +25,7 @@ limitations under the License. padding: 5px; // margin left to not allow the handle to not encroach on the space for the scrollbar margin-left: 8px; + height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel &:hover .mx_RightPanel_ResizeHandle { // Need to use important to override element style attributes diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index c7c0d6fac4..5bf0d953f3 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -68,16 +68,14 @@ limitations under the License. mask-repeat: no-repeat; mask-size: contain; } -} -.mx_RightPanel_membersButton::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); - mask-position: center; -} + &:hover { + background: rgba($accent-color, 0.1); -.mx_RightPanel_filesButton::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); - mask-position: center; + &::before { + background-color: $accent-color; + } + } } .mx_RightPanel_notifsButton::before { @@ -85,6 +83,11 @@ limitations under the License. mask-position: center; } +.mx_RightPanel_roomSummaryButton::before { + mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-position: center; +} + .mx_RightPanel_groupMembersButton::before { mask-image: url('$(res)/img/element-icons/community-members.svg'); mask-position: center; @@ -96,23 +99,11 @@ limitations under the License. } .mx_RightPanel_headerButton_highlight { - background: rgba($accent-color, 0.25); - // make the icon the accent color too &::before { background-color: $accent-color !important; } } -.mx_RightPanel_headerButton:not(.mx_RightPanel_headerButton_highlight) { - &:hover { - background: rgba($accent-color, 0.1); - - &::before { - background-color: $accent-color; - } - } -} - .mx_RightPanel_headerButton_badge { font-size: $font-8px; border-radius: 8px; @@ -146,7 +137,7 @@ limitations under the License. } .mx_RightPanel_empty { - margin-right: -42px; + margin-right: -28px; h2 { font-weight: 700; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 6fa2f2578e..fecac40e4e 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -15,7 +15,6 @@ limitations under the License. */ .mx_UserMenu { - // to make the menu button sort of aligned with the explore button below padding-right: 6px; @@ -59,7 +58,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $tertiary-fg-color; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index 7913058995..d911ac6dfe 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -82,7 +82,6 @@ limitations under the License. } span.mx_IconizedContextMenu_label { // labels - padding-left: 14px; width: 100%; flex: 1; @@ -91,6 +90,10 @@ limitations under the License. overflow: hidden; white-space: nowrap; } + + .mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label { + padding-left: 14px; + } } } diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss new file mode 100644 index 0000000000..26f846fe0a --- /dev/null +++ b/res/css/views/right_panel/_BaseCard.scss @@ -0,0 +1,166 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_BaseCard { + padding: 0 8px; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + + .mx_BaseCard_header { + margin: 8px 0; + + > h2 { + margin: 0 44px; + font-size: $font-18px; + font-weight: $font-semi-bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mx_BaseCard_back, .mx_BaseCard_close { + position: absolute; + background-color: rgba(141, 151, 165, 0.2); + height: 20px; + width: 20px; + margin: 12px; + top: 0; + + &::before { + content: ""; + position: absolute; + height: 20px; + width: 20px; + top: 0; + left: 0; + mask-repeat: no-repeat; + mask-position: center; + background-color: $rightpanel-button-color; + } + } + + .mx_BaseCard_back { + border-radius: 4px; + left: 0; + + &::before { + transform: rotate(90deg); + mask-size: 22px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + } + } + + .mx_BaseCard_close { + border-radius: 10px; + right: 0; + + &::before { + mask-image: url('$(res)/img/icons-close.svg'); + mask-size: 8px; + } + } + } + + .mx_AutoHideScrollbar { + // collapse the margin into a padding to move the scrollbar into the right gutter + margin-right: -8px; + padding-right: 8px; + min-height: 0; + width: 100%; + height: 100%; + } + + .mx_BaseCard_Group { + margin: 20px 0 16px; + + & > * { + margin-left: 12px; + margin-right: 12px; + } + + > h1 { + color: $tertiary-fg-color; + font-size: $font-12px; + font-weight: 500; + } + + .mx_BaseCard_Button { + padding: 10px 38px 10px 12px; + margin: 0; + position: relative; + font-size: $font-13px; + height: 20px; + line-height: 20px; + border-radius: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + &:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + &::after { + content: ''; + position: absolute; + top: 10px; + right: 6px; + height: 20px; + width: 20px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + transform: rotate(270deg); + mask-size: 20px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + } + } + } + + .mx_BaseCard_footer { + padding-top: 4px; + text-align: center; + display: flex; + justify-content: space-around; + + .mx_AccessibleButton_kind_secondary { + color: $secondary-fg-color; + background-color: rgba(141, 151, 165, 0.2); + font-weight: $font-semi-bold; + font-size: $font-14px; + } + + .mx_AccessibleButton_disabled { + cursor: not-allowed; + } + } +} + +.mx_FilePanel, +.mx_UserInfo, +.mx_NotificationPanel, +.mx_MemberList { + &.mx_BaseCard { + padding: 32px 0 0; + + .mx_AutoHideScrollbar { + margin-right: unset; + padding-right: unset; + } + } +} diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss new file mode 100644 index 0000000000..78324c5e89 --- /dev/null +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -0,0 +1,147 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RoomSummaryCard { + .mx_BaseCard_header { + text-align: center; + margin-top: 20px; + + h2 { + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 12px 0 4px; + } + + .mx_RoomSummaryCard_alias { + font-size: $font-13px; + color: $secondary-fg-color; + } + + h2, .mx_RoomSummaryCard_alias { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + .mx_RoomSummaryCard_avatar { + display: inline-flex; + + .mx_RoomSummaryCard_e2ee { + display: inline-block; + position: relative; + width: 54px; + height: 54px; + border-radius: 50%; + background-color: #737d8c; + margin-top: -3px; // alignment + margin-left: -10px; // overlap + border: 3px solid $dark-panel-bg-color; + + &::before { + content: ''; + position: absolute; + top: 13px; + left: 13px; + height: 28px; + width: 28px; + mask-size: cover; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url('$(res)/img/e2e/disabled.svg'); + background-color: #ffffff; + } + } + + .mx_RoomSummaryCard_e2ee_secure { + background-color: #5abff2; + &::before { + mask-image: url('$(res)/img/e2e/normal.svg'); + } + } + } + } + + .mx_RoomSummaryCard_aboutGroup { + .mx_RoomSummaryCard_Button { + padding-left: 44px; + + &::before { + content: ''; + position: absolute; + top: 8px; + left: 10px; + height: 24px; + width: 24px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + } + } + } + + .mx_RoomSummaryCard_appsGroup { + .mx_RoomSummaryCard_Button { + padding-left: 12px; + color: $tertiary-fg-color; + + span { + color: $primary-fg-color; + } + + img { + vertical-align: top; + margin-right: 12px; + border-radius: 4px; + } + + &::before { + content: unset; + } + } + + .mx_RoomSummaryCard_icon_app_pinned::after { + mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + background-color: $accent-color; + transform: unset; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + margin-top: 12px; + margin-bottom: 12px; + font-size: $font-13px; + font-weight: $font-semi-bold; + } +} + +.mx_RoomSummaryCard_icon_people::before { + mask-image: url("$(res)/img/element-icons/room/members.svg"); +} + +.mx_RoomSummaryCard_icon_files::before { + mask-image: url('$(res)/img/element-icons/room/files.svg'); +} + +.mx_RoomSummaryCard_icon_share::before { + mask-image: url('$(res)/img/element-icons/room/share.svg'); +} + +.mx_RoomSummaryCard_icon_settings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); +} diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 6f86d1ad18..9fcf06e5d0 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_UserInfo { +.mx_UserInfo.mx_BaseCard { + // UserInfo has a circular image at the top so it fits between the back & close buttons + padding-top: 0; display: flex; flex-direction: column; flex: 1; diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss new file mode 100644 index 0000000000..315fd5213c --- /dev/null +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -0,0 +1,62 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_WidgetCard { + height: 100%; + display: contents; + + .mx_AppTileFullWidth { + max-width: unset; + height: 100%; + border: 0; + } + + &.mx_WidgetCard_noEdit { + .mx_AccessibleButton_kind_secondary { + margin: 0 12px; + + &:first-child { + // expand the Pin to room primary action + flex-grow: 1; + } + } + } + + .mx_WidgetCard_optionsButton { + position: relative; + height: 18px; + width: 26px; + + &::before { + content: ""; + position: absolute; + width: 20px; + height: 20px; + top: 6px; + left: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); + background-color: $secondary-fg-color; + } + } +} + +.mx_WidgetCard_maxPinnedTooltip { + background-color: $notice-primary-color; + color: #ffffff; +} diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 6be417f631..fee3d61153 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -15,18 +15,39 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* -the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px -the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere), -so the body height would be 300px - 22px (room for title bar) = 278px -BUT! the sticker picker also assumes it's a little less high than that because the iframe -for the sticker picker doesn't have any padding or margin on it's bottom. -so subtracking another 5px, which brings us at 273px. -*/ -$AppsDrawerBodyHeight: 273px; +$MiniAppTileHeight: 114px; .mx_AppsDrawer { - margin: 5px; + margin: 5px 5px 5px 18px; + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; + + .mx_AppsContainer_resizerHandle { + cursor: ns-resize; + border-radius: 3px; + + // Override styles from library + width: unset !important; + height: 4px !important; + + // This is positioned directly below frame + position: absolute; + bottom: -8px !important; // override from library + + // Together, these make the bar 64px wide + // These are also overridden from the library + left: calc(50% - 32px) !important; + right: calc(50% - 32px) !important; + } + + &:hover { + .mx_AppsContainer_resizerHandle { + opacity: 0.8; + background: $primary-fg-color; + } + } } .mx_AppsDrawer_hidden { @@ -36,15 +57,23 @@ $AppsDrawerBodyHeight: 273px; .mx_AppsContainer { display: flex; flex-direction: row; - align-items: center; + align-items: stretch; justify-content: center; + height: 100%; + margin-bottom: 8px; +} + +.mx_AppsDrawer_minimised .mx_AppsContainer { + // override the re-resizable inline styles + height: inherit !important; + min-height: inherit !important; } .mx_AddWidget_button { order: 2; cursor: pointer; padding: 0; - margin: 5px auto 5px auto; + margin: -3px auto 5px 0; color: $accent-color; font-size: $font-12px; } @@ -65,40 +94,52 @@ $AppsDrawerBodyHeight: 273px; .mx_AppTile { max-width: 960px; width: 50%; - margin-right: 5px; border: 5px solid $widget-menu-bar-bg-color; border-radius: 4px; -} + display: flex; + flex-direction: column; -.mx_AppTile:last-child { - margin-right: 1px; + & + .mx_AppTile { + margin-left: 5px; + } } .mx_AppTileFullWidth { max-width: 960px; width: 100%; - height: 100%; margin: 0; padding: 0; border: 5px solid $widget-menu-bar-bg-color; border-radius: 8px; + display: flex; + flex-direction: column; } .mx_AppTile_mini { max-width: 960px; width: 100%; - height: 100%; margin: 0; padding: 0; + display: flex; + flex-direction: column; + height: $MiniAppTileHeight; } -.mx_AppTile_persistedWrapper { - height: $AppsDrawerBodyHeight; +.mx_AppTile.mx_AppTile_minimised, +.mx_AppTileFullWidth.mx_AppTile_minimised, +.mx_AppTile_mini.mx_AppTile_minimised { + height: 14px; } +.mx_AppTile .mx_AppTile_persistedWrapper, +.mx_AppTileFullWidth .mx_AppTile_persistedWrapper, .mx_AppTile_mini .mx_AppTile_persistedWrapper { - height: 114px; - min-width: 300px; + flex: 1; +} + +.mx_AppTile_persistedWrapper div { + width: 100%; + height: 100%; } .mx_AppTileMenuBar { @@ -110,6 +151,7 @@ $AppsDrawerBodyHeight: 273px; align-items: center; justify-content: space-between; cursor: pointer; + width: 100%; } .mx_AppTileMenuBar_expanded { @@ -172,7 +214,7 @@ $AppsDrawerBodyHeight: 273px; } .mx_AppTileBody { - height: $AppsDrawerBodyHeight; + height: 100%; width: 100%; overflow: hidden; } @@ -183,6 +225,13 @@ $AppsDrawerBodyHeight: 273px; overflow: hidden; } +.mx_AppTile .mx_AppTileBody, +.mx_AppTileFullWidth .mx_AppTileBody, +.mx_AppTile_mini .mx_AppTileBody_mini { + height: inherit; + flex: 1; +} + .mx_AppTileBody_mini iframe { border: none; width: 100%; @@ -191,7 +240,7 @@ $AppsDrawerBodyHeight: 273px; .mx_AppTileBody iframe { width: 100%; - height: $AppsDrawerBodyHeight; + height: 100%; overflow: hidden; border: none; padding: 0; @@ -331,7 +380,7 @@ form.mx_Custom_Widget_Form div { align-items: center; font-weight: bold; position: relative; - height: $AppsDrawerBodyHeight; + height: 100%; } .mx_AppLoading .mx_Spinner { @@ -358,3 +407,16 @@ form.mx_Custom_Widget_Form div { .mx_AppLoading iframe { display: none; } + +.mx_AppsDrawer_minimised .mx_AppsContainer_resizerHandle { + display: none; +} + +/* Avoid apptile iframes capturing mouse event focus when resizing */ +.mx_AppsDrawer_resizing iframe { + pointer-events: none; +} + +.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper { + z-index: 1; +} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a880a7bee2..d240877507 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -236,10 +236,6 @@ limitations under the License. } } -.mx_RoomHeader_settingsButton::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); -} - .mx_RoomHeader_forgetButton::before { mask-image: url('$(res)/img/element-icons/leave.svg'); width: 26px; @@ -249,14 +245,6 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); } -.mx_RoomHeader_shareButton::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); -} - -.mx_RoomHeader_manageIntegsButton::before { - mask-image: url('$(res)/img/element-icons/room/integrations.svg'); -} - .mx_RoomHeader_showPanel { height: 16px; } diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 8d1b68dd99..4d26d8a312 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -36,6 +36,10 @@ limitations under the License. } } + .mx_AppTile_persistedWrapper div { + min-width: 300px; + } + .mx_IncomingCallBox { min-width: 250px; background-color: $primary-bg-color; diff --git a/res/img/e2e/disabled.svg b/res/img/e2e/disabled.svg new file mode 100644 index 0000000000..2f6110a36a --- /dev/null +++ b/res/img/e2e/disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/e2e/normal.svg b/res/img/e2e/normal.svg index 23ca78e44d..83b544a326 100644 --- a/res/img/e2e/normal.svg +++ b/res/img/e2e/normal.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/verified.svg b/res/img/e2e/verified.svg index ac4827baed..f90d9db554 100644 --- a/res/img/e2e/verified.svg +++ b/res/img/e2e/verified.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/warning.svg b/res/img/e2e/warning.svg index d42922892a..58f5c3b7d1 100644 --- a/res/img/e2e/warning.svg +++ b/res/img/e2e/warning.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/element-icons/room/default_app.svg b/res/img/element-icons/room/default_app.svg new file mode 100644 index 0000000000..08734170df --- /dev/null +++ b/res/img/element-icons/room/default_app.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/element-icons/room/default_cal.svg b/res/img/element-icons/room/default_cal.svg new file mode 100644 index 0000000000..5bced115cf --- /dev/null +++ b/res/img/element-icons/room/default_cal.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/element-icons/room/default_clock.svg b/res/img/element-icons/room/default_clock.svg new file mode 100644 index 0000000000..cc21716d15 --- /dev/null +++ b/res/img/element-icons/room/default_clock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/default_doc.svg b/res/img/element-icons/room/default_doc.svg new file mode 100644 index 0000000000..93e7507be3 --- /dev/null +++ b/res/img/element-icons/room/default_doc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/room/ellipsis.svg b/res/img/element-icons/room/ellipsis.svg new file mode 100644 index 0000000000..db1db6ec8b --- /dev/null +++ b/res/img/element-icons/room/ellipsis.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/pin-upright.svg b/res/img/element-icons/room/pin-upright.svg new file mode 100644 index 0000000000..9297f62a02 --- /dev/null +++ b/res/img/element-icons/room/pin-upright.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/room-summary.svg b/res/img/element-icons/room/room-summary.svg new file mode 100644 index 0000000000..b6ac258b18 --- /dev/null +++ b/res/img/element-icons/room/room-summary.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 1a361e7b55..e1111a8a94 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,6 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; +import WidgetStore from "../stores/WidgetStore"; declare global { interface Window { @@ -51,6 +52,7 @@ declare global { mxSettingsStore: SettingsStore; mxNotifier: typeof Notifier; mxRightPanelStore: RightPanelStore; + mxWidgetStore: WidgetStore; } interface Document { diff --git a/src/CallHandler.js b/src/CallHandler.js index 18f6aeb98a..27e8e34e16 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -1,7 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017, 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -67,6 +67,7 @@ import {generateHumanReadableId} from "./utils/NamingUtils"; import {Jitsi} from "./widgets/Jitsi"; import {WidgetType} from "./widgets/WidgetType"; import {SettingLevel} from "./settings/SettingLevel"; +import {base32} from "rfc4648"; global.mxCalls = { //room_id: MatrixCall @@ -388,10 +389,21 @@ async function _startCallApp(roomId, type) { return; } - const confId = `JitsiConference${generateHumanReadableId()}`; const jitsiDomain = Jitsi.getInstance().preferredDomain; + const jitsiAuth = await Jitsi.getInstance().getJitsiAuth(); + let confId; + if (jitsiAuth === 'openidtoken-jwt') { + // Create conference ID from room ID + // For compatibility with Jitsi, use base32 without padding. + // More details here: + // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification + confId = base32.stringify(Buffer.from(roomId), { pad: false }); + } else { + // Create a random human readable conference ID + confId = `JitsiConference${generateHumanReadableId()}`; + } - let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl(); + let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth}); // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets const parsedUrl = new URL(widgetUrl); @@ -403,6 +415,7 @@ async function _startCallApp(roomId, type) { conferenceId: confId, isAudioOnly: type === 'voice', domain: jitsiDomain, + auth: jitsiAuth, }; const widgetId = ( diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 661ab74e6f..1dc4a8abc1 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -154,6 +154,19 @@ export const Commands = [ }, category: CommandCategories.messages, }), + new Command({ + command: 'lenny', + args: '', + description: _td('Prepends ( ͡° ͜ʖ ͡°) to a plain-text message'), + runFn: function(roomId, args) { + let message = '( ͡° ͜ʖ ͡°)'; + if (args) { + message = message + ' ' + args; + } + return success(MatrixClientPeg.get().sendTextMessage(roomId, message)); + }, + category: CommandCategories.messages, + }), new Command({ command: 'plain', args: '', diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index 6aed08c39d..c68e926ac1 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -186,7 +186,14 @@ export default class WidgetMessaging { isUserWidget: this.isUserWidget, onFinished: async (confirm) => { - const responseBody = {success: confirm}; + const responseBody = { + // Legacy (early draft) fields + success: confirm, + + // New style MSC1960 fields + state: confirm ? "allowed" : "blocked", + original_request_id: ev.requestId, // eslint-disable-line camelcase + }; if (confirm) { const credentials = await MatrixClientPeg.get().getOpenIdToken(); Object.assign(responseBody, credentials); diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 64e0160d83..884f77aba5 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {CSSProperties, useRef, useState} from "react"; +import React, {CSSProperties, RefObject, useRef, useState} from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; @@ -416,8 +416,8 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None return menuOptions; }; -export const useContextMenu = () => { - const button = useRef(null); +export const useContextMenu = (): [boolean, RefObject, () => void, () => void, (val: boolean) => void] => { + const button = useRef(null); const [isOpen, setIsOpen] = useState(false); const open = () => { setIsOpen(true); diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8aa1192458..6d618d0b9d 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -23,6 +23,8 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; +import BaseCard from "../views/right_panel/BaseCard"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; /* * Component which shows the filtered file using a TimelinePanel @@ -30,6 +32,7 @@ import { _t } from '../../languageHandler'; class FilePanel extends React.Component { static propTypes = { roomId: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, }; // This is used to track if a decrypted event was a live event and should be @@ -188,18 +191,26 @@ class FilePanel extends React.Component { render() { if (MatrixClientPeg.get().isGuest()) { - return
+ return
{ _t("You must register to use this functionality", {}, { 'a': (sub) => { sub } }) }
-
; + ; } else if (this.noRoom) { - return
+ return
{ _t("You must join the room to see its files") }
-
; + ; } // wrap a TimelinePanel with the jump-to-event bits turned off. @@ -215,7 +226,12 @@ class FilePanel extends React.Component { // console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " + // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); return ( -
+ -
+ ); } else { return ( -
+ -
+ ); } } diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 83f70eb72a..5dadba983a 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -1322,7 +1322,7 @@ export default class GroupView extends React.Component { - + { this._getMembershipSection() } { this._getGroupSection() } diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index e427eb92cb..1ac15caa4c 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -76,7 +76,6 @@ interface IProps { hideToSRUsers: boolean; resizeNotifier: ResizeNotifier; middleDisabled: boolean; - initialEventPixelOffset: number; leftDisabled: boolean; rightDisabled: boolean; // eslint-disable-next-line camelcase @@ -257,6 +256,12 @@ class LoggedInView extends React.Component { window.localStorage.setItem("mx_lhs_size", '' + size); this.props.resizeNotifier.notifyLeftHandleResized(); }, + onResizeStart: () => { + this.props.resizeNotifier.startResizing(); + }, + onResizeStop: () => { + this.props.resizeNotifier.stopResizing(); + }, }; const resizer = new Resizer( this._resizeContainer.current, @@ -629,7 +634,6 @@ class LoggedInView extends React.Component { thirdPartyInvite={this.props.thirdPartyInvite} oobData={this.props.roomOobData} viaServers={this.props.viaServers} - eventPixelOffset={this.props.initialEventPixelOffset} key={this.props.currentRoomId || 'roomview'} disabled={this.props.middleDisabled} ConferenceHandler={this.props.ConferenceHandler} @@ -650,12 +654,13 @@ class LoggedInView extends React.Component { break; case PageTypes.UserView: - pageElement = ; + pageElement = ; break; case PageTypes.GroupView: pageElement = ; break; } diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index 800ed76bb9..47dfe83ad6 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -19,9 +19,18 @@ import React from 'react'; import { Resizable } from 're-resizable'; export default class MainSplit extends React.Component { - _onResized = (event, direction, refToElement, delta) => { + _onResizeStart = () => { + this.props.resizeNotifier.startResizing(); + }; + + _onResize = () => { + this.props.resizeNotifier.notifyRightHandleResized(); + }; + + _onResizeStop = (event, direction, refToElement, delta) => { + this.props.resizeNotifier.stopResizing(); window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width); - } + }; _loadSidePanelSize() { let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10); @@ -58,7 +67,9 @@ export default class MainSplit extends React.Component { bottomLeft: false, topLeft: false, }} - onResizeStop={this._onResized} + onResizeStart={this._onResizeStart} + onResize={this._onResize} + onResizeStop={this._onResizeStop} className="mx_RightPanel_ResizeWrapper" handleClasses={{left: "mx_RightPanel_ResizeHandle"}} > diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.js index 6ae7f91142..2889afc1fc 100644 --- a/src/components/structures/NotificationPanel.js +++ b/src/components/structures/NotificationPanel.js @@ -17,14 +17,21 @@ limitations under the License. */ import React from 'react'; +import PropTypes from "prop-types"; + import { _t } from '../../languageHandler'; import {MatrixClientPeg} from "../../MatrixClientPeg"; import * as sdk from "../../index"; +import BaseCard from "../views/right_panel/BaseCard"; /* * Component which shows the global notification list using a TimelinePanel */ class NotificationPanel extends React.Component { + static propTypes = { + onClose: PropTypes.func.isRequired, + }; + render() { // wrap a TimelinePanel with the jump-to-event bits turned off. const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); @@ -35,28 +42,27 @@ class NotificationPanel extends React.Component {

{_t('You have no visible notifications in this room.')}

); + let content; const timelineSet = MatrixClientPeg.get().getNotifTimelineSet(); if (timelineSet) { - return ( -
- -
+ content = ( + ); } else { console.error("No notifTimelineSet available!"); - return ( -
- -
- ); + content = ; } + + return + { content } + ; } } diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 11416b29fb..6c6d8700a5 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -32,6 +32,9 @@ import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPa import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import {Action} from "../../dispatcher/actions"; +import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; +import WidgetCard from "../views/right_panel/WidgetCard"; +import defaultDispatcher from "../../dispatcher/dispatcher"; export default class RightPanel extends React.Component { static get propTypes() { @@ -47,10 +50,10 @@ export default class RightPanel extends React.Component { constructor(props, context) { super(props, context); this.state = { + ...RightPanelStore.getSharedInstance().roomPanelPhaseParams, phase: this._getPhaseFromProps(), isUserPrivilegedInGroup: null, member: this._getUserForPanel(), - verificationRequest: RightPanelStore.getSharedInstance().roomPanelPhaseParams.verificationRequest, }; this.onAction = this.onAction.bind(this); this.onRoomStateMember = this.onRoomStateMember.bind(this); @@ -102,10 +105,6 @@ export default class RightPanel extends React.Component { } return RightPanelPhases.RoomMemberInfo; } else { - if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) { - dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList}); - return RightPanelPhases.RoomMemberList; - } return rps.roomPanelPhase; } } @@ -186,6 +185,7 @@ export default class RightPanel extends React.Component { event: payload.event, verificationRequest: payload.verificationRequest, verificationRequestPromise: payload.verificationRequestPromise, + widgetId: payload.widgetId, }); } } @@ -213,6 +213,14 @@ export default class RightPanel extends React.Component { } }; + onClose = () => { + // the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here + defaultDispatcher.dispatch({ + action: Action.ToggleRightPanel, + type: this.props.groupId ? "group" : "room", + }); + }; + render() { const MemberList = sdk.getComponent('rooms.MemberList'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); @@ -225,36 +233,42 @@ export default class RightPanel extends React.Component { const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo'); let panel =
; + const roomId = this.props.room ? this.props.room.roomId : undefined; switch (this.state.phase) { case RightPanelPhases.RoomMemberList: - if (this.props.room.roomId) { - panel = ; + if (roomId) { + panel = ; } break; + case RightPanelPhases.GroupMemberList: if (this.props.groupId) { panel = ; } break; + case RightPanelPhases.GroupRoomList: panel = ; break; + case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.EncryptionPanel: panel = ; break; + case RightPanelPhases.Room3pidMemberInfo: - panel = ; + panel = ; break; + case RightPanelPhases.GroupMemberInfo: panel = ; break; + case RightPanelPhases.GroupRoomInfo: panel = ; break; + case RightPanelPhases.NotificationPanel: - panel = ; + panel = ; break; + case RightPanelPhases.FilePanel: - panel = ; + panel = ; + break; + + case RightPanelPhases.RoomSummary: + panel = ; + break; + + case RightPanelPhases.Widget: + panel = ; break; } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.tsx similarity index 75% rename from src/components/structures/RoomView.js rename to src/components/structures/RoomView.tsx index ed2e5645e9..738042c680 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.tsx @@ -21,27 +21,27 @@ limitations under the License. // - Search results component // - Drag and drop -import shouldHideEvent from '../../shouldHideEvent'; - import React, {createRef} from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { _t } from '../../languageHandler'; -import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks'; +import {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import {EventSubscription} from "fbemitter"; +import shouldHideEvent from '../../shouldHideEvent'; +import {_t} from '../../languageHandler'; +import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks'; +import ResizeNotifier from '../../utils/ResizeNotifier'; import ContentMessages from '../../ContentMessages'; import Modal from '../../Modal'; import * as sdk from '../../index'; import CallHandler from '../../CallHandler'; import dis from '../../dispatcher/dispatcher'; import Tinter from '../../Tinter'; -import rate_limited_func from '../../ratelimitedfunc'; +import rateLimitedFunc from '../../ratelimitedfunc'; import * as ObjectUtils from '../../ObjectUtils'; import * as Rooms from '../../Rooms'; import eventSearch, {searchPagination} from '../../Searching'; - import {isOnlyCtrlOrCmdIgnoreShiftKeyEvent, isOnlyCtrlOrCmdKeyEvent, Key} from '../../Keyboard'; - import MainSplit from './MainSplit'; import RightPanel from './RightPanel'; import RoomViewStore from '../../stores/RoomViewStore'; @@ -53,12 +53,28 @@ import RightPanelStore from "../../stores/RightPanelStore"; import {haveTileForEvent} from "../views/rooms/EventTile"; import RoomContext from "../../contexts/RoomContext"; import MatrixClientContext from "../../contexts/MatrixClientContext"; -import { shieldStatusForRoom } from '../../utils/ShieldUtils'; +import {E2EStatus, shieldStatusForRoom} from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; +import {IMatrixClientCreds} from "../../MatrixClientPeg"; +import ScrollPanel from "./ScrollPanel"; +import TimelinePanel from "./TimelinePanel"; +import ErrorBoundary from "../views/elements/ErrorBoundary"; +import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; +import ForwardMessage from "../views/rooms/ForwardMessage"; +import SearchBar from "../views/rooms/SearchBar"; +import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; +import RoomRecoveryReminder from "../views/rooms/RoomRecoveryReminder"; +import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; +import AuxPanel from "../views/rooms/AuxPanel"; +import RoomHeader from "../views/rooms/RoomHeader"; +import TintableSvg from "../views/elements/TintableSvg"; +import type * as ConferenceHandler from '../../VectorConferenceHandler'; +import {XOR} from "../../@types/common"; const DEBUG = false; -let debuglog = function() {}; +let debuglog = function(msg: string) {}; const BROWSER_SUPPORTS_SANDBOX = 'sandbox' in document.createElement('iframe'); @@ -67,36 +83,132 @@ if (DEBUG) { debuglog = console.log.bind(console); } -export default class RoomView extends React.Component { - static propTypes = { - ConferenceHandler: PropTypes.any, +interface IProps { + ConferenceHandler?: ConferenceHandler; - // Called with the credentials of a registered user (if they were a ROU that - // transitioned to PWLU) - onRegistered: PropTypes.func, - - // An object representing a third party invite to join this room - // Fields: - // * inviteSignUrl (string) The URL used to join this room from an email invite - // (given as part of the link in the invite email) - // * invitedEmail (string) The email address that was invited to this room - thirdPartyInvite: PropTypes.object, - - // Any data about the room that would normally come from the homeserver - // but has been passed out-of-band, eg. the room name and avatar URL - // from an email invite (a workaround for the fact that we can't - // get this information from the HS using an email invite). - // Fields: - // * name (string) The room's name - // * avatarUrl (string) The mxc:// avatar URL for the room - // * inviterName (string) The display name of the person who - // * invited us to the room - oobData: PropTypes.object, - - // Servers the RoomView can use to try and assist joins - viaServers: PropTypes.arrayOf(PropTypes.string), + // An object representing a third party invite to join this room + // Fields: + // * inviteSignUrl (string) The URL used to join this room from an email invite + // (given as part of the link in the invite email) + // * invitedEmail (string) The email address that was invited to this room + thirdPartyInvite?: { + inviteSignUrl: string; + invitedEmail: string; }; + // Any data about the room that would normally come from the homeserver + // but has been passed out-of-band, eg. the room name and avatar URL + // from an email invite (a workaround for the fact that we can't + // get this information from the HS using an email invite). + // Fields: + // * name (string) The room's name + // * avatarUrl (string) The mxc:// avatar URL for the room + // * inviterName (string) The display name of the person who + // * invited us to the room + oobData?: { + name?: string; + avatarUrl?: string; + inviterName?: string; + }; + + // Servers the RoomView can use to try and assist joins + viaServers?: string[]; + + autoJoin?: boolean; + disabled?: boolean; + resizeNotifier: ResizeNotifier; + + // Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU) + onRegistered?(credentials: IMatrixClientCreds): void; +} + +export interface IState { + room?: Room; + roomId?: string; + roomAlias?: string; + roomLoading: boolean; + peekLoading: boolean; + shouldPeek: boolean; + // used to trigger a rerender in TimelinePanel once the members are loaded, + // so RR are rendered again (now with the members available), ... + membersLoaded: boolean; + // The event to be scrolled to initially + initialEventId?: string; + // The offset in pixels from the event with which to scroll vertically + initialEventPixelOffset?: number; + // Whether to highlight the event scrolled to + isInitialEventHighlighted?: boolean; + forwardingEvent?: MatrixEvent; + numUnreadMessages: number; + draggingFile: boolean; + searching: boolean; + searchTerm?: string; + searchScope?: "All" | "Room"; + searchResults?: XOR<{}, { + count: number; + highlights: string[]; + results: MatrixEvent[]; + next_batch: string; // eslint-disable-line camelcase + }>; + searchHighlights?: string[]; + searchInProgress?: boolean; + callState?: string; + guestsCanJoin: boolean; + canPeek: boolean; + showApps: boolean; + isAlone: boolean; + isPeeking: boolean; + showingPinned: boolean; + showReadReceipts: boolean; + showRightPanel: boolean; + // error object, as from the matrix client/server API + // If we failed to load information about the room, + // store the error here. + roomLoadError?: Error; + // Have we sent a request to join the room that we're waiting to complete? + joining: boolean; + // 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 + // 'scroll to bottom' knob, among a couple of other things. + atEndOfLiveTimeline: boolean; + // used by componentDidUpdate to avoid unnecessary checks + atEndOfLiveTimelineInit: boolean; + showTopUnreadMessagesBar: boolean; + auxPanelMaxHeight?: number; + statusBarVisible: boolean; + // We load this later by asking the js-sdk to suggest a version for us. + // This object is the result of Room#getRecommendedVersion() + upgradeRecommendation?: { + version: string; + needsUpgrade: boolean; + urgent: boolean; + }; + canReact: boolean; + canReply: boolean; + useIRCLayout: boolean; + matrixClientIsReady: boolean; + showUrlPreview?: boolean; + e2eStatus?: E2EStatus; + displayConfCallNotification?: boolean; + rejecting?: boolean; + rejectError?: Error; +} + +export default class RoomView extends React.Component { + private readonly dispatcherRef: string; + private readonly roomStoreToken: EventSubscription; + private readonly rightPanelStoreToken: EventSubscription; + private readonly showReadReceiptsWatchRef: string; + private readonly layoutWatcherRef: string; + + private unmounted = false; + private permalinkCreators: Record = {}; + private searchId: number; + + private roomView = createRef(); + private searchResultsPanel = createRef(); + private messagePanel: TimelinePanel; + static contextType = MatrixClientContext; constructor(props, context) { @@ -104,26 +216,11 @@ export default class RoomView extends React.Component { const llMembers = this.context.hasLazyLoadMembersEnabled(); this.state = { - room: null, roomId: null, roomLoading: true, peekLoading: false, shouldPeek: true, - - // Media limits for uploading. - mediaConfig: undefined, - - // used to trigger a rerender in TimelinePanel once the members are loaded, - // so RR are rendered again (now with the members available), ... membersLoaded: !llMembers, - // The event to be scrolled to initially - initialEventId: null, - // The offset in pixels from the event with which to scroll vertically - initialEventPixelOffset: null, - // Whether to highlight the event scrolled to - isInitialEventHighlighted: null, - - forwardingEvent: null, numUnreadMessages: 0, draggingFile: false, searching: false, @@ -137,36 +234,14 @@ export default class RoomView extends React.Component { showingPinned: false, showReadReceipts: true, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, - - // error object, as from the matrix client/server API - // If we failed to load information about the room, - // store the error here. - 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 - // the end of the live timeline. It has the effect of hiding the - // 'scroll to bottom' knob, among a couple of other things. atEndOfLiveTimeline: true, - atEndOfLiveTimelineInit: false, // used by componentDidUpdate to avoid unnecessary checks - + atEndOfLiveTimelineInit: false, showTopUnreadMessagesBar: false, - - auxPanelMaxHeight: undefined, - statusBarVisible: false, - - // We load this later by asking the js-sdk to suggest a version for us. - // This object is the result of Room#getRecommendedVersion() - upgradeRecommendation: null, - canReact: false, canReply: false, - useIRCLayout: SettingsStore.getValue("useIRCLayout"), - matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }; @@ -184,31 +259,28 @@ export default class RoomView extends React.Component { this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); // Start listening for RoomViewStore updates - this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); - this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); + this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); + this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); - WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate); - this._showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, - this._onReadReceiptsChange); - - this._roomView = createRef(); - this._searchResultsPanel = createRef(); - - this._layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); + WidgetEchoStore.on('update', this.onWidgetEchoStoreUpdate); + this.showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, + this.onReadReceiptsChange); + this.layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); } // TODO: [REACT-WARNING] Move into constructor + // eslint-disable-next-line camelcase UNSAFE_componentWillMount() { - this._onRoomViewStoreUpdate(true); + this.onRoomViewStoreUpdate(true); } - _onReadReceiptsChange = () => { + private onReadReceiptsChange = () => { this.setState({ showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId), }); }; - _onRoomViewStoreUpdate = initial => { + private onRoomViewStoreUpdate = (initial?: boolean) => { if (this.unmounted) { return; } @@ -230,7 +302,7 @@ export default class RoomView extends React.Component { const roomId = RoomViewStore.getRoomId(); - const newState = { + const newState: Pick = { roomId, roomAlias: RoomViewStore.getRoomAlias(), roomLoading: RoomViewStore.isRoomLoading(), @@ -266,8 +338,8 @@ export default class RoomView extends React.Component { if (initial) { newState.room = this.context.getRoom(newState.roomId); if (newState.room) { - newState.showApps = this._shouldShowApps(newState.room); - this._onRoomLoaded(newState.room); + newState.showApps = this.shouldShowApps(newState.room); + this.onRoomLoaded(newState.room); } } @@ -300,48 +372,47 @@ export default class RoomView extends React.Component { // 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) { - this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); + this.setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); } }; - _getRoomId() { - // According to `_onRoomViewStoreUpdate`, `state.roomId` can be null + private getRoomId = () => { + // According to `onRoomViewStoreUpdate`, `state.roomId` can be null // if we have a room alias we haven't resolved yet. To work around this, // first we'll try the room object if it's there, and then fallback to // the bare room ID. (We may want to update `state.roomId` after // resolving aliases, so we could always trust it.) return this.state.room ? this.state.room.roomId : this.state.roomId; - } + }; - _getPermalinkCreatorForRoom(room) { - if (!this._permalinkCreators) this._permalinkCreators = {}; - if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId]; + private getPermalinkCreatorForRoom(room: Room) { + if (this.permalinkCreators[room.roomId]) return this.permalinkCreators[room.roomId]; - this._permalinkCreators[room.roomId] = new RoomPermalinkCreator(room); + this.permalinkCreators[room.roomId] = new RoomPermalinkCreator(room); if (this.state.room && room.roomId === this.state.room.roomId) { // We want to watch for changes in the creator for the primary room in the view, but // don't need to do so for search results. - this._permalinkCreators[room.roomId].start(); + this.permalinkCreators[room.roomId].start(); } else { - this._permalinkCreators[room.roomId].load(); + this.permalinkCreators[room.roomId].load(); } - return this._permalinkCreators[room.roomId]; + return this.permalinkCreators[room.roomId]; } - _stopAllPermalinkCreators() { - if (!this._permalinkCreators) return; - for (const roomId of Object.keys(this._permalinkCreators)) { - this._permalinkCreators[roomId].stop(); + private stopAllPermalinkCreators() { + if (!this.permalinkCreators) return; + for (const roomId of Object.keys(this.permalinkCreators)) { + this.permalinkCreators[roomId].stop(); } } - _onWidgetEchoStoreUpdate = () => { + private onWidgetEchoStoreUpdate = () => { this.setState({ - showApps: this._shouldShowApps(this.state.room), + showApps: this.shouldShowApps(this.state.room), }); }; - _setupRoom(room, roomId, joining, shouldPeek) { + private setupRoom(room: Room, roomId: string, joining: boolean, shouldPeek: boolean) { // 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 publicly join or were invited to. (we can /join) @@ -374,7 +445,7 @@ export default class RoomView extends React.Component { room: room, peekLoading: false, }); - this._onRoomLoaded(room); + this.onRoomLoaded(room); }).catch((err) => { if (this.unmounted) { return; @@ -405,7 +476,7 @@ export default class RoomView extends React.Component { } } - _shouldShowApps(room) { + private shouldShowApps(room: Room) { if (!BROWSER_SUPPORTS_SANDBOX) return false; // Check if user has previously chosen to hide the app drawer for this @@ -419,13 +490,13 @@ export default class RoomView extends React.Component { } componentDidMount() { - const call = this._getCallForRoom(); + const call = this.getCallForRoom(); const callState = call ? call.call_state : "ended"; this.setState({ callState: callState, }); - this._updateConfCallNotification(); + this.updateConfCallNotification(); window.addEventListener('beforeunload', this.onPageUnload); if (this.props.resizeNotifier) { @@ -442,8 +513,8 @@ export default class RoomView extends React.Component { } componentDidUpdate() { - if (this._roomView.current) { - const roomView = this._roomView.current; + if (this.roomView.current) { + const roomView = this.roomView.current; if (!roomView.ondrop) { roomView.addEventListener('drop', this.onDrop); roomView.addEventListener('dragover', this.onDragOver); @@ -457,10 +528,10 @@ export default class RoomView extends React.Component { // in render() prevents the ref from being set on first mount, so we try and // catch the messagePanel when it does mount. Because we only want the ref once, // we use a boolean flag to avoid duplicate work. - if (this._messagePanel && !this.state.atEndOfLiveTimelineInit) { + if (this.messagePanel && !this.state.atEndOfLiveTimelineInit) { this.setState({ atEndOfLiveTimelineInit: true, - atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(), + atEndOfLiveTimeline: this.messagePanel.isAtEndOfLiveTimeline(), }); } } @@ -474,7 +545,7 @@ export default class RoomView extends React.Component { // update the scroll map before we get unmounted if (this.state.roomId) { - RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState()); + RoomScrollStateStore.setScrollState(this.state.roomId, this.getScrollState()); } if (this.state.shouldPeek) { @@ -482,14 +553,14 @@ export default class RoomView extends React.Component { } // stop tracking room changes to format permalinks - this._stopAllPermalinkCreators(); + this.stopAllPermalinkCreators(); - if (this._roomView.current) { + if (this.roomView.current) { // disconnect the D&D event listeners from the room view. This // is really just for hygiene - we're going to be // deleted anyway, so it doesn't matter if the event listeners // don't get cleaned up. - const roomView = this._roomView.current; + const roomView = this.roomView.current; roomView.removeEventListener('drop', this.onDrop); roomView.removeEventListener('dragover', this.onDragOver); roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd); @@ -519,55 +590,54 @@ export default class RoomView extends React.Component { document.removeEventListener("keydown", this.onNativeKeyDown); // Remove RoomStore listener - if (this._roomStoreToken) { - this._roomStoreToken.remove(); + if (this.roomStoreToken) { + this.roomStoreToken.remove(); } // Remove RightPanelStore listener - if (this._rightPanelStoreToken) { - this._rightPanelStoreToken.remove(); + if (this.rightPanelStoreToken) { + this.rightPanelStoreToken.remove(); } - WidgetEchoStore.removeListener('update', this._onWidgetEchoStoreUpdate); + WidgetEchoStore.removeListener('update', this.onWidgetEchoStoreUpdate); - if (this._showReadReceiptsWatchRef) { - SettingsStore.unwatchSetting(this._showReadReceiptsWatchRef); - this._showReadReceiptsWatchRef = null; + if (this.showReadReceiptsWatchRef) { + SettingsStore.unwatchSetting(this.showReadReceiptsWatchRef); } // cancel any pending calls to the rate_limited_funcs - this._updateRoomMembers.cancelPendingCall(); + this.updateRoomMembers.cancelPendingCall(); // no need to do this as Dir & Settings are now overlays. It just burnt CPU. // console.log("Tinter.tint from RoomView.unmount"); // Tinter.tint(); // reset colourscheme - SettingsStore.unwatchSetting(this._layoutWatcherRef); + SettingsStore.unwatchSetting(this.layoutWatcherRef); } - onLayoutChange = () => { + private onLayoutChange = () => { this.setState({ useIRCLayout: SettingsStore.getValue("useIRCLayout"), }); }; - _onRightPanelStoreUpdate = () => { + private onRightPanelStoreUpdate = () => { this.setState({ showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, }); }; - onPageUnload = event => { + private onPageUnload = event => { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { return event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"); - } else if (this._getCallForRoom() && this.state.callState !== 'ended') { + } else if (this.getCallForRoom() && this.state.callState !== 'ended') { return event.returnValue = _t("You seem to be in a call, are you sure you want to quit?"); } }; // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire - onNativeKeyDown = ev => { + private onNativeKeyDown = ev => { let handled = false; const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); @@ -593,13 +663,13 @@ export default class RoomView extends React.Component { } }; - onReactKeyDown = ev => { + private onReactKeyDown = ev => { let handled = false; switch (ev.key) { case Key.ESCAPE: if (!ev.altKey && !ev.ctrlKey && !ev.shiftKey && !ev.metaKey) { - this._messagePanel.forgetReadMarker(); + this.messagePanel.forgetReadMarker(); this.jumpToLiveTimeline(); handled = true; } @@ -624,20 +694,21 @@ export default class RoomView extends React.Component { } }; - onAction = payload => { + private onAction = payload => { switch (payload.action) { case 'message_send_failed': case 'message_sent': - this._checkIfAlone(this.state.room); + this.checkIfAlone(this.state.room); break; case 'post_sticker_message': - this.injectSticker( - payload.data.content.url, - payload.data.content.info, - payload.data.description || payload.data.name); - break; + this.injectSticker( + payload.data.content.url, + payload.data.content.info, + payload.data.description || payload.data.name); + break; case 'picture_snapshot': - ContentMessages.sharedInstance().sendContentListToRoom([payload.file], this.state.room.roomId, this.context); + ContentMessages.sharedInstance().sendContentListToRoom( + [payload.file], this.state.room.roomId, this.context); break; case 'notifier_enabled': case 'upload_started': @@ -645,7 +716,7 @@ export default class RoomView extends React.Component { case 'upload_canceled': this.forceUpdate(); break; - case 'call_state': + case 'call_state': { // don't filter out payloads for room IDs other than props.room because // we may be interested in the conf 1:1 room @@ -653,24 +724,22 @@ export default class RoomView extends React.Component { return; } - var call = this._getCallForRoom(); - var callState; + const call = this.getCallForRoom(); + let callState = "ended"; if (call) { callState = call.call_state; - } else { - callState = "ended"; } // possibly remove the conf call notification if we're now in // the conf - this._updateConfCallNotification(); + this.updateConfCallNotification(); this.setState({ callState: callState, }); - break; + } case 'appsDrawer': this.setState({ showApps: payload.show, @@ -703,14 +772,14 @@ export default class RoomView extends React.Component { matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }, () => { // send another "initial" RVS update to trigger peeking if needed - this._onRoomViewStoreUpdate(true); + this.onRoomViewStoreUpdate(true); }); } break; } }; - onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => { + private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed, data) => { if (this.unmounted) return; // ignore events for other rooms @@ -721,11 +790,11 @@ export default class RoomView extends React.Component { if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return; if (ev.getType() === "org.matrix.room.preview_urls") { - this._updatePreviewUrlVisibility(room); + this.updatePreviewUrlVisibility(room); } if (ev.getType() === "m.room.encryption") { - this._updateE2EStatus(room); + this.updateE2EStatus(room); } // ignore anything but real-time updates at the end of the room: @@ -748,49 +817,49 @@ export default class RoomView extends React.Component { } }; - onRoomName = room => { + private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { this.forceUpdate(); } }; - onRoomRecoveryReminderDontAskAgain = () => { + private onRoomRecoveryReminderDontAskAgain = () => { // Called when the option to not ask again is set: // force an update to hide the recovery reminder this.forceUpdate(); }; - onKeyBackupStatus = () => { + private onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. this.forceUpdate(); }; - canResetTimeline = () => { - if (!this._messagePanel) { + public canResetTimeline = () => { + if (!this.messagePanel) { return true; } - return this._messagePanel.canResetTimeline(); + return this.messagePanel.canResetTimeline(); }; // called when state.room is first initialised (either at initial load, // after a successful peek, or after we join the room). - _onRoomLoaded = room => { - this._calculatePeekRules(room); - this._updatePreviewUrlVisibility(room); - this._loadMembersIfJoined(room); - this._calculateRecommendedVersion(room); - this._updateE2EStatus(room); - this._updatePermissions(room); + private onRoomLoaded = (room: Room) => { + this.calculatePeekRules(room); + this.updatePreviewUrlVisibility(room); + this.loadMembersIfJoined(room); + this.calculateRecommendedVersion(room); + this.updateE2EStatus(room); + this.updatePermissions(room); }; - async _calculateRecommendedVersion(room) { + private async calculateRecommendedVersion(room: Room) { this.setState({ upgradeRecommendation: await room.getRecommendedVersion(), }); } - async _loadMembersIfJoined(room) { + private async loadMembersIfJoined(room: Room) { // lazy load members if enabled if (this.context.hasLazyLoadMembersEnabled()) { if (room && room.getMyMembership() === 'join') { @@ -809,7 +878,7 @@ export default class RoomView extends React.Component { } } - _calculatePeekRules(room) { + private calculatePeekRules(room: Room) { const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", ""); if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") { this.setState({ @@ -825,7 +894,7 @@ export default class RoomView extends React.Component { } } - _updatePreviewUrlVisibility({roomId}) { + private updatePreviewUrlVisibility({roomId}: Room) { // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'; this.setState({ @@ -833,41 +902,41 @@ export default class RoomView extends React.Component { }); } - onRoom = room => { + private onRoom = (room: Room) => { if (!room || room.roomId !== this.state.roomId) { return; } this.setState({ room: room, }, () => { - this._onRoomLoaded(room); + this.onRoomLoaded(room); }); }; - onDeviceVerificationChanged = (userId, device) => { + private onDeviceVerificationChanged = (userId: string, device: object) => { const room = this.state.room; if (!room.currentState.getMember(userId)) { return; } - this._updateE2EStatus(room); + this.updateE2EStatus(room); }; - onUserVerificationChanged = (userId, _trustStatus) => { + private onUserVerificationChanged = (userId: string, trustStatus: object) => { const room = this.state.room; if (!room || !room.currentState.getMember(userId)) { return; } - this._updateE2EStatus(room); + this.updateE2EStatus(room); }; - onCrossSigningKeysChanged = () => { + private onCrossSigningKeysChanged = () => { const room = this.state.room; if (room) { - this._updateE2EStatus(room); + this.updateE2EStatus(room); } }; - async _updateE2EStatus(room) { + private async updateE2EStatus(room: Room) { if (!this.context.isRoomEncrypted(room.roomId)) { return; } @@ -876,7 +945,7 @@ export default class RoomView extends React.Component { // so we don't know what the answer is. Let's error on the safe side and show // a warning for this case. this.setState({ - e2eStatus: "warning", + e2eStatus: E2EStatus.Warning, }); return; } @@ -887,7 +956,7 @@ export default class RoomView extends React.Component { }); } - updateTint() { + private updateTint() { const room = this.state.room; if (!room) return; @@ -896,15 +965,15 @@ export default class RoomView extends React.Component { Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); } - onAccountData = event => { + private onAccountData = (event: MatrixEvent) => { const type = event.getType(); if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) { // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` - this._updatePreviewUrlVisibility(this.state.room); + this.updatePreviewUrlVisibility(this.state.room); } }; - onRoomAccountData = (event, room) => { + private onRoomAccountData = (event: MatrixEvent, room: Room) => { if (room.roomId == this.state.roomId) { const type = event.getType(); if (type === "org.matrix.room.color_scheme") { @@ -914,21 +983,21 @@ export default class RoomView extends React.Component { Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); } else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") { // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` - this._updatePreviewUrlVisibility(room); + this.updatePreviewUrlVisibility(room); } } }; - onRoomStateEvents = (ev, state) => { + private onRoomStateEvents = (ev: MatrixEvent, state) => { // ignore if we don't have a room yet if (!this.state.room || this.state.room.roomId !== state.roomId) { return; } - this._updatePermissions(this.state.room); + this.updatePermissions(this.state.room); }; - onRoomStateMember = (ev, state, member) => { + private onRoomStateMember = (ev: MatrixEvent, state, member) => { // ignore if we don't have a room yet if (!this.state.room) { return; @@ -939,18 +1008,18 @@ export default class RoomView extends React.Component { return; } - this._updateRoomMembers(member); + this.updateRoomMembers(member); }; - onMyMembership = (room, membership, oldMembership) => { + private onMyMembership = (room: Room, membership: string, oldMembership: string) => { if (room.roomId === this.state.roomId) { this.forceUpdate(); - this._loadMembersIfJoined(room); - this._updatePermissions(room); + this.loadMembersIfJoined(room); + this.updatePermissions(room); } }; - _updatePermissions(room) { + private updatePermissions(room: Room) { if (room) { const me = this.context.getUserId(); const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); @@ -960,13 +1029,12 @@ export default class RoomView extends React.Component { } } - // rate limited because a power level change will emit an event for every - // member in the room. - _updateRoomMembers = rate_limited_func((dueToMember) => { + // rate limited because a power level change will emit an event for every member in the room. + private updateRoomMembers = rateLimitedFunc((dueToMember) => { // a member state changed in this room // refresh the conf call notification state - this._updateConfCallNotification(); - this._updateDMState(); + this.updateConfCallNotification(); + this.updateDMState(); let memberCountInfluence = 0; if (dueToMember && dueToMember.membership === "invite" && this.state.room.getInvitedMemberCount() === 0) { @@ -974,15 +1042,15 @@ export default class RoomView extends React.Component { // count by 1 to counteract this. memberCountInfluence = 1; } - this._checkIfAlone(this.state.room, memberCountInfluence); + this.checkIfAlone(this.state.room, memberCountInfluence); - this._updateE2EStatus(this.state.room); + this.updateE2EStatus(this.state.room); }, 500); - _checkIfAlone(room, countInfluence) { + private checkIfAlone(room: Room, countInfluence?: number) { let warnedAboutLonelyRoom = false; if (localStorage) { - warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); + warnedAboutLonelyRoom = Boolean(localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId)); } if (warnedAboutLonelyRoom) { if (this.state.isAlone) this.setState({isAlone: false}); @@ -994,7 +1062,7 @@ export default class RoomView extends React.Component { this.setState({isAlone: joinedOrInvitedMemberCount === 1}); } - _updateConfCallNotification() { + private updateConfCallNotification() { const room = this.state.room; if (!room || !this.props.ConferenceHandler) { return; @@ -1018,7 +1086,7 @@ export default class RoomView extends React.Component { }); } - _updateDMState() { + private updateDMState() { const room = this.state.room; if (room.getMyMembership() != "join") { return; @@ -1029,7 +1097,7 @@ export default class RoomView extends React.Component { } } - onSearchResultsFillRequest = backwards => { + private onSearchResultsFillRequest = (backwards: boolean) => { if (!backwards) { return Promise.resolve(false); } @@ -1037,14 +1105,14 @@ export default class RoomView extends React.Component { if (this.state.searchResults.next_batch) { debuglog("requesting more search results"); const searchPromise = searchPagination(this.state.searchResults); - return this._handleSearchResult(searchPromise); + return this.handleSearchResult(searchPromise); } else { debuglog("no more search results"); return Promise.resolve(false); } }; - onInviteButtonClick = () => { + private onInviteButtonClick = () => { // call AddressPickerDialog dis.dispatch({ action: 'view_invite', @@ -1053,14 +1121,14 @@ export default class RoomView extends React.Component { this.setState({isAlone: false}); // there's a good chance they'll invite someone }; - onStopAloneWarningClick = () => { + private onStopAloneWarningClick = () => { if (localStorage) { - localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true); + localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, String(true)); } this.setState({isAlone: false}); }; - onJoinButtonClicked = ev => { + private onJoinButtonClicked = () => { // If the user is a ROU, allow them to transition to a PWLU if (this.context && this.context.isGuest()) { // Join this room once the user has registered and logged in @@ -1069,7 +1137,7 @@ export default class RoomView extends React.Component { action: 'do_after_sync_prepared', deferred_action: { action: 'view_room', - room_id: this._getRoomId(), + room_id: this.getRoomId(), }, }); @@ -1121,8 +1189,8 @@ export default class RoomView extends React.Component { } }; - onMessageListScroll = ev => { - if (this._messagePanel.isAtEndOfLiveTimeline()) { + private onMessageListScroll = ev => { + if (this.messagePanel.isAtEndOfLiveTimeline()) { this.setState({ numUnreadMessages: 0, atEndOfLiveTimeline: true, @@ -1132,10 +1200,10 @@ export default class RoomView extends React.Component { atEndOfLiveTimeline: false, }); } - this._updateTopUnreadMessagesBar(); + this.updateTopUnreadMessagesBar(); }; - onDragOver = ev => { + private onDragOver = ev => { ev.stopPropagation(); ev.preventDefault(); @@ -1154,7 +1222,7 @@ export default class RoomView extends React.Component { } }; - onDrop = ev => { + private onDrop = ev => { ev.stopPropagation(); ev.preventDefault(); ContentMessages.sharedInstance().sendContentListToRoom( @@ -1164,13 +1232,13 @@ export default class RoomView extends React.Component { dis.fire(Action.FocusComposer); }; - onDragLeaveOrEnd = ev => { + private onDragLeaveOrEnd = ev => { ev.stopPropagation(); ev.preventDefault(); this.setState({ draggingFile: false }); }; - injectSticker(url, info, text) { + private injectSticker(url, info, text) { if (this.context.isGuest()) { dis.dispatch({action: 'require_registration'}); return; @@ -1185,7 +1253,7 @@ export default class RoomView extends React.Component { }); } - onSearch = (term, scope) => { + private onSearch = (term: string, scope) => { this.setState({ searchTerm: term, searchScope: scope, @@ -1195,8 +1263,8 @@ export default class RoomView extends React.Component { // if we already have a search panel, we need to tell it to forget // about its scroll state. - if (this._searchResultsPanel.current) { - this._searchResultsPanel.current.resetScrollState(); + if (this.searchResultsPanel.current) { + this.searchResultsPanel.current.resetScrollState(); } // make sure that we don't end up showing results from @@ -1210,12 +1278,10 @@ export default class RoomView extends React.Component { debuglog("sending search request"); const searchPromise = eventSearch(term, roomId); - this._handleSearchResult(searchPromise); + this.handleSearchResult(searchPromise); }; - _handleSearchResult(searchPromise) { - const self = this; - + private handleSearchResult(searchPromise: Promise) { // keep a record of the current search id, so that if the search terms // change before we get a response, we can ignore the results. const localSearchId = this.searchId; @@ -1224,9 +1290,9 @@ export default class RoomView extends React.Component { searchInProgress: true, }); - return searchPromise.then(function(results) { + return searchPromise.then((results) => { debuglog("search complete"); - if (self.unmounted || !self.state.searching || self.searchId != localSearchId) { + if (this.unmounted || !this.state.searching || this.searchId != localSearchId) { console.error("Discarding stale search results"); return; } @@ -1238,8 +1304,8 @@ export default class RoomView extends React.Component { // whether it was used by the search engine or not. let highlights = results.highlights; - if (highlights.indexOf(self.state.searchTerm) < 0) { - highlights = highlights.concat(self.state.searchTerm); + if (highlights.indexOf(this.state.searchTerm) < 0) { + highlights = highlights.concat(this.state.searchTerm); } // For overlapping highlights, @@ -1248,25 +1314,26 @@ export default class RoomView extends React.Component { return b.length - a.length; }); - self.setState({ + this.setState({ searchHighlights: highlights, searchResults: results, }); - }, function(error) { + }, (error) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Search failed", error); Modal.createTrackedDialog('Search failed', '', ErrorDialog, { title: _t("Search failed"), - description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")), + description: ((error && error.message) ? error.message : + _t("Server may be unavailable, overloaded, or search timed out :(")), }); - }).finally(function() { - self.setState({ + }).finally(() => { + this.setState({ searchInProgress: false, }); }); } - getSearchResultTiles() { + private getSearchResultTiles() { const SearchResultTile = sdk.getComponent('rooms.SearchResultTile'); const Spinner = sdk.getComponent("elements.Spinner"); @@ -1277,20 +1344,20 @@ export default class RoomView extends React.Component { if (this.state.searchInProgress) { ret.push(
  • - -
  • ); + + ); } if (!this.state.searchResults.next_batch) { if (this.state.searchResults.results.length == 0) { ret.push(
  • -

    { _t("No results") }

    -
  • , +

    { _t("No results") }

    + , ); } else { ret.push(
  • -

    { _t("No more results") }

    -
  • , +

    { _t("No more results") }

    + , ); } } @@ -1298,7 +1365,7 @@ export default class RoomView extends React.Component { // once dynamic content in the search results load, make the scrollPanel check // the scroll offsets. const onHeightChanged = () => { - const scrollPanel = this._searchResultsPanel.current; + const scrollPanel = this.searchResultsPanel.current; if (scrollPanel) { scrollPanel.checkScroll(); } @@ -1330,36 +1397,41 @@ export default class RoomView extends React.Component { if (this.state.searchScope === 'All') { if (roomId !== lastRoomId) { ret.push(
  • -

    { _t("Room") }: { room.name }

    -
  • ); +

    { _t("Room") }: { room.name }

    + ); lastRoomId = roomId; } } const resultLink = "#/room/"+roomId+"/"+mxEv.getId(); - ret.push(); + ret.push(); } return ret; } - onPinnedClick = () => { + private onPinnedClick = () => { const nowShowingPinned = !this.state.showingPinned; const roomId = this.state.room.roomId; this.setState({showingPinned: nowShowingPinned, searching: false}); SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); }; - onSettingsClick = () => { - dis.dispatch({ action: 'open_room_settings' }); + private onSettingsClick = () => { + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); }; - onCancelClick = () => { + private onCancelClick = () => { console.log("updateTint from onCancelClick"); this.updateTint(); if (this.state.forwardingEvent) { @@ -1371,31 +1443,30 @@ export default class RoomView extends React.Component { dis.fire(Action.FocusComposer); }; - onLeaveClick = () => { + private onLeaveClick = () => { dis.dispatch({ action: 'leave_room', room_id: this.state.room.roomId, }); }; - onForgetClick = () => { + private onForgetClick = () => { dis.dispatch({ action: 'forget_room', room_id: this.state.room.roomId, }); }; - onRejectButtonClicked = ev => { - const self = this; + private onRejectButtonClicked = ev => { this.setState({ rejecting: true, }); - this.context.leave(this.state.roomId).then(function() { + this.context.leave(this.state.roomId).then(() => { dis.dispatch({ action: 'view_next_room' }); - self.setState({ + this.setState({ rejecting: false, }); - }, function(error) { + }, (error) => { console.error("Failed to reject invite: %s", error); const msg = error.message ? error.message : JSON.stringify(error); @@ -1405,14 +1476,14 @@ export default class RoomView extends React.Component { description: msg, }); - self.setState({ + this.setState({ rejecting: false, rejectError: error, }); }); }; - onRejectAndIgnoreClick = async () => { + private onRejectAndIgnoreClick = async () => { this.setState({ rejecting: true, }); @@ -1439,14 +1510,14 @@ export default class RoomView extends React.Component { description: msg, }); - self.setState({ + this.setState({ rejecting: false, rejectError: error, }); } }; - onRejectThreepidInviteButtonClicked = ev => { + private onRejectThreepidInviteButtonClicked = ev => { // We can reject 3pid invites in the same way that we accept them, // using /leave rather than /join. In the short term though, we // just ignore them. @@ -1454,14 +1525,14 @@ export default class RoomView extends React.Component { dis.fire(Action.ViewRoomDirectory); }; - onSearchClick = () => { + private onSearchClick = () => { this.setState({ searching: !this.state.searching, showingPinned: false, }); }; - onCancelSearchClick = () => { + private onCancelSearchClick = () => { this.setState({ searching: false, searchResults: null, @@ -1469,29 +1540,29 @@ export default class RoomView extends React.Component { }; // jump down to the bottom of this room, where new events are arriving - jumpToLiveTimeline = () => { - this._messagePanel.jumpToLiveTimeline(); + private jumpToLiveTimeline = () => { + this.messagePanel.jumpToLiveTimeline(); dis.fire(Action.FocusComposer); }; // jump up to wherever our read marker is - jumpToReadMarker = () => { - this._messagePanel.jumpToReadMarker(); + private jumpToReadMarker = () => { + this.messagePanel.jumpToReadMarker(); }; // update the read marker to match the read-receipt - forgetReadMarker = ev => { + private forgetReadMarker = ev => { ev.stopPropagation(); - this._messagePanel.forgetReadMarker(); + this.messagePanel.forgetReadMarker(); }; // decide whether or not the top 'unread messages' bar should be shown - _updateTopUnreadMessagesBar = () => { - if (!this._messagePanel) { + private updateTopUnreadMessagesBar = () => { + if (!this.messagePanel) { return; } - const showBar = this._messagePanel.canJumpToReadMarker(); + const showBar = this.messagePanel.canJumpToReadMarker(); if (this.state.showTopUnreadMessagesBar != showBar) { this.setState({showTopUnreadMessagesBar: showBar}); } @@ -1500,8 +1571,8 @@ export default class RoomView extends React.Component { // get the current scroll position of the room, so that it can be // restored when we switch back to it. // - _getScrollState() { - const messagePanel = this._messagePanel; + private getScrollState() { + const messagePanel = this.messagePanel; if (!messagePanel) return null; // if we're following the live timeline, we want to return null; that @@ -1537,7 +1608,7 @@ export default class RoomView extends React.Component { }; } - onResize = () => { + private onResize = () => { // It seems flexbox doesn't give us a way to constrain the auxPanel height to have // a minimum of the height of the video element, whilst also capping it from pushing out the page // so we have to do it via JS instead. In this implementation we cap the height by putting @@ -1545,9 +1616,9 @@ export default class RoomView extends React.Component { // header + footer + status + give us at least 120px of scrollback at all times. let auxPanelMaxHeight = window.innerHeight - - (83 + // height of RoomHeader + (54 + // height of RoomHeader 36 + // height of the status area - 72 + // minimum height of the message compmoser + 51 + // minimum height of the message compmoser 120); // amount of desired scrollback // XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway @@ -1557,15 +1628,15 @@ export default class RoomView extends React.Component { this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); }; - onFullscreenClick = () => { + private onFullscreenClick = () => { dis.dispatch({ action: 'video_fullscreen', fullscreen: true, }, true); }; - onMuteAudioClick = () => { - const call = this._getCallForRoom(); + private onMuteAudioClick = () => { + const call = this.getCallForRoom(); if (!call) { return; } @@ -1574,8 +1645,8 @@ export default class RoomView extends React.Component { this.forceUpdate(); // TODO: just update the voip buttons }; - onMuteVideoClick = () => { - const call = this._getCallForRoom(); + private onMuteVideoClick = () => { + const call = this.getCallForRoom(); if (!call) { return; } @@ -1584,14 +1655,14 @@ export default class RoomView extends React.Component { this.forceUpdate(); // TODO: just update the voip buttons }; - onStatusBarVisible = () => { + private onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ statusBarVisible: true, }); }; - onStatusBarHidden = () => { + private onStatusBarHidden = () => { // This is currently not desired as it is annoying if it keeps expanding and collapsing if (this.unmounted) return; this.setState({ @@ -1604,12 +1675,12 @@ export default class RoomView extends React.Component { * * We pass it down to the scroll panel. */ - handleScrollKey = ev => { + private handleScrollKey = ev => { let panel; - if (this._searchResultsPanel.current) { - panel = this._searchResultsPanel.current; - } else if (this._messagePanel) { - panel = this._messagePanel; + if (this.searchResultsPanel.current) { + panel = this.searchResultsPanel.current; + } else if (this.messagePanel) { + panel = this.messagePanel; } if (panel) { @@ -1620,7 +1691,7 @@ export default class RoomView extends React.Component { /** * get any current call for this room */ - _getCallForRoom() { + private getCallForRoom() { if (!this.state.room) { return null; } @@ -1629,47 +1700,34 @@ export default class RoomView extends React.Component { // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. - _gatherTimelinePanelRef = r => { - this._messagePanel = r; + private gatherTimelinePanelRef = r => { + this.messagePanel = r; if (r) { - console.log("updateTint from RoomView._gatherTimelinePanelRef"); + console.log("updateTint from RoomView.gatherTimelinePanelRef"); this.updateTint(); } }; - _getOldRoom() { + private getOldRoom() { const createEvent = this.state.room.currentState.getStateEvents("m.room.create", ""); if (!createEvent || !createEvent.getContent()['predecessor']) return null; return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']); } - _getHiddenHighlightCount() { - const oldRoom = this._getOldRoom(); + getHiddenHighlightCount() { + const oldRoom = this.getOldRoom(); if (!oldRoom) return 0; return oldRoom.getUnreadNotificationCount('highlight'); } - _onHiddenHighlightsClick = () => { - const oldRoom = this._getOldRoom(); + onHiddenHighlightsClick = () => { + const oldRoom = this.getOldRoom(); if (!oldRoom) return; dis.dispatch({action: "view_room", room_id: oldRoom.roomId}); }; render() { - const RoomHeader = sdk.getComponent('rooms.RoomHeader'); - const ForwardMessage = sdk.getComponent("rooms.ForwardMessage"); - const AuxPanel = sdk.getComponent("rooms.AuxPanel"); - const SearchBar = sdk.getComponent("rooms.SearchBar"); - const PinnedEventsPanel = sdk.getComponent("rooms.PinnedEventsPanel"); - const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); - const TintableSvg = sdk.getComponent("elements.TintableSvg"); - const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar"); - const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); - const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar"); - const RoomRecoveryReminder = sdk.getComponent("rooms.RoomRecoveryReminder"); - const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary"); - if (!this.state.room) { const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading; if (loading) { @@ -1690,11 +1748,11 @@ export default class RoomView extends React.Component {
    ); } else { - var inviterName = undefined; + let inviterName = undefined; if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - var invitedEmail = undefined; + let invitedEmail = undefined; if (this.props.thirdPartyInvite) { invitedEmail = this.props.thirdPartyInvite.invitedEmail; } @@ -1773,13 +1831,13 @@ export default class RoomView extends React.Component { // We have successfully loaded this room, and are not previewing. // Display the "normal" room view. - const call = this._getCallForRoom(); + const call = this.getCallForRoom(); let inCall = false; if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { inCall = true; } - const scrollheader_classes = classNames({ + const scrollheaderClasses = classNames({ mx_RoomView_scrollheader: true, }); @@ -1818,17 +1876,21 @@ export default class RoomView extends React.Component { this.context.getKeyBackupEnabled() === false ); - const hiddenHighlightCount = this._getHiddenHighlightCount(); + const hiddenHighlightCount = this.getHiddenHighlightCount(); let aux = null; let previewBar; let hideCancel = false; let forceHideRightPanel = false; - if (this.state.forwardingEvent !== null) { + if (this.state.forwardingEvent) { aux = ; } else if (this.state.searching) { hideCancel = true; // has own cancel - aux = ; + aux = ; } else if (showRoomUpgradeBar) { aux = ; hideCancel = true; @@ -1841,25 +1903,26 @@ export default class RoomView extends React.Component { } else if (myMembership !== "join") { // We do have a room object for this room, but we're not currently in it. // We may have a 3rd party invite to it. - var inviterName = undefined; + let inviterName = undefined; if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - var invitedEmail = undefined; + let invitedEmail = undefined; if (this.props.thirdPartyInvite) { invitedEmail = this.props.thirdPartyInvite.invitedEmail; } hideCancel = true; previewBar = ( - ); if (!this.state.canPeek) { @@ -1873,8 +1936,11 @@ export default class RoomView extends React.Component { } } else if (hiddenHighlightCount > 0) { aux = ( - + {_t( "You have %(count)s unread notifications in a prior version of this room.", {count: hiddenHighlightCount}, @@ -1884,15 +1950,19 @@ export default class RoomView extends React.Component { } const auxPanel = ( - + { aux } ); @@ -1912,7 +1982,7 @@ export default class RoomView extends React.Component { showApps={this.state.showApps} e2eStatus={this.state.e2eStatus} resizeNotifier={this.props.resizeNotifier} - permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)} + permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)} />; } @@ -1932,26 +2002,37 @@ export default class RoomView extends React.Component { if (call.type === "video") { zoomButton = (
    - +
    ); videoMuteButton =
    - +
    ; } const voiceMuteButton =
    - +
    ; // wrap the existing status bar into a 'callStatusBar' which adds more knobs. @@ -1972,16 +2053,18 @@ export default class RoomView extends React.Component { if (this.state.searchResults) { // show searching spinner if (this.state.searchResults.results === undefined) { - searchResultsPanel = (
    ); + searchResultsPanel = ( +
    + ); } else { searchResultsPanel = ( -
  • +
  • { this.getSearchResultTiles() } ); @@ -2007,7 +2090,7 @@ export default class RoomView extends React.Component { // console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview); const messagePanel = ( ); + topUnreadMessagesBar = ( + + ); } let jumpToBottom; // Do not show JumpToBottomButton if we have search results showing, it makes no sense @@ -2046,19 +2128,14 @@ export default class RoomView extends React.Component { onScrollToBottomClick={this.jumpToLiveTimeline} />); } - const statusBarAreaClass = classNames( - "mx_RoomView_statusArea", - { - "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, - }, - ); - const fadableSectionClasses = classNames( - "mx_RoomView_body", "mx_fadable", - { - "mx_fadable_faded": this.props.disabled, - }, - ); + const statusBarAreaClass = classNames("mx_RoomView_statusArea", { + "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, + }); + + const fadableSectionClasses = classNames("mx_RoomView_body", "mx_fadable", { + "mx_fadable_faded": this.props.disabled, + }); const showRightPanel = !forceHideRightPanel && this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel @@ -2075,7 +2152,7 @@ export default class RoomView extends React.Component { return ( -
    +
    - +
    {auxPanel}
    diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 4e3e00f221..b4f5195803 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -163,7 +163,7 @@ export default class ScrollPanel extends React.Component { this._pendingFillRequests = {b: null, f: null}; if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResized", this.onResize); + this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); } this.resetScrollState(); @@ -193,11 +193,12 @@ export default class ScrollPanel extends React.Component { this.unmounted = true; if (this.props.resizeNotifier) { - this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); + this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize); } } onScroll = ev => { + if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing debuglog("onScroll", this._getScrollNode().scrollTop); this._scrollTimeout.restart(); this._saveScrollState(); @@ -207,6 +208,7 @@ export default class ScrollPanel extends React.Component { }; onResize = () => { + debuglog("onResize"); this.checkScroll(); // update preventShrinkingState if present if (this.preventShrinkingState) { @@ -236,7 +238,6 @@ export default class ScrollPanel extends React.Component { // when scrolled all the way down. E.g. Chrome 72 on debian. // so check difference <= 1; return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1; - }; // returns the vertical height in the given direction that can be removed from diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index daa18bb290..97f9ba48ed 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -104,8 +104,8 @@ class TimelinePanel extends React.Component { // shape property to be passed to EventTiles tileShape: PropTypes.string, - // placeholder text to use if the timeline is empty - empty: PropTypes.string, + // placeholder to use if the timeline is empty + empty: PropTypes.node, // whether to show reactions for an event showReactions: PropTypes.bool, diff --git a/src/components/structures/UserView.js b/src/components/structures/UserView.js index 694592af88..8e21771bb9 100644 --- a/src/components/structures/UserView.js +++ b/src/components/structures/UserView.js @@ -80,7 +80,9 @@ export default class UserView extends React.Component { const RightPanel = sdk.getComponent('structures.RightPanel'); const MainSplit = sdk.getComponent('structures.MainSplit'); const panel = ; - return (); + return ( + + ); } else { return (
    ); } diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 53769fb5a6..a20bf0dd0a 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -124,7 +124,11 @@ export default class LoginComponent extends React.Component { 'm.login.cas': () => this._renderSsoStep("cas"), 'm.login.sso': () => this._renderSsoStep("sso"), }; + } + // TODO: [REACT-WARNING] Replace with appropriate lifecycle event + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount() { this._initLoginLogic(); } diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index b3ca9fde6f..a3fb00a9f4 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -37,7 +37,7 @@ interface IOptionListProps { } interface IOptionProps extends React.ComponentProps { - iconClassName: string; + iconClassName?: string; } interface ICheckboxProps extends React.ComponentProps { @@ -92,7 +92,7 @@ export const IconizedContextMenuCheckbox: React.FC = ({ export const IconizedContextMenuOption: React.FC = ({label, iconClassName, ...props}) => { return - + { iconClassName && } {label} ; }; diff --git a/src/components/views/context_menus/WidgetContextMenu.js b/src/components/views/context_menus/WidgetContextMenu.js index 1ec74b2e6c..9182b92c8c 100644 --- a/src/components/views/context_menus/WidgetContextMenu.js +++ b/src/components/views/context_menus/WidgetContextMenu.js @@ -26,6 +26,9 @@ export default class WidgetContextMenu extends React.Component { // Callback for when the revoke button is clicked. Required. onRevokeClicked: PropTypes.func.isRequired, + // Callback for when the unpin button is clicked. Required. + onUnpinClicked: PropTypes.func.isRequired, + // Callback for when the snapshot button is clicked. Button not shown // without a callback. onSnapshotClicked: PropTypes.func, @@ -70,6 +73,8 @@ export default class WidgetContextMenu extends React.Component { this.proxyClick(this.props.onRevokeClicked); }; + onUnpinClicked = () => this.proxyClick(this.props.onUnpinClicked); + render() { const options = []; @@ -81,6 +86,12 @@ export default class WidgetContextMenu extends React.Component { ); } + options.push( + + {_t("Unpin")} + , + ); + if (this.props.onReloadClicked) { options.push( diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 299025f949..faca743bfd 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -42,6 +42,8 @@ import {WidgetType} from "../../../widgets/WidgetType"; import {Capability} from "../../../widgets/WidgetApi"; import {sleep} from "../../../utils/promise"; import {SettingLevel} from "../../../settings/SettingLevel"; +import WidgetStore from "../../../stores/WidgetStore"; +import {Action} from "../../../dispatcher/actions"; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -100,6 +102,8 @@ export default class AppTile extends React.Component { _getNewState(newProps) { // This is a function to make the impact of calling SettingsStore slightly less const hasPermissionToLoad = () => { + if (this._usingLocalWidget()) return true; + const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", newProps.room.roomId); return !!currentlyAllowedWidgets[newProps.app.eventId]; }; @@ -310,35 +314,12 @@ export default class AppTile extends React.Component { if (this.props.onEditClick) { this.props.onEditClick(); } else { - // TODO: Open the right manager for the widget - if (SettingsStore.getValue("feature_many_integration_managers")) { - IntegrationManagers.sharedInstance().openAll( - this.props.room, - 'type_' + this.props.app.type, - this.props.app.id, - ); - } else { - IntegrationManagers.sharedInstance().getPrimaryManager().open( - this.props.room, - 'type_' + this.props.app.type, - this.props.app.id, - ); - } + WidgetUtils.editWidget(this.props.room, this.props.app); } } _onSnapshotClick() { - console.log("Requesting widget snapshot"); - ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot() - .catch((err) => { - console.error("Failed to get screenshot", err); - }) - .then((screenshot) => { - dis.dispatch({ - action: 'picture_snapshot', - file: screenshot, - }, true); - }); + WidgetUtils.snapshotWidget(this.props.app); } /** @@ -419,6 +400,10 @@ export default class AppTile extends React.Component { } } + _onUnpinClicked = () => { + WidgetStore.instance.unpinWidget(this.props.app.id); + } + _onRevokeClicked() { console.info("Revoke widget permissions - %s", this.props.app.id); this._revokeWidgetPermission(); @@ -490,12 +475,20 @@ export default class AppTile extends React.Component { if (payload.widgetId === this.props.app.id) { switch (payload.action) { case 'm.sticker': - if (this._hasCapability('m.sticker')) { - dis.dispatch({action: 'post_sticker_message', data: payload.data}); - } else { - console.warn('Ignoring sticker message. Invalid capability'); - } - break; + if (this._hasCapability('m.sticker')) { + dis.dispatch({action: 'post_sticker_message', data: payload.data}); + } else { + console.warn('Ignoring sticker message. Invalid capability'); + } + break; + + case Action.AppTileDelete: + this._onDeleteClick(); + break; + + case Action.AppTileRevoke: + this._onRevokeClicked(); + break; } } } @@ -613,6 +606,15 @@ export default class AppTile extends React.Component { return uriFromTemplate(u, vars); } + /** + * Whether we're using a local version of the widget rather than loading the + * actual widget URL + * @returns {bool} true If using a local version of the widget + */ + _usingLocalWidget() { + return WidgetType.JITSI.matches(this.props.app.type); + } + /** * Get the URL used in the iframe * In cases where we supply our own UI for a widget, this is an internal @@ -626,7 +628,10 @@ export default class AppTile extends React.Component { if (WidgetType.JITSI.matches(this.props.app.type)) { console.log("Replacing Jitsi widget URL with local wrapper"); - url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true}); + url = WidgetUtils.getLocalJitsiWrapperUrl({ + forLocalRender: true, + auth: this.props.app.data ? this.props.app.data.auth : null, + }); url = this._addWurlParams(url); } else { url = this._getSafeUrl(this.state.widgetUrl); @@ -637,7 +642,10 @@ export default class AppTile extends React.Component { _getPopoutUrl() { if (WidgetType.JITSI.matches(this.props.app.type)) { return this._templatedUrl( - WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}), + WidgetUtils.getLocalJitsiWrapperUrl({ + forLocalRender: false, + auth: this.props.app.data ? this.props.app.data.auth : null, + }), this.props.app.type, ); } else { @@ -804,14 +812,16 @@ export default class AppTile extends React.Component { const showMinimiseButton = this.props.showMinimise && this.props.show; const showMaximiseButton = this.props.showMinimise && !this.props.show; - let appTileClass; + let appTileClasses; if (this.props.miniMode) { - appTileClass = 'mx_AppTile_mini'; + appTileClasses = {mx_AppTile_mini: true}; } else if (this.props.fullWidth) { - appTileClass = 'mx_AppTileFullWidth'; + appTileClasses = {mx_AppTileFullWidth: true}; } else { - appTileClass = 'mx_AppTile'; + appTileClasses = {mx_AppTile: true}; } + appTileClasses.mx_AppTile_minimised = !this.props.show; + appTileClasses = classNames(appTileClasses); const menuBarClasses = classNames({ mx_AppTileMenuBar: true, @@ -831,6 +841,7 @@ export default class AppTile extends React.Component { contextMenu = ( -
    +
    { this.props.showMenubar &&
    diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js deleted file mode 100644 index 0990218c65..0000000000 --- a/src/components/views/elements/ManageIntegsButton.js +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright 2017 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -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 { _t } from '../../../languageHandler'; -import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; -import SettingsStore from "../../../settings/SettingsStore"; -import AccessibleTooltipButton from "./AccessibleTooltipButton"; - -export default class ManageIntegsButton extends React.Component { - constructor(props) { - super(props); - } - - onManageIntegrations = (ev) => { - ev.preventDefault(); - - const managers = IntegrationManagers.sharedInstance(); - if (!managers.hasManager()) { - managers.openNoManagerDialog(); - } else { - if (SettingsStore.getValue("feature_many_integration_managers")) { - managers.openAll(this.props.room); - } else { - managers.getPrimaryManager().open(this.props.room); - } - } - }; - - render() { - let integrationsButton =
    ; - if (IntegrationManagers.sharedInstance().hasManager()) { - integrationsButton = ( - - ); - } - - return integrationsButton; - } -} - -ManageIntegsButton.propTypes = { - room: PropTypes.object.isRequired, -}; diff --git a/src/components/views/elements/PersistedElement.js b/src/components/views/elements/PersistedElement.js index 7f9bfdebf4..9a64b7c7c4 100644 --- a/src/components/views/elements/PersistedElement.js +++ b/src/components/views/elements/PersistedElement.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; - +import {throttle} from "lodash"; import ResizeObserver from 'resize-observer-polyfill'; import dis from '../../../dispatcher/dispatcher'; @@ -156,7 +156,7 @@ export default class PersistedElement extends React.Component { child.style.display = visible ? 'block' : 'none'; } - updateChildPosition(child, parent) { + updateChildPosition = throttle((child, parent) => { if (!child || !parent) return; const parentRect = parent.getBoundingClientRect(); @@ -167,9 +167,9 @@ export default class PersistedElement extends React.Component { width: parentRect.width + 'px', height: parentRect.height + 'px', }); - } + }, 100, {trailing: true, leading: true}); render() { - return
    ; + return
    ; } } diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index bdf5f60234..686739a9f7 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -76,7 +76,7 @@ export default class PersistentApp extends React.Component { userId={MatrixClientPeg.get().credentials.userId} show={true} creatorUserId={app.creatorUserId} - widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''} + widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={capWhitelist} showDelete={false} diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js index e5f217dd90..66922df0f8 100644 --- a/src/components/views/elements/PowerSelector.js +++ b/src/components/views/elements/PowerSelector.js @@ -57,11 +57,14 @@ export default class PowerSelector extends React.Component { customValue: this.props.value, selectValue: 0, }; - - this._initStateFromProps(this.props); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount() { + this._initStateFromProps(this.props); + } + // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(newProps) { this._initStateFromProps(newProps); diff --git a/src/components/views/right_panel/BaseCard.tsx b/src/components/views/right_panel/BaseCard.tsx new file mode 100644 index 0000000000..3e95da1bc1 --- /dev/null +++ b/src/components/views/right_panel/BaseCard.tsx @@ -0,0 +1,93 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {ReactNode} from 'react'; +import classNames from 'classnames'; + +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {_t} from "../../../languageHandler"; +import AccessibleButton from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; + +interface IProps { + header?: ReactNode; + footer?: ReactNode; + className?: string; + withoutScrollContainer?: boolean; + previousPhase?: RightPanelPhases; + onClose?(): void; +} + +interface IGroupProps { + className?: string; + title: string; +} + +export const Group: React.FC = ({ className, title, children }) => { + return
    +

    {title}

    + {children} +
    ; +}; + +const BaseCard: React.FC = ({ + onClose, + className, + header, + footer, + withoutScrollContainer, + previousPhase, + children, +}) => { + let backButton; + if (previousPhase) { + const onBackClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: previousPhase, + }); + }; + backButton = ; + } + + let closeButton; + if (onClose) { + closeButton = ; + } + + if (!withoutScrollContainer) { + children = + { children } + ; + } + + return ( +
    +
    + { backButton } + { closeButton } + { header } +
    + { children } + { footer &&
    { footer }
    } +
    + ); +}; + +export default BaseCard; diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index e922959bbb..543c7c067f 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -96,8 +96,7 @@ export default abstract class HeaderButtons extends React.Component + return
    {this.renderButtons()}
    ; } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 7d732b8ae3..c2364546fd 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -19,14 +19,18 @@ limitations under the License. */ import React from 'react'; -import { _t } from '../../../languageHandler'; +import {_t} from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HeaderKind} from './HeaderButtons'; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; +import RightPanelStore from "../../../stores/RightPanelStore"; -const MEMBER_PHASES = [ +const ROOM_INFO_PHASES = [ + RightPanelPhases.RoomSummary, + RightPanelPhases.Widget, + RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, RightPanelPhases.RoomMemberInfo, RightPanelPhases.EncryptionPanel, @@ -54,22 +58,21 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - private onMembersClicked = () => { - if (this.state.phase === RightPanelPhases.RoomMemberInfo) { - // send the active phase to trigger a toggle - // XXX: we should pass refireParams here but then it won't collapse as we desire it to - this.setPhase(RightPanelPhases.RoomMemberInfo); + private onRoomSummaryClicked = () => { + // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close + const lastPhase = RightPanelStore.getSharedInstance().roomPanelPhase; + if (ROOM_INFO_PHASES.includes(lastPhase)) { + if (this.state.phase === lastPhase) { + this.setPhase(lastPhase); + } else { + this.setPhase(lastPhase, RightPanelStore.getSharedInstance().roomPanelPhaseParams); + } } else { // This toggles for us, if needed - this.setPhase(RightPanelPhases.RoomMemberList); + this.setPhase(RightPanelPhases.RoomSummary); } }; - private onFilesClicked = () => { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.FilePanel); - }; - private onNotificationsClicked = () => { // This toggles for us, if needed this.setPhase(RightPanelPhases.NotificationPanel); @@ -77,24 +80,22 @@ export default class RoomHeaderButtons extends HeaderButtons { public renderButtons() { return [ - , - , - , + , ]; } } diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx new file mode 100644 index 0000000000..9f803d1185 --- /dev/null +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -0,0 +1,243 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {useCallback, useState, useEffect, useContext} from "react"; +import classNames from "classnames"; +import {Room} from "matrix-js-sdk/src/models/room"; +import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; + +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { useIsEncrypted } from '../../../hooks/useIsEncrypted'; +import BaseCard, { Group } from "./BaseCard"; +import { _t } from '../../../languageHandler'; +import RoomAvatar from "../avatars/RoomAvatar"; +import AccessibleButton from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {Action} from "../../../dispatcher/actions"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import Modal from "../../../Modal"; +import ShareDialog from '../dialogs/ShareDialog'; +import {useEventEmitter} from "../../../hooks/useEventEmitter"; +import WidgetEchoStore from "../../../stores/WidgetEchoStore"; +import WidgetUtils from "../../../utils/WidgetUtils"; +import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; +import SettingsStore from "../../../settings/SettingsStore"; +import TextWithTooltip from "../elements/TextWithTooltip"; +import BaseAvatar from "../avatars/BaseAvatar"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import WidgetStore, {IApp} from "../../../stores/WidgetStore"; + +interface IProps { + room: Room; + onClose(): void; +} + +interface IAppsSectionProps { + room: Room; +} + +interface IButtonProps { + className: string; + onClick(): void; +} + +const Button: React.FC = ({ children, className, onClick }) => { + return + { children } + ; +}; + +export const useWidgets = (room: Room) => { + const [apps, setApps] = useState(WidgetStore.instance.getApps(room)); + + const updateApps = useCallback(() => { + // Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings + setApps([...WidgetStore.instance.getApps(room)]); + }, [room]); + + useEffect(updateApps, [room]); + useEventEmitter(WidgetEchoStore, "update", updateApps); + useEventEmitter(WidgetStore.instance, room.roomId, updateApps); + + return apps; +}; + +const AppsSection: React.FC = ({ room }) => { + const cli = useContext(MatrixClientContext); + const apps = useWidgets(room); + + const onManageIntegrations = () => { + const managers = IntegrationManagers.sharedInstance(); + if (!managers.hasManager()) { + managers.openNoManagerDialog(); + } else { + if (SettingsStore.getValue("feature_many_integration_managers")) { + managers.openAll(room); + } else { + managers.getPrimaryManager().open(room); + } + } + }; + + return + { apps.map(app => { + const name = WidgetUtils.getWidgetName(app); + const dataTitle = WidgetUtils.getWidgetDataTitle(app); + const subtitle = dataTitle && " - " + dataTitle; + + let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg")]; + // heuristics for some better icons until Widgets support their own icons + if (app.type.includes("meeting") || app.type.includes("calendar")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_cal.svg")]; + } else if (app.type.includes("pad") || app.type.includes("doc") || app.type.includes("calc")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_doc.svg")]; + } else if (app.type.includes("clock")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg")]; + } + + if (app.avatar_url) { // MSC2765 + iconUrls.unshift(getHttpUriForMxc(cli.getHomeserverUrl(), app.avatar_url, 20, 20, "crop")); + } + + const isPinned = WidgetStore.instance.isPinned(app.id); + const classes = classNames("mx_RoomSummaryCard_icon_app", { + mx_RoomSummaryCard_icon_app_pinned: isPinned, + }); + + if (isPinned) { + const onClick = () => { + WidgetStore.instance.unpinWidget(app.id); + }; + + return + + {name} + { subtitle } + + } + + const onOpenWidgetClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.Widget, + refireParams: { + widgetId: app.id, + }, + }); + }; + + return ( + + ); + }) } + + + { apps.length > 0 ? _t("Edit apps, bridges & bots") : _t("Add apps, bridges & bots") } + + ; +}; + +const onRoomMembersClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomMemberList, + }); +}; + +const onRoomFilesClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.FilePanel, + }); +}; + +const onRoomSettingsClick = () => { + defaultDispatcher.dispatch({ action: "open_room_settings" }); +}; + +const useMemberCount = (room: Room) => { + const [count, setCount] = useState(room.getJoinedMembers().length); + useEventEmitter(room.currentState, "RoomState.members", () => { + setCount(room.getJoinedMembers().length); + }); + return count; +}; + +const RoomSummaryCard: React.FC = ({ room, onClose }) => { + const cli = useContext(MatrixClientContext); + + const onShareRoomClick = () => { + Modal.createTrackedDialog('share room dialog', '', ShareDialog, { + target: room, + }); + }; + + const isRoomEncrypted = useIsEncrypted(cli, room); + + const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; + const header = +
    + + +
    + +

    { room.name }

    +
    + { alias } +
    +
    ; + + const memberCount = useMemberCount(room); + + return + + + + + + + + + ; +}; + +export default RoomSummaryCard; diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 518bb133ce..fc73c8b542 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -20,7 +20,7 @@ limitations under the License. import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import {Group, RoomMember, User} from 'matrix-js-sdk'; +import {Group, RoomMember, User, Room} from 'matrix-js-sdk'; import dis from '../../../dispatcher/dispatcher'; import Modal from '../../../Modal'; import * as sdk from '../../../index'; @@ -31,7 +31,6 @@ import AccessibleButton from '../elements/AccessibleButton'; import SdkConfig from '../../../SdkConfig'; import SettingsStore from "../../../settings/SettingsStore"; import {EventTimeline} from "matrix-js-sdk"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import RoomViewStore from "../../../stores/RoomViewStore"; import MultiInviter from "../../../utils/MultiInviter"; import GroupStore from "../../../stores/GroupStore"; @@ -46,6 +45,7 @@ import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification'; import {Action} from "../../../dispatcher/actions"; import {useIsEncrypted} from "../../../hooks/useIsEncrypted"; +import BaseCard from "./BaseCard"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -451,7 +451,7 @@ const _isMuted = (member, powerLevelContent) => { return member.powerLevel < levelToSend; }; -const useRoomPowerLevels = (cli, room) => { +export const useRoomPowerLevels = (cli, room) => { const [powerLevels, setPowerLevels] = useState({}); const update = useCallback(() => { @@ -1364,16 +1364,9 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { ; }; -const UserInfoHeader = ({onClose, member, e2eStatus}) => { +const UserInfoHeader = ({member, e2eStatus}) => { const cli = useContext(MatrixClientContext); - let closeButton; - if (onClose) { - closeButton = -
    - ; - } - const onMemberAvatarClick = useCallback(() => { const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl; if (!avatarUrl) return; @@ -1448,7 +1441,6 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { const displayName = member.name || member.displayname; return - { closeButton } { avatarElement }
    @@ -1471,11 +1463,9 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { ; }; -const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => { +const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => { const cli = useContext(MatrixClientContext); - // Load room if we are given a room id and memoize it - this can be undefined for User Info/Group Member Info - const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]); // fetch latest room member if we have a room, so we don't show historical information, falling back to user const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]); @@ -1510,15 +1500,16 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMe break; } - return ( -
    - - + let previousPhase: RightPanelPhases; + // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time + if (room) { + previousPhase = RightPanelPhases.RoomMemberList; + } - { content } - -
    - ); + const header = ; + return + { content } + ; }; UserInfo.propTypes = { @@ -1529,7 +1520,7 @@ UserInfo.propTypes = { ]).isRequired, group: PropTypes.instanceOf(Group), groupId: PropTypes.string, - roomId: PropTypes.string, + room: PropTypes.instanceOf(Room), onClose: PropTypes.func, }; diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx new file mode 100644 index 0000000000..dec30a57f2 --- /dev/null +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -0,0 +1,205 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {useContext, useEffect} from "react"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import BaseCard from "./BaseCard"; +import WidgetUtils from "../../../utils/WidgetUtils"; +import AccessibleButton from "../elements/AccessibleButton"; +import AppTile from "../elements/AppTile"; +import {_t} from "../../../languageHandler"; +import {useWidgets} from "./RoomSummaryCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import WidgetStore from "../../../stores/WidgetStore"; +import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; +import {ChevronFace, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; +import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload"; +import {Capability} from "../../../widgets/WidgetApi"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import classNames from "classnames"; + +interface IProps { + room: Room; + widgetId: string; + onClose(): void; +} + +const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { + const cli = useContext(MatrixClientContext); + + const apps = useWidgets(room); + const app = apps.find(a => a.id === widgetId); + const isPinned = app && WidgetStore.instance.isPinned(app.id); + + const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); + + useEffect(() => { + if (!app || isPinned) { + // stop showing this card + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); + } + }, [app, isPinned]); + + // Don't render anything as we are about to transition + if (!app || isPinned) return null; + + const header = +

    { WidgetUtils.getWidgetName(app) }

    +
    ; + + const canModify = WidgetUtils.canUserModifyWidgets(room.roomId); + + let contextMenu; + if (menuDisplayed) { + let snapshotButton; + if (ActiveWidgetStore.widgetHasCapability(app.id, Capability.Screenshot)) { + const onSnapshotClick = () => { + WidgetUtils.snapshotWidget(app); + closeMenu(); + }; + + snapshotButton = ; + } + + let deleteButton; + if (canModify) { + const onDeleteClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileDelete, + widgetId: app.id, + }); + closeMenu(); + }; + + deleteButton = ; + } + + const onRevokeClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileRevoke, + widgetId: app.id, + }); + closeMenu(); + }; + + const rect = handle.current.getBoundingClientRect(); + contextMenu = ( + + + { snapshotButton } + { deleteButton } + + + + ); + } + + const onPinClick = () => { + WidgetStore.instance.pinWidget(app.id); + }; + + const onEditClick = () => { + WidgetUtils.editWidget(room, app); + }; + + let editButton; + if (canModify) { + editButton = + { _t("Edit") } + ; + } + + const pinButtonClasses = canModify ? "" : "mx_WidgetCard_widePinButton"; + + let pinButton; + if (WidgetStore.instance.canPin(app.id)) { + pinButton = + { _t("Pin to room") } + ; + } else { + pinButton = + { _t("Pin to room") } + ; + } + + const footer = + { editButton } + { pinButton } + + + { contextMenu } + ; + + return + + ; +}; + +export default WidgetCard; diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 50f53d20c2..a67338b9d5 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -15,11 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {useState} from 'react'; import PropTypes from 'prop-types'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import classNames from 'classnames'; +import {Resizable} from "re-resizable"; + import AppTile from '../elements/AppTile'; -import Modal from '../../../Modal'; import dis from '../../../dispatcher/dispatcher'; import * as sdk from '../../../index'; import * as ScalarMessaging from '../../../ScalarMessaging'; @@ -29,14 +30,15 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore"; import AccessibleButton from '../elements/AccessibleButton'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; - -// The maximum number of widgets that can be added in a room -const MAX_WIDGETS = 2; +import {useLocalStorageState} from "../../../hooks/useLocalStorageState"; +import ResizeNotifier from "../../../utils/ResizeNotifier"; +import WidgetStore from "../../../stores/WidgetStore"; export default class AppsDrawer extends React.Component { static propTypes = { userId: PropTypes.string.isRequired, room: PropTypes.object.isRequired, + resizeNotifier: PropTypes.instanceOf(ResizeNotifier).isRequired, showApps: PropTypes.bool, // Should apps be rendered hide: PropTypes.bool, // If rendered, should apps drawer be visible }; @@ -56,17 +58,13 @@ export default class AppsDrawer extends React.Component { componentDidMount() { ScalarMessaging.startListening(); - MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); - WidgetEchoStore.on('update', this._updateApps); + WidgetStore.instance.on(this.props.room.roomId, this._updateApps); this.dispatcherRef = dis.register(this.onAction); } componentWillUnmount() { ScalarMessaging.stopListening(); - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); - } - WidgetEchoStore.removeListener('update', this._updateApps); + WidgetStore.instance.off(this.props.room.roomId, this._updateApps); if (this.dispatcherRef) dis.unregister(this.dispatcherRef); } @@ -95,28 +93,11 @@ export default class AppsDrawer extends React.Component { } }; - onRoomStateEvents = (ev, state) => { - if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') { - return; - } - this._updateApps(); - }; - - _getApps() { - const widgets = WidgetEchoStore.getEchoedRoomWidgets( - this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), - ); - return widgets.map((ev) => { - return WidgetUtils.makeAppConfig( - ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), - ); - }); - } + _getApps = () => WidgetStore.instance.getApps(this.props.room, true); _updateApps = () => { - const apps = this._getApps(); this.setState({ - apps: apps, + apps: this._getApps(), }); }; @@ -139,18 +120,6 @@ export default class AppsDrawer extends React.Component { onClickAddWidget = (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(); }; @@ -161,19 +130,19 @@ export default class AppsDrawer extends React.Component { return (); }); - if (apps.length == 0) { - return
    ; + if (apps.length === 0) { + return
    ; } let addWidget; @@ -202,14 +171,68 @@ export default class AppsDrawer extends React.Component { spinner = ; } + const classes = classNames({ + "mx_AppsDrawer": true, + "mx_AppsDrawer_hidden": this.props.hide, + "mx_AppsDrawer_fullWidth": apps.length < 2, + "mx_AppsDrawer_minimised": !this.props.showApps, + }); + return ( -
    -
    +
    + { apps } { spinner } -
    + { this._canUserModify() && addWidget }
    ); } } + +const PersistentVResizer = ({ + id, + minHeight, + maxHeight, + className, + handleWrapperClass, + handleClass, + resizeNotifier, + children, +}) => { + const [height, setHeight] = useLocalStorageState("pvr_" + id, 280); // old fixed height was 273px + const [resizing, setResizing] = useState(false); + + return { + if (!resizing) setResizing(true); + resizeNotifier.startResizing(); + }} + onResize={() => { + resizeNotifier.notifyTimelineHeightChanged(); + }} + onResizeStop={(e, dir, ref, d) => { + setHeight(height + d.height); + if (resizing) setResizing(false); + resizeNotifier.stopResizing(); + }} + handleWrapperClass={handleWrapperClass} + handleClasses={{bottom: handleClass}} + className={classNames(className, { + mx_AppsDrawer_resizing: resizing, + })} + enable={{bottom: true}} + > + { children } + ; +}; diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index fc31d66160..1f6f104487 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -204,6 +204,7 @@ export default class AuxPanel extends React.Component { maxHeight={this.props.maxHeight} showApps={this.props.showApps} hide={this.props.hideAppsDrawer} + resizeNotifier={this.props.resizeNotifier} />; let stateViews = null; diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 1fdc67eee7..40b3b042b1 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -20,13 +20,14 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher/dispatcher'; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import {isValid3pidInvite} from "../../../RoomInvite"; import rate_limited_func from "../../../ratelimitedfunc"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import * as sdk from "../../../index"; import CallHandler from "../../../CallHandler"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; +import BaseCard from "../right_panel/BaseCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_INVITED = 5; @@ -438,7 +439,13 @@ export default class MemberList extends React.Component { render() { if (this.state.loading) { const Spinner = sdk.getComponent("elements.Spinner"); - return
    ; + return + + ; } const SearchBox = sdk.getComponent('structures.SearchBox'); @@ -485,25 +492,29 @@ export default class MemberList extends React.Component { />; } - return ( -
    - { inviteButton } - -
    - - { invitedHeader } - { invitedSection } -
    -
    - - -
    + const footer = ( + ); + + return +
    + + { invitedHeader } + { invitedSection } +
    +
    ; } onInviteButtonClick = () => { diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 2a44f53d21..1a116838ac 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -18,14 +18,11 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import Modal from "../../../Modal"; import RateLimitedFunc from '../../../ratelimitedfunc'; import { linkifyElement } from '../../../HtmlUtils'; -import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; @@ -114,13 +111,6 @@ export default class RoomHeader extends React.Component { this.forceUpdate(); }; - onShareRoomClick = (ev) => { - const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); - Modal.createTrackedDialog('share room dialog', '', ShareDialog, { - target: this.props.room, - }); - }; - _hasUnreadPins() { const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); if (!currentPinEvent) return false; @@ -150,7 +140,6 @@ export default class RoomHeader extends React.Component { render() { let searchStatus = null; let cancelButton = null; - let settingsButton = null; let pinnedEventsButton = null; if (this.props.onCancelClick) { @@ -214,14 +203,6 @@ export default class RoomHeader extends React.Component { />; } - if (this.props.onSettingsClick) { - settingsButton = - ; - } - if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) { let pinsIndicator = null; if (this._hasUnreadPins()) { @@ -258,26 +239,9 @@ export default class RoomHeader extends React.Component { title={_t("Search")} />; } - let shareRoomButton; - if (this.props.inRoom) { - shareRoomButton = - ; - } - - let manageIntegsButton; - if (this.props.room && this.props.room.roomId && this.props.inRoom) { - manageIntegsButton = ; - } - const rightRow =
    - { settingsButton } { pinnedEventsButton } - { shareRoomButton } - { manageIntegsButton } { forgetButton } { searchButton }
    ; diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts new file mode 100644 index 0000000000..e8eb0c23b4 --- /dev/null +++ b/src/contexts/RoomContext.ts @@ -0,0 +1,48 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { createContext } from "react"; + +import {IState} from "../components/structures/RoomView"; + +const RoomContext = createContext({ + roomLoading: true, + peekLoading: false, + shouldPeek: true, + membersLoaded: false, + numUnreadMessages: 0, + draggingFile: false, + searching: false, + guestsCanJoin: false, + canPeek: false, + showApps: false, + isAlone: false, + isPeeking: false, + showingPinned: false, + showReadReceipts: true, + showRightPanel: true, + joining: false, + atEndOfLiveTimeline: true, + atEndOfLiveTimelineInit: false, + showTopUnreadMessagesBar: false, + statusBarVisible: false, + canReact: false, + canReply: false, + useIRCLayout: false, + matrixClientIsReady: false, +}); +RoomContext.displayName = "RoomContext"; +export default RoomContext; diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 6fb71df30d..26d585b76e 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -94,4 +94,14 @@ export enum Action { * Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload. */ AfterRightPanelPhaseChange = "after_right_panel_phase_change", + + /** + * Requests that the AppTile deletes the widget. Should be used with the AppTileActionPayload. + */ + AppTileDelete = "appTile_delete", + + /** + * Requests that the AppTile revokes the widget. Should be used with the AppTileActionPayload. + */ + AppTileRevoke = "appTile_revoke", } diff --git a/src/contexts/RoomContext.js b/src/dispatcher/payloads/AppTileActionPayload.ts similarity index 66% rename from src/contexts/RoomContext.js rename to src/dispatcher/payloads/AppTileActionPayload.ts index 8613be195c..3cdb0f8c1f 100644 --- a/src/contexts/RoomContext.js +++ b/src/dispatcher/payloads/AppTileActionPayload.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,12 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { createContext } from "react"; +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; -const RoomContext = createContext({ - canReact: undefined, - canReply: undefined, - room: undefined, -}); -RoomContext.displayName = "RoomContext"; -export default RoomContext; +export interface AppTileActionPayload extends ActionPayload { + action: Action.AppTileDelete | Action.AppTileRevoke; + widgetId: string; +} diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts index 75dea9f3df..4126e8a669 100644 --- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts +++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts @@ -34,4 +34,5 @@ export interface SetRightPanelPhaseRefireParams { groupRoomId?: string; // XXX: The type for event should 'view_3pid_invite' action's payload event?: any; + widgetId?: string; } diff --git a/src/hooks/useLocalStorageState.ts b/src/hooks/useLocalStorageState.ts new file mode 100644 index 0000000000..ce3b574f86 --- /dev/null +++ b/src/hooks/useLocalStorageState.ts @@ -0,0 +1,44 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {Dispatch, SetStateAction, useCallback, useEffect, useState} from "react"; + +const getValue = (key: string, initialValue: T): T => { + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + return initialValue; + } +}; + +// Hook behaving like useState but persisting the value to localStorage. Returns same as useState +export const useLocalStorageState = (key: string, initialValue: T) => { + const lsKey = "mx_" + key; + + const [value, setValue] = useState(getValue(lsKey, initialValue)); + + useEffect(() => { + setValue(getValue(lsKey, initialValue)); + }, [lsKey, initialValue]); + + const _setValue: Dispatch> = useCallback((v: T) => { + window.localStorage.setItem(lsKey, JSON.stringify(v)); + setValue(v); + }, [lsKey]); + + return [value, _setValue]; +}; diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 9515a57f8f..14a87f8308 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -889,12 +889,12 @@ "Go back to set it again.": "Gehe zurück und setze es erneut.", "Download": "Herunterladen", "Print it and store it somewhere safe": "Drucke ihn aus und lagere ihn an einem sicheren Ort", - "Save it on a USB key or backup drive": "Speichere ihn auf einem USB-Schlüssel oder Sicherungsslaufwerk", + "Save it on a USB key or backup drive": "Speichere ihn auf einem USB-Schlüssel oder Sicherungslaufwerk", "Copy it to your personal cloud storage": "Kopiere ihn in deinen persönlichen Cloud-Speicher", "Unable to create key backup": "Konnte Schlüsselsicherung nicht erstellen", "Retry": "Erneut probieren", - "Unable to restore backup": "Konnte Sicherung nicht wiederherstellen", - "No backup found!": "Keine Sicherung gefunden!", + "Unable to restore backup": "Konnte Schlüsselsicherung nicht wiederherstellen", + "No backup found!": "Keine Schlüsselsicherung gefunden!", "This looks like a valid recovery key!": "Dies sieht wie ein gültiger Wiederherstellungsschlüssel aus!", "Not a valid recovery key": "Kein valider Wiederherstellungsschlüssel", "There was an error joining the room": "Es gab einen Fehler beim Raum-Beitreten", @@ -934,7 +934,7 @@ "Use a longer keyboard pattern with more turns": "Nutze ein längeres Tastaturmuster mit mehr Abwechslung", "Straight rows of keys are easy to guess": "Gerade Reihen von Tasten sind einfach zu erraten", "Custom user status messages": "Angepasste Nutzerstatus-Nachrichten", - "Unable to load key backup status": "Konnte Status des Schlüsselbackups nicht laden", + "Unable to load key backup status": "Konnte Status der Schlüsselsicherung nicht laden", "Don't ask again": "Nicht erneut fragen", "Set up": "Einrichten", "Please review and accept all of the homeserver's policies": "Bitte prüfen und akzeptieren Sie alle Richtlinien des Heimservers", @@ -942,7 +942,7 @@ "That doesn't look like a valid email address": "Sieht nicht nach einer validen E-Mail-Adresse aus", "Unable to load commit detail: %(msg)s": "Konnte Commit-Details nicht laden: %(msg)s", "Checking...": "Überprüfe...", - "Unable to load backup status": "Konnte Backupstatus nicht laden", + "Unable to load backup status": "Konnte Sicherungsstatus nicht laden", "Failed to decrypt %(failedCount)s sessions!": "Konnte %(failedCount)s Sitzungen nicht entschlüsseln!", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Greifen Sie auf Ihre sichere Nachrichtenhistorie zu und richten Sie einen sicheren Nachrichtenversand ein, indem Sie Ihre Wiederherstellungspassphrase eingeben.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Wenn du deinen Wiederherstellungspassphrase vergessen hast, kannst du deinen Wiederherstellungsschlüssel benutzen oder neue Wiederherstellungsoptionen einrichten", @@ -992,7 +992,7 @@ "You've successfully verified this user.": "Du hast diesen Benutzer erfolgreich verifiziert.", "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Sichere Nachrichten mit diesem Benutzer sind Ende-zu-Ende-verschlüsselt und können nicht von Dritten gelesen werden.", "Got It": "Verstanden", - "Verify this user by confirming the following number appears on their screen.": "Verifizieren Sie diesen Benutzer, indem Sie bestätigen, dass die folgende Nummer auf dessen Bildschirm erscheint.", + "Verify this user by confirming the following number appears on their screen.": "Verifiziere diese Nutzer!n, indem du bestätigst, dass die folgende Nummer auf dessen Bildschirm erscheint.", "Yes": "Ja", "No": "Nein", "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Wir haben dir eine E-Mail geschickt, um deine Adresse zu überprüfen. Bitte folge den Anweisungen dort und klicke dann auf die Schaltfläche unten.", @@ -1111,7 +1111,7 @@ "Ignored users": "Ignorierte Benutzer", "Key backup": "Schlüsselsicherung", "Gets or sets the room topic": "Frage das Thema des Raums ab oder setze es", - "Verify this user by confirming the following emoji appear on their screen.": "Verifizieren Sie diesen Benutzer, indem Sie bestätigen, dass folgendes Emoji auf dessen Bildschirm erscheint.", + "Verify this user by confirming the following emoji appear on their screen.": "Verifiziere diese Nutzer!n, indem du bestätigst, dass folgendes Emoji auf dessen Bildschirm erscheint.", "Missing media permissions, click the button below to request.": "Fehlende Medienberechtigungen. Drücke auf den Knopf unten, um sie anzufordern.", "Request media permissions": "Medienberechtigungen anfordern", "Main address": "Primäre Adresse", @@ -1128,7 +1128,7 @@ "Back up your keys before signing out to avoid losing them.": "Sichere deine Schlüssel bevor du dich abmeldest, damit du sie nicht verlierst.", "Start using Key Backup": "Beginne Schlüsselsicherung zu nutzen", "Credits": "Danksagungen", - "Starting backup...": "Starte Backup...", + "Starting backup...": "Starte Sicherung...", "Success!": "Erfolgreich!", "Your keys are being backed up (the first backup could take a few minutes).": "Deine Schlüssel werden gesichert (Das erste Backup könnte ein paar Minuten in Anspruch nehmen).", "Voice & Video": "Sprach- & Videoanruf", @@ -1380,11 +1380,11 @@ "Manage": "Verwalten", "Securely cache encrypted messages locally for them to appear in search results.": "Speichere verschlüsselte Nachrichten sicher lokal zwischen, sodass sie in Suchergebnissen erscheinen können.", "Enable": "Aktivieren", - "Connecting to integration manager...": "Verbinden zum Integrationsmanager...", + "Connecting to integration manager...": "Verbinde mit Integrationsmanager...", "Cannot connect to integration manager": "Verbindung zum Integrationsmanager fehlgeschlagen", "The integration manager is offline or it cannot reach your homeserver.": "Der Integrationsmanager ist offline oder er kann den Heimserver nicht erreichen.", "not stored": "nicht gespeichert", - "Backup has a signature from unknown user with ID %(deviceId)s": "Backup hat eine Signatur von Unbekanntem Nutzer mit ID %(deviceId)s", + "Backup has a signature from unknown user with ID %(deviceId)s": "Die Sicherung hat eine Signatur von unbekanntem/r Nutzer!n mit ID %(deviceId)s", "Backup key stored: ": "Backup Schlüssel gespeichert: ", "Clear notifications": "Benachrichtigungen löschen", "Disconnect from the identity server and connect to instead?": "Verbindung vom Identitätsserver trennen und stattdessen zu verbinden?", @@ -1431,9 +1431,9 @@ "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "ACHTUNG: SCHLÜSSEL-VERIFIZIERUNG FEHLGESCHLAGEN! Der Signierschlüssel für %(userId)s und Sitzung %(deviceId)s ist \"%(fprint)s\", was nicht mit dem bereitgestellten Schlüssel \"%(fingerprint)s\" übereinstimmt. Das könnte bedeuten, dass deine Kommunikation abgehört wird!", "Never send encrypted messages to unverified sessions from this session": "Sende niemals verschlüsselte Nachrichten von dieser Sitzung zu unverifizierten Sitzungen", "Never send encrypted messages to unverified sessions in this room from this session": "Sende niemals verschlüsselte Nachrichten von dieser Sitzung zu unverifizierten Sitzungen in diesem Raum", - "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Durch die Änderung des Passworts werden derzeit alle End-zu-End-Verschlüsselungsschlüssel in allen Sitzungen zurückgesetzt, sodass der verschlüsselte Chat-Verlauf nicht mehr lesbar ist, es sei denn, Sie exportieren zuerst Ihre Raumschlüssel und importieren sie anschließend wieder. In Zukunft wird dies verbessert werden.", + "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Durch die Änderung des Passworts werden derzeit alle Ende-zu-Ende-Verschlüsselungsschlüssel in allen Sitzungen zurückgesetzt, sodass der verschlüsselte Chat-Verlauf nicht mehr lesbar ist, es sei denn, du exportierst zuerst deine Raumschlüssel und importierst sie anschließend wieder. In Zukunft wird dies verbessert werden.", "Delete %(count)s sessions|other": "Lösche %(count)s Sitzungen", - "Backup is not signed by any of your sessions": "Die Sicherung ist von keiner Ihrer Sitzungen unterzeichnet", + "Backup is not signed by any of your sessions": "Die Sicherung wurde von keiner deiner Sitzungen unterzeichnet", "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Ihr Passwort wurde erfolgreich geändert. Sie erhalten keine Push-Benachrichtigungen zu anderen Sitzungen, bis Sie sich wieder bei diesen anmelden", "Notification sound": "Benachrichtigungston", "Set a new custom sound": "Setze einen neuen benutzerdefinierten Ton", @@ -1569,7 +1569,7 @@ "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s hat die alternative Adresse %(addresses)s für diesen Raum entfernt.", "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s hat die alternative Adresse für diesen Raum geändert.", "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s hat die Haupt- und Alternativadresse für diesen Raum geändert.", - "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Nutzer, die %(glob)s entsprechen", + "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Nutzer!nnen, die %(glob)s entsprechen", "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Räume, die %(glob)s entsprechen", "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Server, die %(glob)s entsprechen", "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel, die %(glob)s entspricht", @@ -1631,8 +1631,8 @@ "Copy": "In Zwischenablage kopieren", "Make a copy of your recovery key": "Speichere deinen Wiederherstellungsschlüssel", "Sends a message as html, without interpreting it as markdown": "Verschickt eine Nachricht im html-Format, ohne sie in Markdown zu formatieren", - "Show rooms with unread notifications first": "Räume mit nicht gelesenen Benachrichtungen zuerst zeigen", - "Show shortcuts to recently viewed rooms above the room list": "Kurzbefehle zu den kürzlich gesichteteten Räumen über der Raumliste anzeigen", + "Show rooms with unread notifications first": "Räume mit ungelesenen Benachrichtigungen zuerst zeigen", + "Show shortcuts to recently viewed rooms above the room list": "Kurzbefehle zu den kürzlich gesichteten Räumen über der Raumliste anzeigen", "Use Single Sign On to continue": "Verwende Single Sign on um fortzufahren", "Confirm adding this email address by using Single Sign On to prove your identity.": "Bestätige die hinzugefügte E-Mail-Adresse mit Single Sign-On, um deine Identität nachzuweisen.", "Single Sign On": "Single Sign-On", @@ -1732,12 +1732,12 @@ "Upgrade": "Hochstufen", "Verify the new login accessing your account: %(name)s": "Verifiziere die neue Anmeldung an deinem Konto: %(name)s", "From %(deviceName)s (%(deviceId)s)": "Von %(deviceName)s (%(deviceId)s)", - "Your homeserver does not support cross-signing.": "Dein Heimserver unterstützt Cross-Signing nicht.", + "Your homeserver does not support cross-signing.": "Dein Heimserver unterstützt kein Cross-Signing.", "Cross-signing and secret storage are enabled.": "Cross-signing und der sichere Speicher wurden eingerichtet.", - "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Dein Konto hat eine Cross-Signing Identität im sicheren Speicher, aber dieser wird von dieser Sitzung noch nicht vertraut.", + "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Dein Konto hat eine Cross-Signing-Identität im sicheren Speicher, der von dieser Sitzung jedoch noch nicht vertraut wird.", "Cross-signing and secret storage are not yet set up.": "Cross-Signing und der sichere Speicher sind noch nicht eingerichtet.", - "Reset cross-signing and secret storage": "Setze Cross-Signing und den sicheren Speicher zurück", - "Bootstrap cross-signing and secret storage": "Richte cross-signing und den sicheren Speicher ein", + "Reset cross-signing and secret storage": "Cross-Signing und den sicheren Speicher zurücksetzen", + "Bootstrap cross-signing and secret storage": "Richte Cross-Signing und den sicheren Speicher ein", "unexpected type": "unbekannter Typ", "Cross-signing public keys:": "Öffentliche Cross-Signing-Schlüssel:", "in memory": "im Speicher", @@ -1750,13 +1750,13 @@ "Session backup key:": "Sitzungswiederherstellungsschlüssel:", "Secret storage public key:": "Öffentlicher Schlüssel des sicheren Speichers:", "in account data": "in den Kontodaten", - "Homeserver feature support:": "Heimserverunterstützung:", + "Homeserver feature support:": "Home-Server-Funktionsunterstützung:", "exists": "existiert", "Delete sessions|other": "Lösche Sitzungen", "Delete sessions|one": "Lösche Sitzung", "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Alle Sitzungen einzeln verifizieren, anstatt auch Sitzungen zu vertrauen, die durch Cross-Signing verifiziert sind.", "Securely cache encrypted messages locally for them to appear in search results, using ": "Der Zwischenspeicher für die lokale Suche in verschlüsselten Nachrichten benötigt ", - " to store messages from ": " um Nachrichten aus ", + " to store messages from ": " um Nachrichten zu speichern von ", "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s benötigt weitere Komponenten um verschlüsselte Nachrichten lokal zu durchsuchen. Wenn du diese Funktion testen möchtest kannst du dir deine eigene Version von %(brand)s Desktop mit der integrierten Suchfunktion bauen.", "Backup has a valid signature from this user": "Die Sicherung hat eine gültige Signatur dieses Benutzers", "Backup has a invalid signature from this user": "Die Sicherung hat eine ungültige Signatur dieses Benutzers", @@ -2094,7 +2094,7 @@ "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "Ein Widget unter %(widgetUrl)s möchte deine Identität überprüfen. Wenn du dies zulässt, kann das Widget deine Nutzer-ID überprüfen, jedoch keine Aktionen in deinem Namen ausführen.", "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Der sichere Speicher konnte nicht geladen werden. Bitte stelle sicher dass du die richtige Wiederherstellungspassphrase eingegeben hast.", "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Die Sicherung konnte nicht mit dem angegebenen Wiederherstellungsschlüssel entschlüsselt werden: Bitte überprüfe ob du den richtigen Wiederherstellungsschlüssel eingegeben hast.", - "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Die Sicherung konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden: Bitte überprüfe ob du den richtigen Wiederherstellungspassphrase eingegeben hast.", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Die Sicherung konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden: Bitte überprüfe ob du die richtige Wiederherstellungspassphrase eingegeben hast.", "Nice, strong password!": "Super, ein starkes Passwort!", "Other users can invite you to rooms using your contact details": "Andere Benutzer können dich mit deinen Kontaktdaten in Räume einladen", "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Lege eine E-Mail für die Kontowiederherstellung fest. Verwende optional E-Mail oder Telefon, um von Anderen gefunden zu werden.", @@ -2423,5 +2423,39 @@ "Error leaving room": "Fehler beim Verlassen des Raums", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 Prototyp. Benötigt einen kompatiblen Heimserver. Höchst experimentell - mit Vorsicht verwenden.", "Explore rooms in %(communityName)s": "Erkunde Räume in %(communityName)s", - "Set up Secure Backup": "Sicherung einrichten" + "Set up Secure Backup": "Schlüsselsicherung einrichten", + "Information": "Information", + "Add another email": "Weitere E-Mail-Adresse hinzufügen", + "Send %(count)s invites|other": "%(count)s Einladungen senden", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Beim Erstellen deiner Community ist ein Fehler aufgetreten. Entweder ist der Name schon vergeben oder der Server kann die Anfrage nicht verarbeiten.", + "Community ID: +:%(domain)s": "Community-ID: +:%(domain)s", + "Explore community rooms": "Entdecke Community Räume", + "You can change this later if needed.": "Falls nötig, kannst du es später noch ändern.", + "What's the name of your community or team?": "Welchen Namen hat deine Community oder dein Team?", + "Enter name": "Namen eingeben", + "Add image (optional)": "Bild hinzufügen (optional)", + "Create a room in %(communityName)s": "Erstelle einen Raum in %(communityName)s", + "Create community": "Erstelle Community", + "Cross-signing and secret storage are ready for use.": "Cross-Signing und der sichere Speicher sind bereit zur Benutzung.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-Signing ist bereit, aber der sichere Speicher wird noch nicht als Schlüsselbackup benutzt.", + "People you know on %(brand)s": "Leute, die du auf %(brand)s kennst", + "Send %(count)s invites|one": "%(count)s Einladung senden", + "Invite people to join %(communityName)s": "Lade Leute ein %(communityName)s beizutreten", + "An image will help people identify your community.": "Ein Bild hilft anderen, deine Community zu Identifizieren.", + "Use this when referencing your community to others. The community ID cannot be changed.": "Verwende dies, um deine Community von andere referenzieren zu lassen. Die Community-ID kann später nicht geändert werden.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Private Räume können nur auf Einladung gefunden und betreten werden. Öffentliche Räume können von jedem/r gefunden und betreten werden.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private Räume können nur auf Einladung gefunden und betreten werden. Öffentliche Räume können von jedem/r in dieser Community gefunden und betreten werden.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Du solltest dies aktivieren, wenn der Raum nur für die Zusammenarbeit mit internen Teams auf deinem Heimserver verwendet wird. Dies kann später nicht mehr geändert werden.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Du solltest dies deaktivieren, wenn der Raum für die Zusammenarbeit mit externen Teams auf deren Home-Server verwendet wird. Dies kann später nicht mehr geändert werden.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Blockiere alle, die nicht Teil von %(serverName)s sind, diesen Raum jemals zu betreten.", + "Privacy": "Privatsphäre", + "There was an error updating your community. The server is unable to process your request.": "Beim Aktualisieren deiner Community ist ein Fehler aufgetreten. Der Server kann deine Anfrage nicht verarbeiten.", + "Update community": "Community aktualisieren", + "May include members not in %(communityName)s": "Kann Mitglieder enthalten, die nicht in %(communityName)s enthalten sind", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Starte ein Gespräch mit jemandem unter Verwendung seines/ihres Namens, Nutzernamens (wie ) oder E-Mail-Adresse. Dadurch werden sie nicht zu %(communityName)s eingeladen. Klicke hier hier, um jemanden zu %(communityName)s einzuladen.", + "Failed to find the general chat for this community": "Der allgemeine Chat für diese Community konnte nicht gefunden werden", + "Community settings": "Community-Einstellungen", + "User settings": "Nutzer-Einstellungen", + "Community and user menu": "Community- und Nutzer-Menü", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Stellt ( ͡° ͜ʖ ͡°) einer Klartextnachricht voran" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 95b6c23a77..054777fd64 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -149,6 +149,7 @@ "Command error": "Command error", "Usage": "Usage", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", @@ -386,6 +387,7 @@ "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", + "Unknown App": "Unknown App", "Help us improve %(brand)s": "Help us improve %(brand)s", "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.", "I want to help": "I want to help", @@ -1027,8 +1029,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "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.", "Phone Number": "Phone Number", - "Cannot add any more widgets": "Cannot add any more widgets", - "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.", "Add a widget": "Add a widget", "Drop File Here": "Drop File Here", "Drop file here to upload": "Drop file here to upload", @@ -1113,10 +1113,8 @@ "(~%(count)s results)|other": "(~%(count)s results)", "(~%(count)s results)|one": "(~%(count)s result)", "Join Room": "Join Room", - "Settings": "Settings", "Forget room": "Forget room", "Search": "Search", - "Share room": "Share room", "Invites": "Invites", "Favourites": "Favourites", "People": "People", @@ -1133,6 +1131,7 @@ "Can't see what you’re looking for?": "Can't see what you’re looking for?", "Explore all public rooms": "Explore all public rooms", "%(count)s results|other": "%(count)s results", + "%(count)s results|one": "%(count)s result", "This room": "This room", "Joining room …": "Joining room …", "Loading …": "Loading …", @@ -1195,6 +1194,7 @@ "Favourited": "Favourited", "Favourite": "Favourite", "Low Priority": "Low Priority", + "Settings": "Settings", "Leave Room": "Leave Room", "Room options": "Room options", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", @@ -1265,6 +1265,7 @@ "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.", "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.", + "Back": "Back", "Waiting for you to accept on your other session…": "Waiting for you to accept on your other session…", "Waiting for %(displayName)s to accept…": "Waiting for %(displayName)s to accept…", "Accepting…": "Accepting…", @@ -1282,7 +1283,18 @@ "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", "Yours, or the other users’ session": "Yours, or the other users’ session", "Members": "Members", - "Files": "Files", + "Room Info": "Room Info", + "Apps": "Apps", + "Unpin app": "Unpin app", + "Edit apps, bridges & bots": "Edit apps, bridges & bots", + "Add apps, bridges & bots": "Add apps, bridges & bots", + "Not encrypted": "Not encrypted", + "About": "About", + "%(count)s people|other": "%(count)s people", + "%(count)s people|one": "%(count)s person", + "Show files": "Show files", + "Share room": "Share room", + "Room settings": "Room settings", "Trusted": "Trusted", "Not trusted": "Not trusted", "%(count)s verified sessions|other": "%(count)s verified sessions", @@ -1360,6 +1372,12 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", + "Take a picture": "Take a picture", + "Remove for everyone": "Remove for everyone", + "Remove for me": "Remove for me", + "Edit": "Edit", + "Pin to room": "Pin to room", + "You can only pin 2 apps at a time": "You can only pin 2 apps at a time", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday", @@ -1377,7 +1395,6 @@ "Error decrypting audio": "Error decrypting audio", "React": "React", "Reply": "Reply", - "Edit": "Edit", "Message Actions": "Message Actions", "Attachment": "Attachment", "Error decrypting attachment": "Error decrypting attachment", @@ -1488,7 +1505,6 @@ "Download this file": "Download this file", "Information": "Information", "Language Dropdown": "Language Dropdown", - "Manage Integrations": "Manage Integrations", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", @@ -1668,7 +1684,6 @@ "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", - "Back": "Back", "Send": "Send", "Send Custom Event": "Send Custom Event", "You must specify an event type!": "You must specify an event type!", @@ -1909,10 +1924,9 @@ "Set status": "Set status", "Set a new status...": "Set a new status...", "View Community": "View Community", + "Unpin": "Unpin", "Reload": "Reload", "Take picture": "Take picture", - "Remove for everyone": "Remove for everyone", - "Remove for me": "Remove for me", "This room is public": "This room is public", "Away": "Away", "User Status": "User Status", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index a4249a93eb..a1275fb089 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -618,5 +618,37 @@ "Try using turn.matrix.org": "Try using turn.matrix.org", "Messages": "Messages", "Actions": "Actions", - "Other": "Other" + "Other": "Other", + "Confirm": "Confirm", + "Add Email Address": "Add Email Address", + "Confirm adding this phone number by using Single Sign On to prove your identity.": "Confirm adding this phone number by using Single Sign On to prove your identity.", + "Confirm adding phone number": "Confirm adding phone number", + "Click the button below to confirm adding this phone number.": "Click the button below to confirm adding this phone number.", + "Add Phone Number": "Add Phone Number", + "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Whether you're using %(brand)s on a device where touch is the primary input mechanism", + "Whether you're using %(brand)s as an installed Progressive Web App": "Whether you're using %(brand)s as an installed Progressive Web App", + "Your user agent": "Your user agent", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.", + "Cancel entering passphrase?": "Cancel entering passphrase?", + "Are you sure you want to cancel entering passphrase?": "Are you sure you want to cancel entering passphrase?", + "Go Back": "Go Back", + "Setting up keys": "Setting up keys", + "Room name or address": "Room name or address", + "Identity server has no terms of service": "Identity server has no terms of service", + "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", + "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", + "Trust": "Trust", + "%(name)s is requesting verification": "%(name)s is requesting verification", + "Sign In or Create Account": "Sign In or Create Account", + "Use your account or create a new one to continue.": "Use your account or create a new one to continue.", + "Create Account": "Create Account", + "Sign In": "Sign In", + "Custom (%(level)s)": "Custom (%(level)s)", + "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", + "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", + "You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.", + "Error upgrading room": "Error upgrading room", + "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", + "Changes the avatar of the current room": "Changes the avatar of the current room", + "Changes your avatar in all rooms": "Changes your avatar in all rooms" } diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 1619bb7616..307c8c10c9 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -50,7 +50,7 @@ "Deops user with given id": "Degrada al usuario con la ID dada", "Default": "Por Defecto", "Disinvite": "Deshacer invitación", - "Displays action": "Muestra la acción", + "Displays action": "Hacer una acción", "Download %(text)s": "Descargar %(text)s", "Email": "Correo electrónico", "Email address": "Dirección de correo electrónico", @@ -285,7 +285,7 @@ "Uploading %(filename)s and %(count)s others|one": "Subiendo %(filename)s y otros %(count)s", "Uploading %(filename)s and %(count)s others|other": "Subiendo %(filename)s y otros %(count)s", "Upload avatar": "Subir avatar", - "Upload Failed": "No Se Pudo Subir", + "Upload Failed": "Subida fallida", "Upload file": "Subir archivo", "Upload new:": "Subir nuevo:", "Usage": "Uso", @@ -397,7 +397,7 @@ "This Room": "Esta sala", "Resend": "Reenviar", "Room not found": "Sala no encontrada", - "Messages containing my display name": "Mensajes que contienen mi nombre público", + "Messages containing my display name": "Mensajes que contengan mi nombre público", "Messages in one-to-one chats": "Mensajes en conversaciones uno a uno", "Unavailable": "No disponible", "View Decrypted Source": "Ver Fuente Descifrada", @@ -472,7 +472,7 @@ "Unhide Preview": "Mostrar Vista Previa", "Unable to join network": "No se puede unir a la red", "Sorry, your browser is not able to run %(brand)s.": "¡Lo sentimos! Su navegador no puede ejecutar %(brand)s.", - "Messages in group chats": "Mensajes en conversaciones en grupo", + "Messages in group chats": "Mensajes en conversaciones grupales", "Yesterday": "Ayer", "Error encountered (%(errorDetail)s).": "Error encontrado (%(errorDetail)s).", "Low Priority": "Prioridad Baja", @@ -518,7 +518,7 @@ "Failed to invite the following users to %(groupId)s:": "No se pudo invitar a los siguientes usuarios a %(groupId)s:", "Failed to invite users to community": "No se pudo invitar usuarios a la comunidad", "Failed to invite users to %(groupId)s": "No se pudo invitar usuarios a %(groupId)s", - "Failed to add the following rooms to %(groupId)s:": "No se pudo añadir a las siguientes salas a %(groupId)s:", + "Failed to add the following rooms to %(groupId)s:": "No se pudieron añadir las siguientes salas a %(groupId)s:", "Restricted": "Restringido", "Missing roomId.": "Falta el Id de sala.", "Ignores a user, hiding their messages from you": "Ignora a un usuario, ocultando sus mensajes", @@ -537,7 +537,7 @@ "Not a valid %(brand)s keyfile": "No es un archivo de claves de %(brand)s válido", "Message Pinning": "Mensajes con chincheta", "Always show encryption icons": "Mostrar siempre iconos de cifrado", - "Automatically replace plain text Emoji": "Sustituir automáticamente Emojis de texto", + "Automatically replace plain text Emoji": "Reemplazar automáticamente texto por Emojis", "Mirror local video feed": "Clonar transmisión de video local", "Send analytics data": "Enviar datos de análisis de estadísticas", "Enable inline URL previews by default": "Habilitar vistas previas de URL en línea por defecto", @@ -802,7 +802,7 @@ "Old cryptography data detected": "Se detectó información de criptografía antigua", "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Se detectó una versión más antigua de %(brand)s. Esto habrá provocado que la criptografía de extremo a extremo funcione incorrectamente en la versión más antigua. Los mensajes cifrados de extremo a extremo intercambiados recientemente mientras usaba la versión más antigua puede que no sean descifrables con esta versión. Esto también puede hacer que fallen con la más reciente. Si experimenta problemas, desconecte y vuelva a ingresar. Para conservar el historial de mensajes, exporte y vuelva a importar sus claves.", "Your Communities": "Sus Comunidades", - "Did you know: you can use communities to filter your %(brand)s experience!": "Sabía que: puede usar comunidades para filtrar su experiencia con %(brand)s", + "Did you know: you can use communities to filter your %(brand)s experience!": "Sabía que: puede usar comunidades para filtrar su experiencia con %(brand)s !", "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Para configurar un filtro, arrastre un avatar de comunidad sobre el panel de filtro en la parte izquierda de la pantalla. Puede pulsar sobre un avatar en el panel de filtro en cualquier momento para ver solo las salas y personas asociadas con esa comunidad.", "Error whilst fetching joined communities": "Error al recuperar las comunidades a las que estás unido", "Create a new community": "Crear una comunidad nueva", @@ -927,22 +927,22 @@ "Custom user status messages": "Mensajes de estado de usuario personalizados", "Group & filter rooms by custom tags (refresh to apply changes)": "Agrupa y filtra salas por etiquetas personalizadas (refresca para aplicar cambios)", "Render simple counters in room header": "Muestra contadores simples en la cabecera de la sala", - "Enable Emoji suggestions while typing": "Habiliatar sugerencia de Emojis mientras se teclea", + "Enable Emoji suggestions while typing": "Habilitar sugerencia de Emojis mientras se teclea", "Show a placeholder for removed messages": "Mostrar una marca para los mensaje borrados", - "Show join/leave messages (invites/kicks/bans unaffected)": "Mostrar mensajes de unir/salir (no afecta a invitaciones/pateos/baneos )", + "Show join/leave messages (invites/kicks/bans unaffected)": "Mostrar mensajes de entrada/salida (no afecta a invitaciones/expulsiones/baneos)", "Show avatar changes": "Mostrar cambios de avatar", "Show display name changes": "Muestra cambios en los nombres", - "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Mostrar ecordatorio para habilitar 'Recuperación Segura de Mensajes ' en sala cifradas", + "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Mostrar un recordatorio para habilitar 'Recuperación Segura de Mensajes' en sala cifradas", "Show avatars in user and room mentions": "Mostrar avatares en menciones a usuarios y salas", "Enable big emoji in chat": "Habilitar emojis grandes en el chat", "Send typing notifications": "Enviar notificaciones de tecleo", "Allow Peer-to-Peer for 1:1 calls": "Permitir conexión de pares en llamadas individuales", - "Prompt before sending invites to potentially invalid matrix IDs": "Pedir confirmación antes de enviar invitaciones a IDs de matrix que parezcan inválidos", + "Prompt before sending invites to potentially invalid matrix IDs": "Pedir confirmación antes de enviar invitaciones a IDs de matrix que parezcan inválidas", "Show developer tools": "Mostrar herramientas de desarrollador", "Messages containing my username": "Mensajes que contengan mi nombre", "Messages containing @room": "Mensajes que contengan @room", - "Encrypted messages in one-to-one chats": "Mensajes cifrados en salas 1 a 1", - "Encrypted messages in group chats": "Mensajes cifrados en chats grupales", + "Encrypted messages in one-to-one chats": "Mensajes cifrados en salas uno a uno", + "Encrypted messages in group chats": "Mensajes cifrados en conversaciones grupales", "The other party cancelled the verification.": "El otro lado canceló la verificación.", "Verified!": "¡Verificado!", "You've successfully verified this user.": "Has verificado correctamente a este usuario.", @@ -1162,7 +1162,7 @@ "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s hizo una llamada de vídeo (no soportada por este navegador)", "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", "Try out new ways to ignore people (experimental)": "Pruebe nuevas formas de ignorar a usuarios (experimental)", - "Match system theme": "Usar el tema del sistema", + "Match system theme": "Utilizar el mismo tema del sistema", "Show previews/thumbnails for images": "Mostrar vistas previas para las imágenes", "When rooms are upgraded": "Cuando las salas son actualizadas", "My Ban List": "Mi lista de baneos", @@ -1246,7 +1246,7 @@ "Are you sure you want to sign out?": "¿Estás seguro de que quieres salir?", "Message edits": "Ediciones del mensaje", "New session": "Nueva sesión", - "Use this session to verify your new one, granting it access to encrypted messages:": "Usa esta sesión para verificar tu nueva sesión, dándola acceso a mensajes encriptados:", + "Use this session to verify your new one, granting it access to encrypted messages:": "Usa esta sesión para verificar tu nueva sesión, dándole acceso a mensajes encriptados:", "If you didn’t sign in to this session, your account may be compromised.": "Si no te conectaste a esta sesión, es posible que tu cuenta haya sido comprometida.", "This wasn't me": "No fui yo", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Si encuentras algún error o quieres compartir una opinión, por favor, contacta con nosotros en GitHub.", @@ -1318,7 +1318,7 @@ "Your user agent": "Tu agente de usuario", "If you cancel now, you won't complete verifying the other user.": "Si cancelas ahora, no completarás la verificación del otro usuario.", "If you cancel now, you won't complete verifying your other session.": "Si cancelas ahora, no completarás la verificación de tu otra sesión.", - "Cancel entering passphrase?": "¿Cancelar la introducción de frase de contraseña?", + "Cancel entering passphrase?": "¿Cancelar el ingresar tu contraseña de recuperación?", "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s actualizó la regla bloqueando salas que coinciden con %(glob)s por %(reason)s", "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s actualizó la regla bloqueando servidores que coinciden con %(glob)s por %(reason)s", "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s actualizó una regla de bloqueo correspondiente a %(glob)s por %(reason)s", @@ -1356,7 +1356,7 @@ "Compare unique emoji": "Comparar emoji único", "Compare a unique set of emoji if you don't have a camera on either device": "Comparar un conjunto de emojis si no tienes cámara en ninguno de los dispositivos", "Start": "Inicio", - "Waiting for %(displayName)s to verify…": "Esperando la verificación de %(displayName)s …", + "Waiting for %(displayName)s to verify…": "Esperando la verificación de %(displayName)s…", "Review": "Revise", "in secret storage": "en almacén secreto", "Secret storage public key:": "Clave pública del almacén secreto:", @@ -1436,7 +1436,7 @@ "If you cancel now, you won't complete your operation.": "Si cancela ahora, no completará la operación.", "Review where you’re logged in": "Revise dónde hizo su registro", "New login. Was this you?": "Nuevo registro. ¿Fuiste tú?", - "%(name)s is requesting verification": "%(name)s solicita verificación", + "%(name)s is requesting verification": "%(name)s solicita verificación", "Sign In or Create Account": "Iniciar sesión o Crear una cuenta", "Use your account or create a new one to continue.": "Usa tu cuenta existente o crea una nueva para continuar.", "Create Account": "Crear cuenta", @@ -1471,11 +1471,11 @@ "Show shortcuts to recently viewed rooms above the room list": "Mostrar atajos a las salas recientemente vistas por encima de la lista de salas", "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Permitir el servidor de respaldo de asistencia de llamadas turn.matrix.org cuando su servidor doméstico no lo ofrece (su dirección IP se compartiría durante una llamada)", "Send read receipts for messages (requires compatible homeserver to disable)": "Enviar recibos de lectura de mensajes (requiere un servidor local compatible para desactivarlo)", - "Manually verify all remote sessions": "Verifica manualmente todas las sesiones remotas", + "Manually verify all remote sessions": "Verificar manualmente todas las sesiones remotas", "Confirm the emoji below are displayed on both sessions, in the same order:": "Confirma que los emoji de abajo se muestran en el mismo orden en ambas sesiones:", "Verify this session by confirming the following number appears on its screen.": "Verifique esta sesión confirmando que el siguiente número aparece en su pantalla.", "Waiting for your other session, %(deviceName)s (%(deviceId)s), to verify…": "Esperando a que su otra sesión, %(deviceName)s (%(deviceId)s), verifica…", - "Cancelling…": "Anulando …", + "Cancelling…": "Anulando…", "Verify all your sessions to ensure your account & messages are safe": "Verifica todas tus sesiones abiertas para asegurarte de que tu cuenta y tus mensajes estén seguros", "Set up": "Configurar", "Verify the new login accessing your account: %(name)s": "Verifique el nuevo ingreso que está accediendo a su cuenta: %(name)s", @@ -1614,7 +1614,7 @@ "Can't find this server or its room list": "No puedo encontrar este servidor o su lista de salas", "All rooms": "Todas las salas", "Your server": "Tu", - "Are you sure you want to remove %(serverName)s": "¿ Está seguro de querer eliminar %(serverName)s?", + "Are you sure you want to remove %(serverName)s": "¿Está seguro de querer eliminar %(serverName)s ?", "Remove server": "Quitar servidor", "Matrix": "Matrix", "Add a new server": "Añadir un nuevo servidor", @@ -1914,7 +1914,7 @@ "Unable to restore backup": "No se pudo restaurar la copia de seguridad", "No backup found!": "¡No se encontró una copia de seguridad!", "Keys restored": "Se restauraron las claves", - "Failed to decrypt %(failedCount)s sessions!": "¡Error en descifrar %(failedCount) sesiones!", + "Failed to decrypt %(failedCount)s sessions!": "¡Error al descifrar %(failedCount)s sesiones!", "Successfully restored %(sessionCount)s keys": "%(sessionCount)s claves restauradas con éxito", "Warning: you should only set up key backup from a trusted computer.": "Advertencia: deberías configurar la copia de seguridad de claves solamente usando un ordenador de confianza.", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Acceda a su historial de mensajes seguros y configure la mensajería segura introduciendo su contraseña de recuperación.", @@ -2075,5 +2075,65 @@ "%(targetName)s changed their avatar": "%(targetName)s ha cambiado su avatar", "You changed the room name": "Has cambiado el nombre de la sala", "%(senderName)s changed the room name": "%(senderName)s cambio el nombre de la sala", - "You invited %(targetName)s": "Has invitado a %(targetName)s" + "You invited %(targetName)s": "Has invitado a %(targetName)s", + "Are you sure you want to cancel entering passphrase?": "¿Estas seguro que quieres cancelar el ingresar tu contraseña de recuperación?", + "Go Back": "No cancelar", + "Joins room with given address": "Entrar a la sala con la dirección especificada", + "Unrecognised room address:": "No se encuentra la dirección de la sala:", + "Opens chat with the given user": "Abrir una conversación con el usuario especificado", + "Sends a message to the given user": "Enviar un mensaje al usuario especificado", + "Light": "Claro", + "Dark": "Oscuro", + "Unexpected server error trying to leave the room": "Error inesperado del servidor al abandonar esta sala", + "Error leaving room": "Error al salir de la sala", + "Your homeserver has exceeded its user limit.": "Tú servidor ha excedido su limite de usuarios.", + "Your homeserver has exceeded one of its resource limits.": "Tú servidor ha excedido el limite de sus recursos.", + "Contact your server admin.": "Contacta con el administrador del servidor.", + "The person who invited you already left the room.": "La persona que te invito abandono la sala.", + "The person who invited you already left the room, or their server is offline.": "La persona que te invito abandono la sala, o puede que su servidor se encuentre desconectado.", + "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Tu nueva sesión se encuentra verificada ahora. Ahora tiene acceso a los mensajes encriptados y otros usuarios verán la sesión como verificada.", + "Your new session is now verified. Other users will see it as trusted.": "Tu sesión se encuentra ahora verificada. Otros usuarios la verán como confiable.", + "This session is encrypting history using the new recovery method.": "Esta sesión se encuentra encriptando el historial usando el nuevo método de verificación.", + "Change notification settings": "Cambiar los ajustes de notificaciones", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipo de comunidades v2. Requiere un servidor compatible. Altamente experimental - usar con precuación.", + "Font size": "Tamaño de la fuente", + "Use custom size": "Utilizar un tamaño personalizado", + "Use a more compact ‘Modern’ layout": "Usar un diseño más 'moderno' y compacto", + "Use a system font": "Utilizar una fuente del sistema", + "System font name": "Nombre de la fuente", + "Enable experimental, compact IRC style layout": "Activar el diseño de IRC compacto, en prueba", + "Uploading logs": "Subiendo registros", + "Downloading logs": "Descargando registro", + "Incoming voice call": "Llamada de voz entrante", + "Incoming video call": "Videollamada entrante", + "Incoming call": "Llamada entrante", + "Waiting for your other session to verify…": "Esperando a tu otra sesión confirme…", + "Your server isn't responding to some requests.": "Tú servidor no esta respondiendo a ciertas solicitudes.", + "There are advanced notifications which are not shown here.": "Hay configuraciones avanzadas que no se muestran aquí.", + "You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "Puede que las hayas configurado en otro cliente además de %(brand)s. No puedes cambiarlas en %(brand)s pero sus efectos siguen aplicándose.", + "New version available. Update now.": "Nueva versión disponible. Actualiza ahora.", + "Hey you. You're the best!": "Oye tú. ¡Eres el mejor!", + "Size must be a number": "El tamaño debe ser un dígito", + "Custom font size can only be between %(min)s pt and %(max)s pt": "El tamaño de la fuente solo puede estar entre los valores %(min)s y %(max)s", + "Use between %(min)s pt and %(max)s pt": "Utiliza un valor entre %(min)s y %(max)s", + "Message layout": "Diseño del mensaje", + "Compact": "Compacto", + "Modern": "Moderno", + "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Inserta el nombre de la fuente instalada en tu sistema y %(brand)s intentara utilizarla.", + "Customise your appearance": "Personaliza tu apariencia", + "Appearance Settings only affect this %(brand)s session.": "Cambiar las opciones de apariencia solo afecta esta %(brand)s sesión.", + "Please verify the room ID or address and try again.": "Por favor, verifica la ID o dirección de esta sala e inténtalo de nuevo.", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "El administrador del servidor domestico ha deshabilitado la encriptación de extremo a extremo en salas privadas y mensajes directos.", + "To link to this room, please add an address.": "Para vincular esta sala, por favor añade una dirección.", + "The authenticity of this encrypted message can't be guaranteed on this device.": "La autenticidad de estos mensajes encriptados no pueden ser garantizados en este dispositivo.", + "No recently visited rooms": "No hay salas visitadas recientemente", + "People": "Gente", + "Explore public rooms": "Buscar salas publicas", + "Can't see what you’re looking for?": "¿No encuentras nada de lo que buscas?", + "Explore all public rooms": "Buscar todas las salas publicas", + "%(count)s results|other": "%(count)s resultados" } diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 8a0ba7f54b..5500a4bd02 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2425,5 +2425,39 @@ "Error leaving room": "Viga jututoast lahkumisel", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Kogukondade v2 prototüüp. Eeldab, et koduserver toetab sellist funktsionaalsust. Lahendus on esialgne ja katseline - kui kasutad, siis väga ettevaatlikult.", "Explore rooms in %(communityName)s": "Uuri jututubasid %(communityName)s kogukonnas", - "Set up Secure Backup": "Võta kasutusele turvaline varundus" + "Set up Secure Backup": "Võta kasutusele turvaline varundus", + "Information": "Teave", + "Add another email": "Lisa veel üks e-posti aadress", + "People you know on %(brand)s": "%(brand)s kasutajad, keda sa tead", + "Send %(count)s invites|other": "Saada %(count)s kutset", + "Send %(count)s invites|one": "Saada %(count)s kutse", + "Invite people to join %(communityName)s": "Kutsu kasutajaid %(communityName)s kogukonna liikmeks", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Kogukonna loomisel tekkis viga. Selline nimi kas on juba kasutusel või server ei saa hetkel seda päringut töödelda.", + "Community ID: +:%(domain)s": "Kogukonna tunnus: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Viidates kogukonnale kasuta seda tunnust. Kogukonna tunnust ei ole võimalik muuta.", + "You can change this later if needed.": "Kui vaja, siis sa saad seda hiljem muuta.", + "What's the name of your community or team?": "Mis on sinu kogukonna või tiimi nimi?", + "Enter name": "Sisesta nimi", + "Add image (optional)": "Lisa pilt (kui soovid)", + "An image will help people identify your community.": "Pilt aitab inimestel teie kogukonda ära tunda.", + "Create community": "Loo kogukond", + "Explore community rooms": "Sirvi kogukonna jututubasid", + "Create a room in %(communityName)s": "Loo uus jututuba %(communityName)s kogukonda", + "Cross-signing and secret storage are ready for use.": "Risttunnustamine ja turvahoidla on kasutamiseks valmis.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Risttunnustamine on kasutamiseks valmis, kuid turvahoidla ei ole hetkel krüptovõtmete varundamiseks kasutusel.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Avalikke jututubasid saavad kõik leida ning nendega liituda.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Selles kogukonnas saavad avalikke jututubasid kõik leida ning nendega liituda.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Sa võid sellise võimaluse kasutusele võtta, kui seda jututuba kasutatakse vaid organisatsioonisiseste tiimide ühistööks oma koduserveri piires. Seda ei saa hiljem muuta.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Sa võid sellise võimaluse jätta kasutusele võtmata, kui seda jututuba kasutatakse erinevate väliste tiimide ühistööks kasutades erinevaid koduservereid. Seda ei saa hiljem muuta.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris.", + "May include members not in %(communityName)s": "Siin võib leiduda kasutajaid, kes ei ole %(communityName)s kogukonna liikmed", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Alusta vestust teise osapoolega tema nime, kasutajanime (näiteks ) või e-posti aadressi alusel. Sellega aga sa ei kutsu teda %(communityName)s kogukonna liikmeks. Kui soovid kedagi kutsuda %(communityName)s kogukonda, siis vajuta siia.", + "There was an error updating your community. The server is unable to process your request.": "Sinu kogukonna andmete uuendamisel tekkis viga. Server ei suuda sinu päringut töödelda.", + "Update community": "Uuenda kogukonda", + "Failed to find the general chat for this community": "Ei õnnestunud tuvastada selle kogukonna üldist rühmavestlust", + "Community settings": "Kogukonna seadistused", + "User settings": "Kasutaja seadistused", + "Community and user menu": "Kogukonna ja kasutaja menüü", + "Privacy": "Privaatsus", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lisa ( ͡° ͜ʖ ͡°) smaili vormindamata sõnumi algusesse" } diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 42f9d74502..42eead2057 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2377,5 +2377,23 @@ "%(brand)s Web": "%(brand)s Web", "%(brand)s Desktop": "%(brand)s Desktop", "%(brand)s iOS": "%(brand)s iOS", - "%(brand)s X for Android": "%(brand)s X pour Android" + "%(brand)s X for Android": "%(brand)s X pour Android", + "Are you sure you want to cancel entering passphrase?": "Souhaitez-vous vraiment annuler l'entrée de la phrase de passe ?", + "Unexpected server error trying to leave the room": "Erreur de serveur inattendue en essayant de quitter le salon", + "Error leaving room": "Erreur en essayant de quitter le salon", + "The person who invited you already left the room.": "La personne vous ayant invité a déjà quitté le salon.", + "The person who invited you already left the room, or their server is offline.": "La personne vous ayant invité a déjà quitté le salon, ou son serveur est hors-ligne.", + "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", + "Change notification settings": "Modifier les paramètres de notification", + "Show message previews for reactions in DMs": "Afficher la prévisualisation des messages pour les réactions dans les messages privés", + "Show message previews for reactions in all rooms": "Afficher la prévisualisation des messages pour les réactions dans tous les salons", + "Enable advanced debugging for the room list": "Activer le débogage avancé pour la liste de salons", + "Uploading logs": "Téléversement des journaux", + "Downloading logs": "Téléchargement des journaux", + "Your server isn't responding to some requests.": "Votre serveur ne répond pas à certaines requêtes.", + "Cross-signing and secret storage are ready for use.": "La signature croisée et le coffre secret sont prêt à l'emploi.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "La signature croisée est prête à l'emploi, mais le coffre secret n'est pas actuellement utilisé pour sauvegarder vos clés.", + "Master private key:": "Clé privée maîtresse :", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s ne peut actuellement mettre en cache vos messages chiffrés localement de manière sécurisée via le navigateur Web. Utilisez %(brand)s Desktop pour que les messages chiffrés apparaissent dans vos résultats de recherche.", + "There are advanced notifications which are not shown here.": "Des notifications avancées ne sont pas affichées ici." } diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 0f2c83fd55..b8dcc48b68 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -1884,7 +1884,7 @@ "Message layout": "Disposición da mensaxe", "Compact": "Compacta", "Modern": "Moderna", - "Power level": "Poderío", + "Power level": "Nivel de permisos", "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifica este dispositivo para marcalo como confiable. Confiando neste dispositivo permite que ti e outras usuarias estedes máis tranquilas ao utilizar mensaxes cifradas.", "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Ao verificar este dispositivo marcaralo como confiable, e as usuarias que confiaron en ti tamén confiarán nel.", "Waiting for partner to confirm...": "Agardando a que o compañeiro confirme...", @@ -2425,5 +2425,38 @@ "Error leaving room": "Erro ó saír da sala", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipos de Comunidades v2. Require un servidor compatible. Característica experimental - usa con tino.", "Explore rooms in %(communityName)s": "Explorar salas en %(communityName)s", - "Set up Secure Backup": "Configurar Copia de apoio Segura" + "Set up Secure Backup": "Configurar Copia de apoio Segura", + "Cross-signing and secret storage are ready for use.": "A Sinatura-Cruzada e o almacenaxe segredo están listos para usar.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "A Sinatura-Cruzada está preparada para usala, mais o almacenaxe segredo aínda non foi usado para facer copia das chaves.", + "Explore community rooms": "Explorar salas da comunidade", + "Information": "Información", + "Add another email": "Engadir outro email", + "People you know on %(brand)s": "Persoas que coñeces en %(brand)s", + "Send %(count)s invites|other": "Enviar %(count)s convites", + "Send %(count)s invites|one": "Enviar %(count)s convite", + "Invite people to join %(communityName)s": "Convida a persoas a unirse a %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Algo fallou ó crear a túa comunidade. O nome podería estar pillado ou o servidor non pode procesar a túa solicitude.", + "Community ID: +:%(domain)s": "ID da comunidade: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Usa esto cando queiras falar sobre a túa comunidade. O ID da comunidade non se pode cambiar.", + "You can change this later if needed.": "Podes cambiar esto máis tarde se o precisas.", + "What's the name of your community or team?": "¿Cómo se chama a túa comunidade ou equipo?", + "Enter name": "Escribe o nome", + "Add image (optional)": "Engade unha imaxe (optativo)", + "An image will help people identify your community.": "Unha imaxe axudaralle á xente a identificar a túa comunidade.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "As salas privadas só se poden atopar e unirse por convite. As salas públicas son accesibles para calquera.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "As salas privadas só poden ser atopadas e unirse por convite. As salas públicas son accesibles para calquera nesta comunidade.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Pode resultar útil se a sala vai ser utilizada só polo equipo de xestión interna do servidor. Non se pode cambiar máis tarde.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Poderías desactivalo se a sala vai ser utilizada para colaborar con equipos externos que teñen o seu propio servidor. Esto non se pode cambiar máis tarde.", + "Create a room in %(communityName)s": "Crear unha sala en %(communityName)s", + "Block anyone not part of %(serverName)s from ever joining this room.": "Evitar que calquera externo a %(serverName)s se poida unir a esta sala.", + "Create community": "Crear comunidade", + "Privacy": "Privacidade", + "There was an error updating your community. The server is unable to process your request.": "Algo fallou ó actualizar a comunidade. O servidor non é quen de procesar a solicitude.", + "Update community": "Actualizar comunidade", + "May include members not in %(communityName)s": "Podería incluir membros que non están en %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Iniciar unha conversa con alguén utilizando o seu nome, nome de usuaria (como ) ou enderezo de email. Esto non as convidará a %(communityName)s. Para convidar a alguén a %(communityName)s, preme aquí.", + "Failed to find the general chat for this community": "Non se atopou o chat xenérico para esta comunidade", + "Community settings": "Axustes da comunidade", + "User settings": "Axustes de usuaria", + "Community and user menu": "Menú de usuaria e comunidade" } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 71dab37a55..a9e209c169 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -483,7 +483,7 @@ "Community ID": "Közösség azonosító", "Add rooms to the community summary": "Szobák hozzáadása a közösségi összefoglalóhoz", "Add users to the community summary": "Felhasználók hozzáadása a közösségi összefoglalóhoz", - "Failed to update community": "Közösség frissítése sikertelen", + "Failed to update community": "Közösség módosítása sikertelen", "Leave Community": "Közösség elhagyása", "Add rooms to this community": "Szobák hozzáadása ehhez a közösséghez", "%(inviter)s has invited you to join this community": "%(inviter)s meghívott ebbe a közösségbe", @@ -2413,5 +2413,49 @@ "You’re all caught up": "Mindent elolvastál", "You have no visible notifications in this room.": "Nincsenek látható értesítéseid ebben a szobában.", "%(brand)s Android": "%(brand)s Android", - "Explore public rooms": "Nyilvános szobák felderítése" + "Explore public rooms": "Nyilvános szobák felderítése", + "Unexpected server error trying to leave the room": "Váratlan szerver hiba lépett fel a szobából való kilépés közben", + "Error leaving room": "A szoba elhagyásakor hiba történt", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Közösségek v2 prototípus. Kompatibilis matrix szervert igényel. Erősen kísérleti állapotban van - körültekintően használd.", + "Uploading logs": "Napló feltöltése folyamatban", + "Downloading logs": "Napló letöltése folyamatban", + "Can't see what you’re looking for?": "Nem találod amit keresel?", + "Explore all public rooms": "Fedezd fel a nyilvános szobákat", + "%(count)s results|other": "%(count)s találat", + "Information": "Információ", + "Preparing to download logs": "Napló előkészítése feltöltéshez", + "Download logs": "Napló letöltése", + "Add another email": "Másik e-mail hozzáadása", + "People you know on %(brand)s": "Akiket ismerhetsz itt: %(brand)s", + "Send %(count)s invites|other": "%(count)s meghívó küldése", + "Send %(count)s invites|one": "%(count)s meghívó küldése", + "Invite people to join %(communityName)s": "Hívj meg embereket ide: %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "A közösség létrehozásánál hiba történt. A név már foglalt vagy a szerver nem tudja feldolgozni a kérést.", + "Community ID: +:%(domain)s": "Közösség azon.: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Ha másoknál hivatkozol a közösségre ezt használd. A közösség azonosítót nem lehet megváltoztatni.", + "You can change this later if needed.": "Később megváltoztathatod ha kell.", + "What's the name of your community or team?": "Mi a közösséged vagy csoportod neve?", + "Enter name": "Név megadása", + "Add image (optional)": "Kép hozzáadása (opcionális)", + "An image will help people identify your community.": "A kép segít az embereknek a közösség azonosításában.", + "Explore rooms in %(communityName)s": "Fedezd fel a szobákat itt: %(communityName)s", + "Create community": "Közösség létrehozása", + "Set up Secure Backup": "Biztonsági mentés beállítása", + "Explore community rooms": "Fedezd fel a közösségi szobákat", + "Create a room in %(communityName)s": "Készíts szobát itt: %(communityName)s", + "Cross-signing and secret storage are ready for use.": "Az eszközök közti hitelesítés és a biztonsági tároló kész a használatra.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Az eszközök közti hitelesítés kész a használatra, de a biztonsági tároló nincs használva a kulcsok mentéséhez.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat bárki megtalálhatja és be is léphet.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat a közösség bármely tagja megtalálhatja és be is léphet.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Beállíthatod, ha a szobát csak egy belső csoport használja majd a matrix szervereden. Ezt később nem lehet megváltoztatni.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Ne engedélyezd ezt, ha a szobát külső csapat is használja másik matrix szerverről. Később nem lehet megváltoztatni.", + "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába való belépés megtagadása mindenkitől ki nem ezt a matrix szervert használja: %(serverName)s.", + "There was an error updating your community. The server is unable to process your request.": "A közösség módosításakor hiba történt. A szerver nem tudja feldolgozni a kérést.", + "Update community": "Közösség módosítása", + "May include members not in %(communityName)s": "Olyan tagok is lehetnek akik nincsenek ebben a közösségben: %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Kezdj beszélgetést valakivel akár a neve, felhasználói neve (mint ) vagy az e-mail címe segítségével. %(communityName)s közösségbe nem lesznek meghívva. Ha valakit meg szeretnél hívni ide: %(communityName)s, kattints ide.", + "Failed to find the general chat for this community": "Ehhez a közösséghez nem található általános csevegés", + "Community settings": "Közösségi beállítások", + "User settings": "Felhasználói beállítások", + "Community and user menu": "Közösségi és felhasználói menü" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 8cea189ff6..48e2b2df20 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2423,5 +2423,34 @@ "Explore all public rooms": "Esplora tutte le stanze pubbliche", "%(count)s results|other": "%(count)s risultati", "Preparing to download logs": "Preparazione al download dei log", - "Download logs": "Scarica i log" + "Download logs": "Scarica i log", + "Unexpected server error trying to leave the room": "Errore inaspettato del server tentando di abbandonare la stanza", + "Error leaving room": "Errore uscendo dalla stanza", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipi di comunità v2. Richiede un homeserver compatibile. Altamente sperimentale - usa con attenzione.", + "Cross-signing and secret storage are ready for use.": "La firma incrociata e l'archivio segreto sono pronti all'uso.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "La firma incrociata è pronta all'uso, ma l'archivio segreto attualmente non è usato per fare il backup delle tue chiavi.", + "Explore community rooms": "Esplora stanze della comunità", + "Information": "Informazione", + "Add another email": "Aggiungi un'altra email", + "People you know on %(brand)s": "Persone che conosci su %(brand)s", + "Send %(count)s invites|other": "Manda %(count)s inviti", + "Send %(count)s invites|one": "Manda %(count)s invito", + "Invite people to join %(communityName)s": "Invita persone ad unirsi a %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Si è verificato un errore nella creazione della comunità. Il nome potrebbe essere già in uso o il server non riesce ad elaborare la richiesta.", + "Community ID: +:%(domain)s": "ID comunità: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Usalo quando ti riferisci alla comunità con gli altri. L'ID della comunità non può essere cambiato.", + "You can change this later if needed.": "Puoi cambiarlo più tardi se necessario.", + "What's the name of your community or team?": "Qual è il nome della tua comunità o squadra?", + "Enter name": "Inserisci nome", + "Add image (optional)": "Aggiungi immagine (facoltativo)", + "An image will help people identify your community.": "Un'immagine aiuterà le persone ad identificare la tua comunità.", + "Create a room in %(communityName)s": "Crea una stanza in %(communityName)s", + "Explore rooms in %(communityName)s": "Esplora le stanze in %(communityName)s", + "Create community": "Crea comunità", + "Set up Secure Backup": "Imposta il Backup Sicuro", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Le stanze private possono essere trovate e visitate solo con invito. Le stanze pubbliche invece sono aperte a tutti.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Le stanze private possono essere trovate e visitate solo con invito. Le stanze pubbliche invece sono aperte a tutti i membri di questa comunità.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Dovresti attivarlo se questa stanza verrà usata solo per collaborazioni tra squadre interne nel tuo homeserver. Non può essere cambiato in seguito.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Dovresti disattivarlo se questa stanza verrà usata per collaborazioni con squadre esterne che hanno il loro homeserver. Non può essere cambiato in seguito.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Blocca l'accesso alla stanza per chiunque non faccia parte di %(serverName)s." } diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index 9af55c0793..f5efc1bb87 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -480,7 +480,7 @@ "Compact": "Akussem", "Modern": "Atrar", "Online": "Srid", - "Mention": "Abder", + "Mention": "Abdar", "Verify session": "Asenqed n tɣimit", "Message edits": "Tiẓrigin n yizen", "Your account is not secure": "Amiḍan-ik·im d araɣelsan", @@ -649,7 +649,7 @@ "Connecting to integration manager...": "Tuqqna ɣer umsefrak n useddu...", "Cannot connect to integration manager": "Ur nessaweḍ ara ad neqqen ɣer umsefrak n useddu", "Delete Backup": "Kkes aḥraz", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan yettwawgelhen ttuḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč d unermas (inermasen) i yesεan tisura akken ad ɣren iznan-a.", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan yettwawgelhen ttuḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč d uɣerwaḍ (yiɣerwaḍen) i yesεan tisura akken ad ɣren iznan-a.", "This session is backing up your keys. ": "Tiɣimit tḥerrez tisura-inek·inem. ", "Connect this session to Key Backup": "Qqen tiɣimit-a ɣer uḥraz n tsarut", "Server Name": "Isem n uqeddac", @@ -1592,7 +1592,7 @@ "Your homeserver has exceeded one of its resource limits.": "Aqeddac-inek·inem agejdan iɛedda yiwet seg tlisa-ines tiɣbula.", "Contact your server admin.": "Nermes anedbal-inek·inem n uqeddac.", "To return to your account in future you need to set a password": "Akken ad tuɣaleḍ ɣer umiḍan-ik·im ɣer sdat tesriḍ ad tesbaduḍ awal uffir", - "New spinner design": "Afeṣṣel amaynut n usezzay ", + "New spinner design": "Afeṣṣel amaynut n tuzzya", "Render simple counters in room header": "Err amsiḍen afessa ɣef uqerru n texxamt", "Multiple integration managers": "Imsefrak n waṭas n yimsidaf", "Try out new ways to ignore people (experimental)": "Ɛreḍ iberdan-nniḍen i tigtin n yimdanen (armitan)", @@ -1876,12 +1876,12 @@ "Send read receipts for messages (requires compatible homeserver to disable)": "Azen inagan n tɣuri i yiznan (yesra aqeddac agejdan yemṣadan i wakken ad yens)", "Show previews/thumbnails for images": "Sken tiskanin/tinfulin i tugniwin", "How fast should messages be downloaded.": "Acḥal i ilaq ad yili urured i wakken ad d-adren yiznan.", - "Enable experimental, compact IRC style layout": "Rmed aseflu n wanaw n IRC armitan, ussid ", - "Waiting for %(displayName)s to verify…": "Aṛaǧu n %(displayName)s i usenqed...", + "Enable experimental, compact IRC style layout": "Rmed aseflu n uɣanib n IRC armitan, ussid", + "Waiting for %(displayName)s to verify…": "Aṛaǧu n %(displayName)s i usenqed…", "Securely cache encrypted messages locally for them to appear in search results, using ": "Ḥrez iznan iwgelhanen idiganen s wudem awurman i wakken ad d-banen deg yigmaḍ n unadi, s useqdec ", "Securely cache encrypted messages locally for them to appear in search results.": "Ḥrez iznan iwgelhanen idiganen s wudem awurman i wakken ad d-banen deg yigmaḍ n unadi.", "The integration manager is offline or it cannot reach your homeserver.": "Amsefrak n umsidef ha-t-an beṛṛa n tuqqna neɣ ur yezmir ara ad yaweḍ ɣer uqeddac-ik·im agejdan.", - "This backup is trusted because it has been restored on this session": "Aḥraz yettwaḍman acku yuɣal-d seg tɣimit-a ", + "This backup is trusted because it has been restored on this session": "Aḥraz yettwaḍman acku yuɣal-d seg tɣimit-a", "Back up your keys before signing out to avoid losing them.": "Ḥrez tisura-ik·im send tuffɣa i wakken ur ttruḥunt ara.", "Error saving email notification preferences": "Tuccḍa deg usekles n yismenyaf n ulɣu n yimayl", "An error occurred whilst saving your email notification preferences.": "Tella-d tuccḍa lawan n usekles n yismenyaf n ulɣu n yimayl.", @@ -1901,7 +1901,7 @@ "This session, or the other session": "Tiɣimita, neɣ tiɣimit tayeḍ", "If you didn’t sign in to this session, your account may be compromised.": "Ma yella ur teqqineḍ ara ɣer tɣimit-a, amiḍan-ik·im yezmer ad yettwaker.", "Please fill why you're reporting.": "Ttxil-k·m ini-aɣ-d ayɣer i d-tettazneḍ alɣu.", - "Report Content to Your Homeserver Administrator": "Ttxil-k·m azen aneqqis i unedbal-ik·im n usebter agejdan.", + "Report Content to Your Homeserver Administrator": "Ttxil-k·m azen aneqqis i unedbal-ik·im n usebter agejdan", "Wrong Recovery Key": "Mačči d tasarut-ik·im n uɛeddi tagi", "Invalid Recovery Key": "Tasarut-ik·im n uɛeddi d tarameɣtut", "Security Phrase": "Tafyirt n tɣellist", @@ -1911,7 +1911,7 @@ "Unable to load backup status": "Yegguma ad d-yali waddad n uḥraz", "Recovery key mismatch": "Tasarut n tririt ur temṣada ara", "No backup found!": "Ulac aḥraz yettwafen!", - "Failed to decrypt %(failedCount)s sessions!": "Awgelhen n tɣimiyin %(failedCount)s ur yeddi ara", + "Failed to decrypt %(failedCount)s sessions!": "Tukksa n uwgelhen n tɣimiyin %(failedCount)s ur yeddi ara!", "Successfully restored %(sessionCount)s keys": "Tiririt n tsura n %(sessionCount)s yedda akken iwata", "This looks like a valid recovery key!": "Ayagi yettban am wakken tasarut n tririt d tameɣtut!", "Not a valid recovery key": "Tasarut n tririt mačči d tameɣtut", @@ -1928,7 +1928,7 @@ "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Tzemreḍ ad tesqedceḍ tixtiṛiyin n uqeddac udmawan i wakken ad teqqneḍ ɣer iqeddacen-nniḍen n Matrix s ufran n URL n uqeddac agejdan yemgaraden. Ayagi ad ak-yeǧǧ ad tesqedceḍ %(brand)s s umiḍan n Matrix yellan ɣef uqeddac agejdan yemgaraden.", "Confirm your identity by entering your account password below.": "Sentem timagit-ik·im s usekcem n wawal uffir n umiḍan-ik·im ddaw.", "Please review and accept all of the homeserver's policies": "Ttxil-k·m senqed syen qbel tisertiyin akk n uqeddac agejdan", - "Please review and accept the policies of this homeserver:": "Ttxil-k·m senqed syen qbel tisertiyin n uqeddac-a agejdan", + "Please review and accept the policies of this homeserver:": "Ttxil-k·m senqed syen qbel tisertiyin n uqeddac-a agejdan:", "An email has been sent to %(emailAddress)s": "Yettwazen yimayl ɣer %(emailAddress)s", "Token incorrect": "Ajuṭu d arameɣtu", "Identity Server URL": "URL n uqeddac n timagit", @@ -2003,12 +2003,12 @@ "Read Marker off-screen lifetime (ms)": "Ɣer tanzagt n tudert n tecreḍt beṛṛa n ugdil (ms)", "Unignore": "Ur yettwazgel ara", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Anedbal-ik·im n uqeddac issens awgelhen seg yixef ɣer yixef s wudem amezwer deg texxamin tusligin & yiznan usriden.", - "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Sefrek ismawen syen ffeɣ seg tɣimiyin-ik·im ddaw neɣ senqed-itent deg umaɣnu-ik·im n useqdac.", + "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Sefrek ismawen syen ffeɣ seg tɣimiyin-ik·im ddaw neɣ senqed-itent deg umaɣnu-ik·im n useqdac.", "A session's public name is visible to people you communicate with": "Isem n tiɣimit tazayezt yettban i yimdanen wukud tettmeslayeḍ", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s ileqqeḍ tasleḍt tudrigt i wakken ad aɣ-iɛawen ad nesnerni asnas.", "You have ignored this user, so their message is hidden. Show anyways.": "Tzegleḍ useqdac-a, ihi iznan-ines ffren. Ɣas akken sken-iten-id.", "You cancelled verifying %(name)s": "Tesfesxeḍ asenqed n %(name)s", - "Declining …": "Tigtin...", + "Declining …": "Tigtin…", "You sent a verification request": "Tuzneḍ asuter n usenqed", "Error decrypting video": "Tuccḍa deg uwgelhen n tvidyut", "Reactions": "Tisedmirin", @@ -2074,5 +2074,341 @@ "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "War aswel n Secure Message Recovery, ad tmedleḍ amazray-ik·im n yiznan uffiren ma yella teffɣeḍ.", "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ma yella ur tesbaduḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren.", "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Tiɣimit-a tufa-d tafyirt-ik·im tuffirt n tririt d tsarut-ik·im n yiznan uffiren ttwakksent.", - "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ma yella ur tekkiseḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren." + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ma yella ur tekkiseḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren.", + "Mirror local video feed": "Asbani n usuddem n tvidyut tadigant", + "Low bandwidth mode": "Askar n tehri n tesfift adday", + "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Sireg aqeddac n tallalt i yisawalen n ufran aneggaru turn.matrix.org ma yili aqeddac-ik·im agejdan ur d-yettmudd ara yiwen (tansa-ik·im n IP ad tettwabḍu lawan n usiwel)", + "Compare a unique set of emoji if you don't have a camera on either device": "Serwes tagrumma n yimujiten asufen ma yella ur tesɛiḍ ara takamiṛat ɣef yiwen seg sin yibenkan", + "Unable to find a supported verification method.": "D awezɣi ad d-naf tarrayt n usenqed yettusefraken.", + "Waiting for your other session, %(deviceName)s (%(deviceId)s), to verify…": "Deg uṛaǧu n tɣimit-ik·im tayeḍ, %(deviceName)s (%(deviceId)s), i usenqed…", + "To be secure, do this in person or use a trusted way to communicate.": "I wakken ad tḍemneḍ taɣellistik·im, eg ayagi s timmad-ik·im neɣ seqdec abrid n teywalt iɣef ara tettekleḍ.", + "You may need to manually permit %(brand)s to access your microphone/webcam": "Ilaq-ak·am ahat ad tesirgeḍ s ufus %(brand)s i unekcum ɣer usawaḍ/webcam", + "This room is not accessible by remote Matrix servers": "Anekcum er texxamt-a ulamek s yiqeddacen n Matrix inmeggagen", + "No users have specific privileges in this room": "Ulac aqeddac yesan taseglut tuzzigtt deg texxamt-a", + "Select the roles required to change various parts of the room": "Fren timlilin yettusran i usnifel n yiḥricen yemgaraden n texxamt", + "Guests cannot join this room even if explicitly invited.": "Ur zmiren ara inebgawen ad d-rnun ɣer texxamt-a alamma ttusnubegten-d s tidet.", + "Once enabled, encryption cannot be disabled.": "Akken ara yettwarmad, awgelhen ur yettizmir ara ad yens.", + "Click the link in the email you received to verify and then click continue again.": "Sit ɣef useɣwen yella deg yimayl i teṭṭfeḍ i usenqed syen sit tikkelt tayeḍ ad tkemmleḍ.", + "Discovery options will appear once you have added an email above.": "Tixtiṛiyin n usnirem ad d-banent akken ara ternuḍ imayl s ufella.", + "Discovery options will appear once you have added a phone number above.": "Tixtiṛiyin n usnirem ad d-banent akken ara ternuḍ uṭṭun n tilifun s ufella.", + "The maximum permitted number of widgets have already been added to this room.": "Amḍan afellay yettusirgen n yiwiǧiten yettwarna yakan ɣer texxamt-a.", + "This room doesn't exist. Are you sure you're at the right place?": "Taxxamt-a ulac-itt. Tetteḥqeḍ aql-ak·akem deg wadeg i iṣeḥḥan?", + "Try again later, or ask a room admin to check if you have access.": "Ɛreḍ tikkelt-nniḍen ticki, neɣ suter deg unedbal n texxamt ad iwali ma tzemreḍ ad tkecmeḍ.", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s yuɣal-d lawan n uneɛruḍ n unekcum ɣer texxamt. Ma yella izen-a twalaḍ-t ur tebniḍ fell-as, ttxil-k·m azen aneqqis n wabug.", + "Never lose encrypted messages": "Ur ttamdal ara akk iznan iwgelhanen", + "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan deg texxamt-a ttwaḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč·kemm d uɣerwaḍ (yiɣerwaḍen) i yesεan tisura akken ad ɣren iznan-a.", + "Securely back up your keys to avoid losing them. Learn more.": "Ḥrez tisura-k·m s wudem aɣelsan i wakken ur ak·am-ttṛuḥunt ara. Issin ugar", + "Unrecognised command: %(commandText)s": "Taladna d tarussint: %(commandText)s", + "Hint: Begin your message with // to start it with a slash.": "Taxballut: Bdu izen-ik·im s // i wakken ad t-tebduḍ s uṣlac.", + "Failed to connect to integration manager": "Tuqqna ɣer umsefrak n umsidef ur yeddi ara", + "Jump to first unread message.": "Ɛeddi ɣer yizen amezwaru ur nettwaɣra ara.", + "Error updating main address": "Tuccḍa deg usali n tensa tagejdant", + "You don't have permission to delete the address.": "Ur tesɛiḍ ara tisirag i wakken ad tekkseḍ tansa.", + "This room has no local addresses": "Taxxamt-a ur tesɛi ara tansiwin tidiganin", + "Error updating flair": "Tuccḍa deg uleqqem n lbenna", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' mačči d asulay n temɣiwent ameɣtu", + "Use bots, bridges, widgets and sticker packs": "Seqdec abuten, tileggiyin, iwiǧiten d tɣawsiwin n umyintaḍ", + "To continue you need to accept the terms of this service.": "I wakken ad tkemmleḍ tesriḍ ad tqebleḍ tiwtilin n umeẓlu-a.", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Afaylu-a ɣezzif aṭas i wakken ad d-yali. Talast n teɣzi n ufaylu d %(limit)s maca afaylu-a d %(sizeOfThisFile)s.", + "These files are too large to upload. The file size limit is %(limit)s.": "Ifuyla-a ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Kra n yifuyla ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", + "Upload %(count)s other files|other": "Sali-d %(count)s ifuyla-nniḍen", + "Upload %(count)s other files|one": "Sali-d %(count)s afaylu-nniḍen", + "Upload Error": "Tuccḍa deg usali", + "A widget would like to verify your identity": "Awiǧit yebɣa ad issenqed timagit-inek·inem", + "Remember my selection for this widget": "Cfu ɣef tefrant-inu i uwiǧit-a", + "Wrong file type": "Anaw n yifuyla d arameɣtu", + "Looks good!": "Yettban igerrez!", + "Enter your Security Phrase or to continue.": "Sekcem tafyirt-ik·im n tɣellist neɣ ": "Ma yella tettuḍ tasarut-ik·im n uɛeddi, tzemreḍ ", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Wennez kullec neɣ sefsex kullec tura. Tzemreḍ daɣen ad tferneḍ iznan udmawanen i uwennez neɣ i usefsex.", + "Tried to load a specific point in this room's timeline, but was unable to find it.": "Ɛerḍeɣ ad d-saliɣ tazmilt tufrint tesnakudt n texxamt-a, maca ur ssawḍeɣ ara ad t-naf.", + "A verification email will be sent to your inbox to confirm setting your new password.": "Imayl n usenqed ad yettwazen ɣer tbewwaḍt-ik·im n yimayl i usentem n yiɣewwaren n wawal-ik·im uffir.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kcem ɣer umazray aɣelsan n yiznan-inek·inem syen sbadu tirawt taɣelsant s usekcem n tefyirt tuffirt n uɛeddi.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kcem ɣer umazray aɣelsan n yiznan-ik·im syen sbadu tirawt taɣelsant s usekcem n tsarut n uɛeddi.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Txuṣṣ tsarut tazayezt n captcha deg umtawi n uqeddac agejdan. Ttxil-k·m azen aneqqis ɣef waya i unedbal n uqeddac-ik·im agejdan.", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Sekcem adeg n uqeddac-ik·im agejdan n umeẓlu n Element Matrix. Yezmer ad iseqdec isem n taɣult-ik·im uzzig neɣ ad yili d taɣult tarnawt n element.io.", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ulac aqeddac n timagit yettusiwlen, ɣef waya ur tettizmireḍ ara ad ternuḍ tansa n yimayl i wakken ad twennzeḍ awal-ik·im uffir ɣer sdat.", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Ma yella ur d-tefrineḍ ara tansa n yimayl, ur tettizmireḍ ara ad twennzeḍ awal-ik·im uffir. Tebɣiḍ?", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Sbadu imayl i tririt n umiḍan. Seqdec imayl neɣ tiliɣri i wakken ad tettwaf s uxtiṛi sɣur inermisen i yellan.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Sbadu imayl i tririt n umiḍan. Seqdec imayl s ufran i wakken ad d-iban i yinermisen i yellan.", + "%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s isseqdac aṭas n tmahilin leqqayen n yiminig, kra seg-sent ulac-itent neɣ d tirmitanin deg yiminig-ik·im amiran.", + "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "S yiminig-ik·im amiran, ameẓraw d umḥulfan n usnas zemren ad ilin mačči akk d imeɣta, rnu kra neɣ akk timahilin zemrent ur teddunt ara. Ma yella tebɣiḍ ɣas akken ad t-tɛerḍeḍ tzemreḍ ad tkemmleḍ, maca ur tseɛɛuḍ ara akk tallalt ma yella temlaleḍ-d d wuguren!", + "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "Aql-ak·akem d (t)anedbal(t) n temɣiwent-a. Ur tettizmireḍ ara ad tɛawdeḍ anekcum alamma s tinubga n unedbal-nniḍen.", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Isnifal i d-yellan ɣef isem d avaṭar i temɣiwent-ik·im ur ttmeẓran ara sɣur yiseqdacen-nniḍen alamma d 30 tesdidin .", + "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Tixxamin-a ttwaskanent i yiɛeggalen n temɣiwent ɣef usebter n temɣiwent. Iɛeggalen n temɣiwent zemren ad ttekkin deg texxamin s usiti fell-asent.", + "Your community hasn't got a Long Description, a HTML page to show to community members.
    Click here to open settings and give it one!": "Tamɣiwent-ik·im ur tesɛi ara aglam ɣezzifen, asebter HTML i uskan n yiɛeggalen n temɣiwent.
    Sit da i wakken ad teldiḍ iɣewwaren syen rnu yiwet!", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Tixxamin tusligin zemrent ad ttwafent, timerna ɣer-sent s tinubga kan. Tixxamin tizuyaz zemrent ad ttwafent, yal wa yezmer ad yernu ɣer-sent.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Tixxamin tusligin zemrent ad ttwafent, timerna ɣer-sent s tinubga kan. Tixxamin tizuyaz zemrent ad ttwafent, yal wa yezmer ad yernu ɣer-sent deg temɣiwent-a.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Ilaq ad tesremdeḍ aya ma yella taxxamt ad tettwaseqdec kan i uttekki d trebbaɛ tigensanin ɣef uqeddac-ik·im agejdan. Ayagi ur yettubeddal ara ɣer sdat.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Ilaq ad tsenseḍ aya ma yella taxxamt ad tettuseqdac i uttekki d trebbaɛ tuffiɣin i yesɛan aqeddac-nsent agejdan. Aya ur yettwabeddal ara ɣer sdat.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Asewḥel n yal amdan ur nettekki ara deg %(serverName)s ur d-irennu ara akk ɣer texamt-a.", + "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Timenna ɣef yizen-a ad yazen \"asulay n uneḍru\" asuf i unedbal n uqeddac agejdan. Ma yella iznan n texxamt-a ttwawgelhen, anedbal-ik·im n uqeddac agejdan ur yettizmir ara ad d-iɣer aḍris n yizen neɣ senqed ifuyla neɣ tugniwin.", + "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "I wakken ad tkemmleḍ aseqdec n uqeddac agejdan n %(homeserverDomain)s ilaq ad talseḍ asenqed syen ad tqebleḍ tiwtilin-nneɣ s umata.", + "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Isefka n lqem aqbur n %(brand)s ttwafen. Ayagi ad d-yeglu s yir tamahalt n uwgelhen seg yixef ɣer yixef deg lqem aqbur. Iznan yettwawgelhen seg yixef ɣer yixef yettumbeddalen yakan mi akken yettuseqdac lqem aqbur yezmer ur asen-ittekkes ara uwgelhen deg lqem-a. Aya yezmer daɣen ad d-yeglu asefsex n yiznan yettumbeddalen s lqem-a. Ma yella temlaleḍ-d uguren, ffeɣ syen tuɣaleḍ-d tikkelt-nniḍen. I wakken ad tḥerzeḍ amazray n yiznan, sifeḍ rnu ales kter tisura-ik·im.", + "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "I usbadu n umsizdeg, jbed-d avaṭar n temɣiwent ɣer ugalis n umsizdeg deg tama tazelmaḍt akk n ugdil. Tzemreḍ ad tsiteḍ ɣef avaṭar deg ugalis n yimsizdeg deg yal akud i wakken ad twaliḍ tixxamin kan d yimdanen yettwaqqnen d temɣiwent-a.", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Rnu tamɣiwent i wakken ad d-tesdukkleḍ akk iseqdac d texxamin! Snulfu-d asebter agejdan i ucraḍ n tallunt-ik·im deg umaḍal n Matrix.", + "You can't send any messages until you review and agree to our terms and conditions.": "Ur tezmireḍ ara ad tazneḍ iznan alamma tesneqdeḍ syen ad tqebleḍ tiwtilin-nneɣ.", + "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Izen-ik·im ur yettwazen ara acku aqeddac-a agejdan iɛedda talast n useqdac urmid n wayyur. Ttxil-k·m nermes anedbal-ik·im n umeẓlu i wakken ad tkemmleḍ aseqdec ameẓlu.", + "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Izen-ik·im ur yettwazen ara acku aqeddac-a agejdan iɛedda talast n yiɣbula. Ttxil-k·m nermes anedbal-ik·im n umeẓlu i wakken ad tkemmleḍ aseqdec ameẓlu.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Ales tuzna n yizen neɣ sefsex izen tura.", + "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tɛerḍeḍ ad d-tsaliḍ tazmilt tufrint deg tesnakudt n teamt, maca ur tesɛiḍ ara tisirag ad d-tsekneḍ izen i d-teɛniḍ.", + "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Asnifel n wawal-ik·im uffir ad iwennez akk tisura n uwgelhen seg yixef ɣer yixef deg meṛṛa n tɣimiyin-ik·im, s tririt n umazray n udiwenni awgelhan ur yettwaɣra ara. Sbadu aḥraz n tsarut neɣ sifeḍ tisura n texxamt-ik·im seg texxamt-nniḍen send awennez n wawal-ik·im uffir.", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Imayl yettwazen ɣer %(emailAddress)s. Akken ara tḍefreḍ aseɣwen i yellan deg-s, sit ddaw.", + "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Aql-ak·akem teffɣeḍ seg meṛṛa tiɣimiyin syen ur d-teṭṭifeḍ ara akk ilɣa n Push. I wakken ad talseḍ armad n yilɣa, kcem tikkelt-nniḍen ɣef yal ibenk.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Ur tessawḍeḍ ara ad teqqneḍ ɣer uqeddac agejdan s HTTP mi ara yili URL n HTTPS deg ufeggag n yiminig-ik·im. Seqdec HTTPS neɣ sermed isekripten ariɣelsanen.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Yegguma ad yeqqen ɣer uqeddac agejdan - ttxil-k·m senqed tuqqna-inek·inem, tḍemneḍ belli aselken n SSL n uqeddac agejdan yettwattkal, rnu aseɣzan n yiminig-nni ur issewḥal ara isutar.", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Amiḍan-ik·im amaynut (%(newAccountId)s) yettwaseklas, maca teqqneḍ yakan ɣer umiḍan wayeḍ (%(loggedInUserId)s).", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Tasarut-ik·im n tririt d azeṭṭa aɣelsan - tzemreḍ ad tt-tesqedceḍ i wakken ad d-terreḍ anekcum ɣer yiznan-ik·im yettwawgelhen ma yella tettuḍ tafyirt-ik·im tuffirt n tririt.", + "Trophy": "Arraz" } diff --git a/src/i18n/strings/nb_NO.json b/src/i18n/strings/nb_NO.json index 1f6720f613..f8f0d890be 100644 --- a/src/i18n/strings/nb_NO.json +++ b/src/i18n/strings/nb_NO.json @@ -1416,5 +1416,30 @@ "To return to your account in future you need to set a password": "For å komme tilbake til kontoen din senere, må du velge et passord", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Vennligst glem alle meldingene jeg har sendt når kontoen min er deaktivert (Advarsel: Dette vil føre til at fremtidige brukere ser en ufullstendig visning av samtaler)", "To help us prevent this in future, please send us logs.": "For å hjelpe oss med å forhindre dette i fremtiden, vennligst send oss loggfiler.", - "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ingen identitetstjener er satt opp, så du kan ikke bruke en E-postadresse til å tilbakestille passordet ditt senere." + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ingen identitetstjener er satt opp, så du kan ikke bruke en E-postadresse til å tilbakestille passordet ditt senere.", + "Incoming call": "Innkommende samtale", + "Lock": "Lås", + "Compact": "Kompakt", + "Modern": "Moderne", + "Server or user ID to ignore": "Tjener- eller bruker-ID-en som skal ignoreres", + "Show %(count)s more|other": "Vis %(count) til", + "Show %(count)s more|one": "Vis %(count) til", + "Use default": "Bruk standarden", + "Notification options": "Varselsinnstillinger", + "Room options": "Rominnstillinger", + "Your messages are not secure": "Dine meldinger er ikke sikre", + "Verification cancelled": "Verifiseringen ble avbrutt", + "Edited at %(date)s": "Redigert den %(date)s", + "Click to view edits": "Klikk for å vise redigeringer", + "This widget may use cookies.": "Denne modulen bruker kanskje infokapsler.", + "Confirm account deactivation": "Bekreft deaktivering av kontoen", + "Send Account Data": "Send kontodata", + "The server is offline.": "Denne tjeneren er offline.", + "Wrong file type": "Feil filtype", + "Looks good!": "Ser bra ut!", + "Security & privacy": "Sikkerhet og personvern", + "User menu": "Brukermeny", + "Use Recovery Key": "Bruk gjenopprettingsnøkkel", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android" } diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index c16e7a980d..975281aa00 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -31,7 +31,7 @@ "Failed to unban": "Não foi possível remover o banimento", "Favourite": "Favoritar", "Favourites": "Favoritos", - "Filter room members": "Filtrar integrantes da sala", + "Filter room members": "Pesquisar participantes da sala", "Forget room": "Esquecer sala", "For security, this session has been signed out. Please sign in again.": "Por questões de segurança, esta sessão foi encerrada. Por gentileza conecte-se novamente.", "Guests cannot join this room even if explicitly invited.": "Visitantes não podem entrar nesta sala, mesmo se forem explicitamente convidadas/os.", @@ -142,8 +142,8 @@ "%(targetName)s joined the room.": "%(targetName)s entrou na sala.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s removeu %(targetName)s da sala.", "%(targetName)s left the room.": "%(targetName)s saiu da sala.", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s deixou o histórico futuro da sala visível para todos os membros da sala, a partir de quando foram convidados.", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s deixou o histórico futuro da sala visível para todos os membros da sala, a partir de quando entraram.", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s deixou o histórico futuro da sala visível para todos os participantes da sala, a partir de quando foram convidados.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s deixou o histórico futuro da sala visível para todos os participantes da sala, a partir de quando entraram.", "%(senderName)s made future room history visible to all room members.": "%(senderName)s deixou o histórico futuro da sala visível para todas as pessoas da sala.", "%(senderName)s made future room history visible to anyone.": "%(senderName)s deixou o histórico futuro da sala visível para qualquer pessoa.", "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s deixou o histórico futuro da sala visível para desconhecido (%(visibility)s).", @@ -212,7 +212,7 @@ "Failed to change power level": "Não foi possível alterar o nível de permissão", "Failed to join room": "Não foi possível ingressar na sala", "Failed to kick": "Não foi possível remover o usuário", - "Failed to load timeline position": "Não foi possível carregar a posição na linha do tempo", + "Failed to load timeline position": "Não foi possível carregar um trecho da conversa", "Failed to mute user": "Não foi possível remover notificações da/do usuária/o", "Failed to reject invite": "Não foi possível recusar o convite", "Failed to set display name": "Falha ao definir o nome e sobrenome", @@ -233,8 +233,8 @@ "%(count)s of your messages have not been sent.|other": "Algumas das suas mensagens não foram enviadas.", "Submit": "Enviar", "This room has no local addresses": "Esta sala não tem endereços locais", - "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas parece que você não tem permissões para ver a mensagem em questão.", - "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas não o encontrei.", + "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Não foi possível carregar um trecho específico da conversa desta sala, porque parece que você não tem permissão para ler a mensagem em questão.", + "Tried to load a specific point in this room's timeline, but was unable to find it.": "Não foi possível carregar um trecho específico da conversa desta sala.", "You seem to be in a call, are you sure you want to quit?": "Parece que você está em uma chamada. Tem certeza que quer sair?", "You seem to be uploading files, are you sure you want to quit?": "Parece que você está enviando arquivos. Tem certeza que quer sair?", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Você não poderá desfazer essa alteração, pois está promovendo o usuário ao mesmo nível de permissão que você.", @@ -332,7 +332,7 @@ "Add": "Adicionar", "Error: Problem communicating with the given homeserver.": "Erro: problema de comunicação com o Servidor de Base fornecido.", "Failed to fetch avatar URL": "Falha ao obter o link da foto de perfil", - "Home": "Início", + "Home": "Home", "The phone number entered looks invalid": "O número de telefone inserido parece ser inválido", "Uploading %(filename)s and %(count)s others|zero": "Enviando o arquivo %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Enviando o arquivo %(filename)s e %(count)s outros arquivos", @@ -404,7 +404,7 @@ "The platform you're on": "A plataforma que você está usando", "Your language of choice": "O idioma que você selecionou", "Which officially provided instance you are using, if any": "Qual instância oficial você está usando, se for o caso", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "Se você está usando o editor de texto visual", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Se você está usando a formatação de texto no campo de texto", "Your homeserver's URL": "O endereço do seu servidor local", "The information being sent to us to help make %(brand)s better includes:": "As informações que estão sendo enviadas para ajudar a melhorar o %(brand)s incluem:", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Quando esta página inclui informações identificáveis, como uma sala, ID de usuário ou de comunidade, estes dados são removidos antes de serem enviados ao servidor.", @@ -414,9 +414,9 @@ "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s de %(monthName)s de %(fullYear)s", "Who would you like to add to this community?": "Quem você gostaria de adicionar a esta comunidade?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Atenção: qualquer pessoa que você adicionar a esta comunidade estará publicamente visível para todas as pessoas que conheçam o ID da comunidade", - "Invite new community members": "Convidar novos integrantes para a comunidade", + "Invite new community members": "Convidar novos participantes para a comunidade", "Which rooms would you like to add to this community?": "Quais salas você quer adicionar a esta comunidade?", - "Show these rooms to non-members on the community page and room list?": "Exibir estas salas para não integrantes na página da comunidade e na lista de salas?", + "Show these rooms to non-members on the community page and room list?": "Exibir estas salas para não participantes na página da comunidade e na lista de salas?", "Unable to create widget.": "Não foi possível criar o widget.", "You are now ignoring %(userId)s": "Agora você está bloqueando %(userId)s", "Unignored user": "Usuário desbloqueado", @@ -432,7 +432,7 @@ "Mirror local video feed": "Espelhar o feed de vídeo local", "Enable inline URL previews by default": "Ativar, por padrão, a visualização de resumo de links", "Enable URL previews for this room (only affects you)": "Ativar, para esta sala, a visualização de links (só afeta você)", - "Enable URL previews by default for participants in this room": "Ativar, para todos os integrantes desta sala, a visualização de links", + "Enable URL previews by default for participants in this room": "Ativar, para todos os participantes desta sala, a visualização de links", "Cannot add any more widgets": "Não é possível adicionar novos widgets", "The maximum permitted number of widgets have already been added to this room.": "O número máximo de widgets permitidos já foi atingido nesta sala.", "Add a widget": "Adicionar um widget", @@ -452,7 +452,7 @@ "Send an encrypted reply…": "Digite sua resposta criptografada…", "Send an encrypted message…": "Digite uma mensagem criptografada…", "Jump to message": "Pular para mensagem", - "No pinned messages.": "Não há mensagens fixas.", + "No pinned messages.": "Nenhuma mensagem fixada.", "Loading...": "Carregando...", "Pinned Messages": "Mensagens fixas", "%(duration)ss": "%(duration)ss", @@ -472,9 +472,9 @@ "Community Invites": "Convites a comunidades", "Banned by %(displayName)s": "Banido por %(displayName)s", "Publish this room to the public in %(domain)s's room directory?": "Quer publicar esta sala na lista pública de salas da %(domain)s?", - "Members only (since the point in time of selecting this option)": "Apenas integrantes (a partir do momento em que esta opção for selecionada)", - "Members only (since they were invited)": "Apenas integrantes (desde que foram convidadas/os)", - "Members only (since they joined)": "Apenas integrantes (desde que entraram na sala)", + "Members only (since the point in time of selecting this option)": "Apenas participantes (a partir do momento em que esta opção for selecionada)", + "Members only (since they were invited)": "Apenas participantes (desde que foram convidadas/os)", + "Members only (since they joined)": "Apenas participantes (desde que entraram na sala)", "Invalid community ID": "ID de comunidade inválido", "'%(groupId)s' is not a valid community ID": "'%(groupId)s' não é um ID de comunidade válido", "Flair": "Ícone", @@ -500,11 +500,11 @@ "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "A visibilidade da sala '%(roomName)s' na comunidade %(groupId)s não pôde ser atualizada.", "Visibility in Room List": "Visibilidade na lista de salas", "Visible to everyone": "Visível para todos", - "Only visible to community members": "Apenas visível para integrantes da comunidade", + "Only visible to community members": "Apenas visível para participantes da comunidade", "Filter community rooms": "Filtrar salas da comunidade", "Something went wrong when trying to get your communities.": "Não foi possível carregar suas comunidades.", "Display your community flair in rooms configured to show it.": "Mostrar o ícone da sua comunidade nas salas que o permitem.", - "You're not currently a member of any communities.": "Atualmente você não é integrante de nenhuma comunidade.", + "You're not currently a member of any communities.": "No momento, você não é participante de nenhuma comunidade.", "Allow": "Permitir", "Delete Widget": "Apagar widget", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Remover um widget o remove para todas as pessoas desta sala. Tem certeza que quer remover este widget?", @@ -581,7 +581,7 @@ "example": "exemplo", "Create": "Criar", "To get started, please pick a username!": "Para começar, escolha um nome de usuário!", - "

    HTML for your community's page

    \n

    \n Use the long description to introduce new members to the community, or distribute\n some important links\n

    \n

    \n You can even use 'img' tags\n

    \n": "

    HTML para a página da sua comunidade

    \n

    \n Use a descrição longa para apresentar a comunidade para novas/os integrantes ou partilhe links importantes.\n

    \n

    \n Você pode até mesmo usar tags 'img' do HTML\n

    \n", + "

    HTML for your community's page

    \n

    \n Use the long description to introduce new members to the community, or distribute\n some important links\n

    \n

    \n You can even use 'img' tags\n

    \n": "

    HTML para a página da sua comunidade

    \n

    \n Use a descrição longa para apresentar a comunidade para novas/os participantes, ou compartilhe links importantes.\n

    \n

    \n Você pode até mesmo usar tags 'img' em HTML\n

    \n", "Add rooms to the community summary": "Adicionar salas para o índice da comunidade", "Which rooms would you like to add to this summary?": "Quais salas você gostaria de adicionar a este índice?", "Add to summary": "Adicionar ao índice", @@ -603,12 +603,12 @@ "Leave %(groupName)s?": "Quer sair da comunidade %(groupName)s?", "Leave": "Sair", "Community Settings": "Configurações da comunidade", - "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Essas salas são exibidas para os membros da comunidade na página da comunidade. Os membros da comunidade entram nas salas clicando nelas.", + "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Essas salas são exibidas para os participantes da comunidade na página da comunidade. Os paraticipantes da comunidade entram nas salas clicando nelas.", "Featured Rooms:": "Salas em destaque:", "Featured Users:": "Pessoas em destaque:", "%(inviter)s has invited you to join this community": "%(inviter)s convidou você para entrar nesta comunidade", "You are an administrator of this community": "Você é administrador(a) desta comunidade", - "You are a member of this community": "Você é um/a integrante desta comunidade", + "You are a member of this community": "Você é um/a participante desta comunidade", "Your community hasn't got a Long Description, a HTML page to show to community members.
    Click here to open settings and give it one!": "Sua comunidade não tem uma descrição longa, ou seja, uma página HTML para ser exibida às pessoas que fazem parte da comunidade.
    Clique aqui para abrir as configurações e criar uma!", "Long Description (HTML)": "Descrição longa (HTML)", "Description": "Descrição", @@ -668,7 +668,7 @@ "Resend": "Reenviar", "Error saving email notification preferences": "Erro ao salvar a configuração de notificações por e-mail", "Messages containing my display name": "Mensagens contendo meu nome e sobrenome", - "Messages in one-to-one chats": "Mensagens em conversas pessoais", + "Messages in one-to-one chats": "Mensagens em conversas individuais", "Unavailable": "Indisponível", "View Decrypted Source": "Ver código-fonte descriptografado", "Failed to update keywords": "Falha ao alterar as palavras-chave", @@ -681,7 +681,7 @@ "Source URL": "Link do código-fonte", "Messages sent by bot": "Mensagens enviadas por bots", "Filter results": "Filtrar resultados", - "Members": "Membros", + "Members": "Participantes", "No update available.": "Nenhuma atualização disponível.", "Noisy": "Ativado com som", "Files": "Arquivos", @@ -716,13 +716,13 @@ "Invite to this room": "Convidar para esta sala", "Send logs": "Enviar registros", "All messages": "Todas as mensagens novas", - "Call invitation": "Convite para chamada", + "Call invitation": "Recebendo chamada", "Downloading update...": "Baixando atualização...", "State Key": "Chave do Estado", "Failed to send custom event.": "Falha ao enviar evento personalizado.", "What's new?": "O que há de novidades?", "Notify me for anything else": "Notificar-me sobre qualquer outro evento", - "When I'm invited to a room": "Quando sou convidada(o) a uma sala", + "When I'm invited to a room": "Quando eu for convidada(o) a uma sala", "Can't update user notification settings": "Não foi possível atualizar a configuração das notificações", "Notify for all other messages/rooms": "Notificar para todas as outras mensagens e salas", "Unable to look up room ID from server": "Não foi possível buscar identificação da sala no servidor", @@ -819,8 +819,8 @@ "Enable widget screenshots on supported widgets": "Ativar capturas de tela do widget em widgets suportados", "Show developer tools": "Mostrar ferramentas de desenvolvedor", "Messages containing @room": "Mensagens contendo @room", - "Encrypted messages in one-to-one chats": "Mensagens criptografadas em bate-papos individuais", - "Encrypted messages in group chats": "Mensagens criptografadas em conversas em grupo", + "Encrypted messages in one-to-one chats": "Mensagens criptografadas em conversas individuais", + "Encrypted messages in group chats": "Mensagens criptografadas em salas", "Delete Backup": "Deletar Backup", "Unable to load key backup status": "Não é possível carregar o status da chave de backup", "Backup version: ": "Versão do Backup: ", @@ -855,7 +855,7 @@ "The email field must not be blank.": "O campo de e-mail não pode estar em branco.", "The phone number field must not be blank.": "O campo do número de telefone não pode estar em branco.", "The password field must not be blank.": "O campo da senha não pode ficar em branco.", - "Failed to load group members": "Falha ao carregar membros da comunidade", + "Failed to load group members": "Falha ao carregar participantes da comunidade", "Failed to remove widget": "Falha ao remover o widget", "An error ocurred whilst trying to remove the widget from the room": "Ocorreu um erro ao tentar remover o widget da sala", "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Não é possível carregar o evento que foi respondido, ele não existe ou você não tem permissão para visualizá-lo.", @@ -886,7 +886,7 @@ "Create a new room with the same name, description and avatar": "Criar uma nova sala com o mesmo nome, descrição e foto", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Impeça os usuários de conversarem na versão antiga da sala. Além disso, digite uma mensagem aconselhando os usuários a migrarem para a nova sala", "Put a link back to the old room at the start of the new room so people can see old messages": "Colocar um link para a sala antiga no começo da sala nova de modo que as pessoas possam ver mensagens antigas", - "You've previously used %(brand)s on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, %(brand)s needs to resync your account.": "Você já usou o %(brand)s em %(host)s com o carregamento Lazy de membros ativado. Nesta versão, o carregamento Lazy está desativado. Como o cache local não é compatível entre essas duas configurações, o %(brand)s precisa ressincronizar sua conta.", + "You've previously used %(brand)s on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, %(brand)s needs to resync your account.": "Você já usou o %(brand)s em %(host)s com o carregamento Lazy de participantes ativado. Nesta versão, o carregamento Lazy está desativado. Como o cache local não é compatível entre essas duas configurações, o %(brand)s precisa ressincronizar sua conta.", "If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Se a outra versão do %(brand)s ainda estiver aberta em outra aba, por favor, feche-a pois usar o %(brand)s no mesmo host com o carregamento Lazy ativado e desativado simultaneamente causará problemas.", "Update any local room aliases to point to the new room": "Atualize todos os aliases da sala local para apontar para a nova sala", "Clear Storage and Sign Out": "Limpar armazenamento e sair", @@ -894,12 +894,12 @@ "We encountered an error trying to restore your previous session.": "Encontramos um erro ao tentar restaurar sua sessão anterior.", "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Limpar o armazenamento do seu navegador pode resolver o problema, mas você será deslogado e isso fará que qualquer histórico de bate-papo criptografado fique ilegível.", "Checking...": "Checando...", - "Share Room": "Compartilhar Sala", - "Link to most recent message": "Link para a mensagem mais recente", + "Share Room": "Compartilhar sala", + "Link to most recent message": "Link da mensagem mais recente", "Share User": "Compartilhar usuário", "Share Community": "Compartilhar Comunidade", "Share Room Message": "Compartilhar Mensagem da Sala", - "Link to selected message": "Link para a mensagem selecionada", + "Link to selected message": "Link da mensagem selecionada", "COPY": "COPIAR", "Unable to load backup status": "Não é possível carregar o status do backup", "Unable to restore backup": "Não é possível restaurar o backup", @@ -933,7 +933,7 @@ "You can't send any messages until you review and agree to our terms and conditions.": "Você não pode enviar nenhuma mensagem até revisar e concordar com nossos termos e condições.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Sua mensagem não foi enviada porque este homeserver atingiu seu Limite de usuário ativo mensal. Por favor, entre em contato com o seu administrador de serviços para continuar usando o serviço.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Sua mensagem não foi enviada porque este servidor local excedeu o limite de recursos. Por favor, entre em contato com o seu administrador de serviços para continuar usando o serviço.", - "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Se você enviou um bug por meio do GitHub, os logs de depuração podem nos ajudar a rastrear o problema. Os logs de depuração contêm dados de uso do aplicativo, incluindo seu nome de usuário, os IDs ou apelidos das salas ou comunidades que você visitou e os nomes de usuários de outros usuários. Eles não contêm mensagens.", + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Se você enviou um bug por meio do GitHub, os registros de depuração podem nos ajudar a rastrear o problema. Os registros de depuração contêm dados de uso do aplicativo, incluindo seu nome de usuário, os IDs ou apelidos das salas ou comunidades que você visitou e os nomes de usuários de outros usuários. Eles não contêm mensagens.", "Legal": "Legal", "No Audio Outputs detected": "Nenhuma caixa de som detectada", "Audio Output": "Caixa de som", @@ -993,7 +993,7 @@ "%(names)s and %(count)s others are typing …|other": "%(names)s e %(count)s outras pessoas estão digitando…", "%(names)s and %(count)s others are typing …|one": "%(names)s e outra pessoa estão digitando…", "%(names)s and %(lastPerson)s are typing …": "%(names)s e %(lastPerson)s estão digitando…", - "Show read receipts sent by other users": "Mostrar confirmações de leitura enviadas por outros usuários", + "Show read receipts sent by other users": "Mostrar confirmações de leitura dos outros usuários", "Show avatars in user and room mentions": "Mostrar fotos de perfil em menções de usuários e de salas", "Enable big emoji in chat": "Ativar emojis grandes no bate-papo", "Send typing notifications": "Permitir que saibam quando eu estiver digitando", @@ -1005,7 +1005,7 @@ "You've successfully verified this user.": "Você confirmou este usuário com sucesso.", "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "As mensagens com este usuário estão protegidas com a criptografia de ponta a ponta e não podem ser lidas por terceiros.", "Got It": "Ok, entendi", - "Unable to find a supported verification method.": "Não há um método de confirmação suportado.", + "Unable to find a supported verification method.": "Nenhum método de confirmação é suportado.", "Dog": "Cachorro", "Cat": "Gato", "Lion": "Leão", @@ -1100,7 +1100,7 @@ "Account management": "Gerenciamento da Conta", "Deactivating your account is a permanent action - be careful!": "Desativar sua conta é uma ação permanente - tenha cuidado!", "General": "Geral", - "Credits": "Créditos", + "Credits": "Licenças", "For help with using %(brand)s, click here.": "Para obter ajuda com o uso do %(brand)s, clique aqui.", "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "Para obter ajuda com o uso do %(brand)s, clique aqui ou inicie um bate-papo com nosso bot usando o botão abaixo.", "Chat with %(brand)s Bot": "Converse com o bot do %(brand)s", @@ -1109,8 +1109,8 @@ "FAQ": "FAQ", "Versions": "Versões", "Preferences": "Preferências", - "Composer": "Compositor", - "Timeline": "Linha do Tempo", + "Composer": "Campo de texto", + "Timeline": "Conversas", "Room list": "Lista de Salas", "Autocomplete delay (ms)": "Atraso no preenchimento automático (ms)", "Ignored users": "Usuários bloqueados", @@ -1181,8 +1181,8 @@ "Messages": "Mensagens", "Actions": "Ações", "Other": "Outros", - "Sends a message as plain text, without interpreting it as markdown": "Envia uma mensagem como texto puro, sem interpretá-la como markdown", - "Sends a message as html, without interpreting it as markdown": "Envia uma mensagem como HTML, sem interpretá-la como markdown", + "Sends a message as plain text, without interpreting it as markdown": "Envia uma mensagem como texto simples, sem formatar o texto", + "Sends a message as html, without interpreting it as markdown": "Envia uma mensagem como HTML, sem formatar o texto", "You do not have the required permissions to use this command.": "Você não tem as permissões necessárias para usar este comando.", "Error upgrading room": "Erro atualizando a sala", "Double check that your server supports the room version chosen and try again.": "Verifique se seu servidor suporta a versão de sala escolhida e tente novamente.", @@ -1341,7 +1341,7 @@ "Order rooms by name": "Ordenar salas por nome", "Show rooms with unread notifications first": "Mostrar primeiro as salas com notificações não lidas", "Show shortcuts to recently viewed rooms above the room list": "Mostrar atalhos para salas recentemente visualizadas acima da lista de salas", - "Show hidden events in timeline": "Mostrar eventos ocultos na timeline", + "Show hidden events in timeline": "Mostrar eventos ocultos nas conversas", "Low bandwidth mode": "Modo de baixo uso de dados", "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Permitir a assistência do servidor de chamadas reserva turn.matrix.org quando seu servidor não oferecer este serviço (seu endereço IP será transmitido quando você ligar)", "Send read receipts for messages (requires compatible homeserver to disable)": "Enviar confirmação de leitura para mensagens (necessita um servidor compatível para desativar)", @@ -1351,7 +1351,7 @@ "Manually verify all remote sessions": "Verificar manualmente todas as sessões remotas", "IRC display name width": "Largura do nome e sobrenome nas mensagens", "Enable experimental, compact IRC style layout": "Ativar o layout compacto experimental nas mensagens", - "When rooms are upgraded": "Quando salas são atualizadas", + "When rooms are upgraded": "Quando a versão da sala é atualizada", "My Ban List": "Minha lista de banidos", "This is your list of users/servers you have blocked - don't leave the room!": "Esta é a sua lista de usuárias(os)/servidores que você bloqueou - não saia da sala!", "Unknown caller": "Pessoa desconhecida ligando", @@ -1610,7 +1610,7 @@ "If disabled, messages from encrypted rooms won't appear in search results.": "Se desativado, as mensagens de salas criptografadas não aparecerão em resultados de buscas.", "%(brand)s is securely caching encrypted messages locally for them to appear in search results:": "%(brand)s está armazenando de forma segura as mensagens criptografadas localmente, para que possam aparecer nos resultados das buscas:", "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s de %(totalRooms)s", - "Jump to start/end of the composer": "Pule para o início/fim do compositor", + "Jump to start/end of the composer": "Pule para o início/fim do campo de texto", "Click the button below to confirm adding this phone number.": "Clique no botão abaixo para confirmar a adição deste número de telefone.", "Clear notifications": "Limpar notificações", "There are advanced notifications which are not shown here.": "Existem notificações avançadas que não são mostradas aqui.", @@ -1700,7 +1700,7 @@ "Verification Requests": "Solicitações de confirmação", "Integrations are disabled": "As integrações estão desativadas", "Integrations not allowed": "As integrações não estão permitidas", - "End": "Fim", + "End": "End", "List options": "Opções da Lista", "Jump to first unread room.": "Ir para a primeira sala não lida.", "Jump to first invite.": "Ir para o primeiro convite.", @@ -1832,7 +1832,7 @@ "Select the roles required to change various parts of the room": "Selecione as permissões necessárias para alterar várias partes da sala", "Emoji picker": "Enviar emoji", "Room %(name)s": "Sala %(name)s", - "No recently visited rooms": "Não há salas visitadas recentemente", + "No recently visited rooms": "Nenhuma sala foi visitada recentemente", "Custom Tag": "Etiqueta personalizada", "Joining room …": "Entrando na sala…", "Loading …": "Carregando…", @@ -1870,7 +1870,7 @@ "Appearance": "Aparência", "Show rooms with unread messages first": "Mostrar salas não lidas em primeiro", "Show previews of messages": "Mostrar pré-visualizações de mensagens", - "Sort by": "Ordenar por", + "Sort by": "Classificar por", "Activity": "Atividade recente", "A-Z": "A-Z", "Unknown Command": "Comando desconhecido", @@ -1908,7 +1908,7 @@ "Cancel replying to a message": "Cancelar resposta à mensagem", "Toggle microphone mute": "Ativar/desativar som do microfone", "Toggle video on/off": "Ativar/desativar o vídeo", - "Scroll up/down in the timeline": "Rolar para cima/baixo na linha do tempo", + "Scroll up/down in the timeline": "Rolar para cima/baixo na conversa", "Dismiss read marker and jump to bottom": "Ignorar o marcador de leitura e ir para o final", "Jump to oldest unread message": "Ir para a mensagem não lida mais antiga", "Upload a file": "Enviar um arquivo", @@ -2225,7 +2225,7 @@ "This homeserver does not support login using email address.": "Este servidor local não suporta login usando endereço de e-mail.", "or another cross-signing capable Matrix client": "ou outro cliente Matrix com capacidade de autoverificação", "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "Quando há muitas mensagens, isso pode levar algum tempo. Por favor, não recarregue o seu cliente enquanto isso.", - "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Atenção: ao atualizar uma sala, os membros da sala não são migrados automaticamente para a versão atualizada da sala. Publicaremos um link da nova sala na versão antiga da sala - os membros da sala terão que clicar neste link para entrar na nova sala.", + "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Atenção: ao atualizar uma sala, os participantes da sala não são migrados automaticamente para a versão atualizada da sala. Publicaremos um link da nova sala na versão antiga da sala - os participantes da sala terão que clicar neste link para entrar na nova sala.", "Upgrade this room to the recommended room version": "Atualizar a versão desta sala", "this room": "esta sala", "View older messages in %(roomName)s.": "Ler mensagens antigas em %(roomName)s.", @@ -2261,9 +2261,9 @@ "Send report": "Enviar relatório", "A browser extension is preventing the request.": "Uma extensão do navegador está impedindo a solicitação.", "The server has denied your request.": "O servidor recusou a sua solicitação.", - "No files visible in this room": "Não há arquivos nesta sala", + "No files visible in this room": "Nenhum arquivo nesta sala", "Attach files from chat or just drag and drop them anywhere in a room.": "Anexe arquivos na conversa, ou simplesmente arraste e solte arquivos em qualquer lugar na sala.", - "You have no visible notifications in this room.": "Não há notificações nesta sala.", + "You have no visible notifications in this room.": "Nenhuma notificação nesta sala", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Sua nova conta (%(newAccountId)s) foi registrada, mas você já está conectado a uma conta diferente (%(loggedInUserId)s).", "%(brand)s Desktop": "%(brand)s para Computador", "%(brand)s iOS": "%(brand)s para iOS", @@ -2283,5 +2283,17 @@ "Page Down": "Page Down", "You've successfully verified %(deviceName)s (%(deviceId)s)!": "Você confirmou %(deviceName)s (%(deviceId)s) com êxito!", "Verified": "Confirmado", - "Close dialog or context menu": "Fechar caixa de diálogo ou menu" + "Close dialog or context menu": "Fechar caixa de diálogo ou menu", + "Try scrolling up in the timeline to see if there are any earlier ones.": "Tente rolar para cima na conversa para ver se há mensagens anteriores.", + "Navigate composer history": "Ver o histórico de mensagens enviadas no campo de texto", + "Unexpected server error trying to leave the room": "Erro inesperado no servidor, ao tentar sair da sala", + "Error leaving room": "Erro ao sair da sala", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Protótipo da segunda versão das Comunidades. Requer servidor principal compatível. Altamente experimental - use com cautela.", + "Space": "Barra de espaço", + "People you know on %(brand)s": "Pessoas que você conhece em %(brand)s", + "Show": "Mostrar", + "Send %(count)s invites|other": "Enviar %(count)s convites", + "Send %(count)s invites|one": "Enviar %(count)s convite", + "Community ID: +:%(domain)s": "ID da comunidade: +:%(domain)s", + "Enter name": "Digitar nome" } diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index e0be2f9820..203f407ece 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2420,5 +2420,40 @@ "Error leaving room": "Ошибка при выходе из комнаты", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Прототипы сообщества v2. Требуется совместимый домашний сервер. Очень экспериментально - используйте с осторожностью.", "Explore rooms in %(communityName)s": "Посмотреть комнаты в %(communityName)s", - "Set up Secure Backup": "Настроить безопасное резервное копирование" + "Set up Secure Backup": "Настроить безопасное резервное копирование", + "Explore community rooms": "Просмотреть комнаты сообщества", + "Information": "Информация", + "Add another email": "Добавить еще один адрес электронной почты", + "People you know on %(brand)s": "Люди, которых вы знаете в %(brand)s", + "Show": "Показать", + "Send %(count)s invites|other": "Отправить %(count)s приглашений", + "Send %(count)s invites|one": "Отправить %(count)s приглашение", + "Invite people to join %(communityName)s": "Пригласите людей вступить в %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "При создании сообщества произошла ошибка. Имя может быть занято или сервер не может обработать ваш запрос.", + "Community ID: +:%(domain)s": "ID сообщества: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Используйте это при обращении к другим людям. ID сообщества не может быть изменён.", + "You can change this later if needed.": "При необходимости вы можете изменить это позже.", + "What's the name of your community or team?": "Как называется ваше сообщество или команда?", + "Enter name": "Введите имя", + "Add image (optional)": "Добавить изображение (необязательно)", + "An image will help people identify your community.": "Изображение поможет людям идентифицировать ваше сообщество.", + "Create a room in %(communityName)s": "Создать комнату в %(communityName)s", + "Create community": "Создать сообщество", + "Cross-signing and secret storage are ready for use.": "Кросс-подпись и секретное хранилище готовы к использованию.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Кросс-подпись готова к использованию, но секретное хранилище в настоящее время не используется для резервного копирования ваших ключей.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Приватные комнаты можно найти и присоединиться только по приглашению. Публичные комнаты может найти и присоединиться к ним кто угодно.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Приватные комнаты можно найти и присоединиться только по приглашению. Публичные комнаты могут находить и присоединяться к ним любые участники этого сообщества.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Вы можете включить это, если комната будет использоваться только для совместной работы с внутренними командами на вашем домашнем сервере. Это не может быть изменено позже.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Вы можете отключить это, если комната будет использоваться для совместной работы с внешними командами, у которых есть собственный домашний сервер. Это не может быть изменено позже.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Запретить кому-либо, не входящему в %(serverName)s, когда-либо присоединяться к этой комнате.", + "There was an error updating your community. The server is unable to process your request.": "Произошла ошибка в обновлении вашего сообщества. Сервер не может обработать ваш запрос.", + "Update community": "Обновить сообщество", + "May include members not in %(communityName)s": "Может включать участников, не входящих в %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Начните разговор с кем-нибудь, используя имя, имя пользователя (например, ) или адрес электронной почты. Это не пригласит их в %(communityName)s. Чтобы пригласить кого-нибудь в %(communityName)s, нажмите здесь.", + "Failed to find the general chat for this community": "Не удалось найти общий чат для этого сообщества", + "Community settings": "Настройки сообщества", + "User settings": "Пользовательские настройки", + "Community and user menu": "Сообщество и меню пользователя", + "Privacy": "Конфиденциальность", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению" } diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index 667768fd57..5ed562e400 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -1798,5 +1798,18 @@ "Security & privacy": "Bezpečnosť & súkromie", "All settings": "Všetky nastavenia", "Feedback": "Spätná väzba", - "Indexed rooms:": "Indexované miestnosti:" + "Indexed rooms:": "Indexované miestnosti:", + "Unexpected server error trying to leave the room": "Neočakávaná chyba servera pri pokuse opustiť miestnosť", + "Emoji picker": "Vybrať emoji", + "Send a reply…": "Odoslať odpoveď…", + "Send a message…": "Odoslať správu…", + "Bold": "Tučné", + "Italics": "Kurzíva", + "Strikethrough": "Preškrtnuté", + "Leave Room": "Opustiť miestnosť", + "Direct message": "Priama správa", + "Security": "Zabezpečenie", + "Send a Direct Message": "Poslať priamu správu", + "User menu": "Používateľské menu", + "Toggle Italics": "Prepnúť kurzíva" } diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 9dd4c69840..2032eca4ff 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -7,7 +7,7 @@ "No Microphones detected": "Ingen mikrofon hittades", "No Webcams detected": "Ingen webbkamera hittades", "No media permissions": "Inga mediebehörigheter", - "You may need to manually permit %(brand)s to access your microphone/webcam": "Du måste manuellt tillåta %(brand)s att komma åt din mikrofon/kamera", + "You may need to manually permit %(brand)s to access your microphone/webcam": "Du behöver manuellt tillåta %(brand)s att komma åt din mikrofon/kamera", "Default Device": "Standardenhet", "Microphone": "Mikrofon", "Camera": "Kamera", @@ -15,8 +15,8 @@ "Always show message timestamps": "Visa alltid tidsstämplar för meddelanden", "Authentication": "Autentisering", "%(items)s and %(lastItem)s": "%(items)s och %(lastItem)s", - "and %(count)s others...|other": "och %(count)s andra...", - "and %(count)s others...|one": "och en annan...", + "and %(count)s others...|other": "och %(count)s andra…", + "and %(count)s others...|one": "och en annan…", "A new password must be entered.": "Ett nytt lösenord måste anges.", "%(senderName)s answered the call.": "%(senderName)s svarade på samtalet.", "Anyone who knows the room's link, including guests": "Alla som har rummets adress, inklusive gäster", @@ -33,7 +33,7 @@ "Ban": "Banna", "Attachment": "Bilaga", "Call Timeout": "Samtalstimeout", - "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Det går inte att ansluta till en hemserver via HTTP då adressen i webbläsaren är HTTPS. Använd HTTPS, eller aktivera osäkra skript.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Kan inte ansluta till en hemserver via HTTP då adressen i webbläsaren är HTTPS. Använd HTTPS, eller aktivera osäkra skript.", "Change Password": "Byt lösenord", "%(senderName)s changed their profile picture.": "%(senderName)s bytte sin profilbild.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s bytte rummets namn till %(roomName)s.", @@ -42,9 +42,9 @@ "Changes your display nickname": "Ändrar ditt visningsnamn", "Click here to fix": "Klicka här för att fixa", "Click to mute audio": "Klicka för att tysta ljud", - "Click to mute video": "Klicka för att stänga av video", + "Click to mute video": "Klicka för att tysta videon", "click to reveal": "klicka för att avslöja", - "Click to unmute video": "Klicka för att sätta på video", + "Click to unmute video": "Klicka för att sluta tysta videon", "Click to unmute audio": "Klicka för att sätta på ljud", "Command error": "Kommandofel", "Commands": "Kommandon", @@ -56,67 +56,67 @@ "Custom level": "Anpassad nivå", "/ddg is not a command": "/ddg är inte ett kommando", "Deactivate Account": "Inaktivera konto", - "Decrypt %(text)s": "Dekryptera %(text)s", + "Decrypt %(text)s": "Avkryptera %(text)s", "Deops user with given id": "Degraderar användaren med givet ID", "Default": "Standard", "Disinvite": "Häv inbjudan", "Displays action": "Visar åtgärd", "Download %(text)s": "Ladda ner %(text)s", - "Email": "Epost", - "Email address": "Epostadress", + "Email": "E-post", + "Email address": "E-postadress", "Emoji": "Emoji", "%(senderName)s ended the call.": "%(senderName)s avslutade samtalet.", "Error": "Fel", - "Error decrypting attachment": "Det gick inte att dekryptera bilagan", + "Error decrypting attachment": "Fel vid avkryptering av bilagan", "Existing Call": "Existerande samtal", "Export": "Exportera", "Export E2E room keys": "Exportera krypteringsrumsnycklar", - "Failed to ban user": "Det gick inte att banna användaren", - "Failed to change password. Is your password correct?": "Det gick inte att byta lösenord. Är lösenordet rätt?", - "Failed to change power level": "Det gick inte att ändra behörighetsnivå", - "Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s", - "Failed to join room": "Det gick inte att gå med i rummet", - "Failed to kick": "Det gick inte att kicka", + "Failed to ban user": "Misslyckades att banna användaren", + "Failed to change password. Is your password correct?": "Misslyckades att byta lösenord. Är lösenordet rätt?", + "Failed to change power level": "Misslyckades att ändra behörighetsnivå", + "Failed to forget room %(errCode)s": "Misslyckades att glömma bort rummet %(errCode)s", + "Failed to join room": "Misslyckades att gå med i rummet", + "Failed to kick": "Misslyckades att kicka", "Failed to leave room": "Det gick inte att lämna rummet", - "Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen", - "Failed to mute user": "Det gick inte att tysta användaren", - "Failed to reject invite": "Det gick inte att avböja inbjudan", - "Failed to reject invitation": "Det gick inte att avböja inbjudan", - "Failed to send email": "Det gick inte att skicka epost", - "Failed to send request.": "Det gick inte att sända begäran.", - "Failed to set display name": "Det gick inte att ange visningsnamn", - "Failed to unban": "Det gick inte att avbanna", - "Failed to verify email address: make sure you clicked the link in the email": "Det gick inte att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", + "Failed to load timeline position": "Misslyckades att hämta positionen på tidslinjen", + "Failed to mute user": "Misslyckades att tysta användaren", + "Failed to reject invite": "Misslyckades att avböja inbjudan", + "Failed to reject invitation": "Misslyckades att avböja inbjudan", + "Failed to send email": "Misslyckades att skicka e-post", + "Failed to send request.": "Misslyckades att sända begäran.", + "Failed to set display name": "Misslyckades att ange visningsnamn", + "Failed to unban": "Misslyckades att avbanna", + "Failed to verify email address: make sure you clicked the link in the email": "Misslyckades att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", "Favourite": "Favorit", "Accept": "Godkänn", "Access Token:": "Åtkomsttoken:", "Active call (%(roomName)s)": "Aktiv samtal (%(roomName)s)", "Add": "Lägg till", "Admin Tools": "Admin-verktyg", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Det gick inte att ansluta till hemservern - kontrollera anslutningen, se till att hemserverns SSL-certifikat är betrott, och att inget webbläsartillägg blockerar förfrågningar.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kan inte ansluta till hemservern - vänligen kolla din nätverksanslutning, se till att hemserverns SSL-certifikat är betrott, och att inget webbläsartillägg blockerar förfrågningar.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ändrade behörighetsnivå för %(powerLevelDiffText)s.", "Close": "Stäng", "Custom": "Egen", "Decline": "Avvisa", "Drop File Here": "Dra filen hit", "Enter passphrase": "Ange lösenfras", - "Error: Problem communicating with the given homeserver.": "Fel: Det gick inte att kommunicera med den angivna hemservern.", - "Failed to fetch avatar URL": "Det gick inte att hämta avatar-URL", - "Failed to upload profile picture!": "Det gick inte att ladda upp profilbild!", - "Failure to create room": "Det gick inte att skapa rummet", + "Error: Problem communicating with the given homeserver.": "Fel: Problem med att kommunicera med den angivna hemservern.", + "Failed to fetch avatar URL": "Misslyckades att hämta avatar-URL", + "Failed to upload profile picture!": "Misslyckades att ladda upp profilbild!", + "Failure to create room": "Misslyckades att skapa rummet", "Favourites": "Favoriter", "Fill screen": "Fyll skärmen", "Filter room members": "Filtrera rumsmedlemmar", "Forget room": "Glöm bort rum", "For security, this session has been signed out. Please sign in again.": "Av säkerhetsskäl har den här sessionen loggats ut. Vänligen logga in igen.", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s från %(fromPowerLevel)s till %(toPowerLevel)s", - "Guests cannot join this room even if explicitly invited.": "Gäster kan inte gå med i det här rummet fastän de är uttryckligen inbjudna.", + "Guests cannot join this room even if explicitly invited.": "Gäster kan inte gå med i det här rummet även om de är uttryckligen inbjudna.", "Hangup": "Lägg på", "Historical": "Historiska", "Home": "Hem", - "Homeserver is": "Hemserver är", - "Identity Server is": "Identitetsserver är", - "I have verified my email address": "Jag har verifierat min epostadress", + "Homeserver is": "Hemservern är", + "Identity Server is": "Identitetsservern är", + "I have verified my email address": "Jag har verifierat min e-postadress", "Import": "Importera", "Import E2E room keys": "Importera rumskrypteringsnycklar", "Incoming call from %(name)s": "Inkommande samtal från %(name)s", @@ -124,7 +124,7 @@ "Incoming voice call from %(name)s": "Inkommande röstsamtal från %(name)s", "Incorrect username and/or password.": "Fel användarnamn och/eller lösenord.", "Incorrect verification code": "Fel verifieringskod", - "Invalid Email Address": "Ogiltig epostadress", + "Invalid Email Address": "Ogiltig e-postadress", "Invalid file%(extra)s": "Felaktig fil%(extra)s", "%(senderName)s invited %(targetName)s.": "%(senderName)s bjöd in %(targetName)s.", "Invited": "Inbjuden", @@ -134,11 +134,11 @@ "Join as voice or video.": "Gå med som röst eller video.", "Join Room": "Gå med i rum", "%(targetName)s joined the room.": "%(targetName)s gick med i rummet.", - "Jump to first unread message.": "Hoppa till första olästa meddelande.", + "Jump to first unread message.": "Hoppa till första olästa meddelandet.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s kickade %(targetName)s.", "Kick": "Kicka", "Kicks user with given id": "Kickar användaren med givet ID", - "Labs": "Labb", + "Labs": "Experiment", "Last seen": "Senast sedd", "Leave room": "Lämna rummet", "%(targetName)s left the room.": "%(targetName)s lämnade rummet.", @@ -155,8 +155,8 @@ "Mute": "Tysta", "Name": "Namn", "New passwords don't match": "De nya lösenorden matchar inte", - "New passwords must match each other.": "De nya lösenorden måste vara de samma.", - "not specified": "inte specifierad", + "New passwords must match each other.": "De nya lösenorden måste matcha.", + "not specified": "inte specificerad", "Notifications": "Aviseringar", "(not supported by this browser)": "(stöds inte av webbläsaren)", "": "", @@ -172,7 +172,7 @@ "Passwords can't be empty": "Lösenorden kan inte vara tomma", "Permissions": "Behörigheter", "Phone": "Telefon", - "Please check your email and click on the link it contains. Once this is done, click continue.": "Öppna meddelandet i din epost och klicka på länken i meddelandet. När du har gjort detta, klicka vidare.", + "Please check your email and click on the link it contains. Once this is done, click continue.": "Öppna meddelandet i din e-post och klicka på länken i meddelandet. När du har gjort detta, klicka fortsätt.", "Power level must be positive integer.": "Behörighetsnivå måste vara ett positivt heltal.", "Private Chat": "Privatchatt", "Privileged Users": "Privilegierade användare", @@ -187,10 +187,10 @@ "Remove": "Ta bort", "%(senderName)s requested a VoIP conference.": "%(senderName)s begärde ett VoIP-gruppsamtal.", "Results from DuckDuckGo": "Resultat från DuckDuckGo", - "Return to login screen": "Tillbaka till login-skärmen", + "Return to login screen": "Tillbaka till inloggningsskärmen", "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s har inte tillstånd att skicka aviseringar - kontrollera webbläsarens inställningar", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s fick inte tillstånd att skicka aviseringar - försök igen", - "%(brand)s version:": "%(brand)s -version:", + "%(brand)s version:": "%(brand)s-version:", "Room %(roomId)s not visible": "Rummet %(roomId)s är inte synligt", "Room Colour": "Rumsfärg", "%(roomName)s does not exist.": "%(roomName)s finns inte.", @@ -205,9 +205,9 @@ "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s skickade en bild.", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s bjöd in %(targetDisplayName)s att gå med i rummet.", "Server error": "Serverfel", - "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig, överbelastad, eller så tog sökningen för lång tid :(", + "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig eller överbelastad, eller så tog sökningen för lång tid :(", "Server may be unavailable, overloaded, or you hit a bug.": "Servern kan vara otillgänglig eller överbelastad, eller så stötte du på en bugg.", - "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig, överbelastad, eller så gick något annat fel.", + "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig eller överbelastad, eller så gick något annat fel.", "Session ID": "Sessions-ID", "%(senderName)s set a profile picture.": "%(senderName)s satte en profilbild.", "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s bytte sitt visningnamn till %(displayName)s.", @@ -229,24 +229,24 @@ "unknown error code": "okänd felkod", "Add a widget": "Lägg till en widget", "Allow": "Tillåt", - "Cannot add any more widgets": "Det går inte att lägga till fler widgets", - "Delete widget": "Ta bort widget", + "Cannot add any more widgets": "Kan inte lägga till fler widgets", + "Delete widget": "Radera widget", "Define the power level of a user": "Definiera behörighetsnivå för en användare", "Edit": "Ändra", "Enable automatic language detection for syntax highlighting": "Aktivera automatisk språkdetektering för syntaxmarkering", "Publish this room to the public in %(domain)s's room directory?": "Publicera rummet i den offentliga rumskatalogen på %(domain)s?", "AM": "FM", "PM": "EM", - "Submit": "Lämna in", - "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillsats till rummet.", - "The phone number entered looks invalid": "Det angivna telefonnumret är ogiltigt", + "Submit": "Skicka in", + "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillagts till rummet.", + "The phone number entered looks invalid": "Det angivna telefonnumret ser ogiltigt ut", "This email address is already in use": "Den här e-postadressen används redan", "This email address was not found": "Den här e-postadressen finns inte", - "The email address linked to your account must be entered.": "Epostadressen som är kopplad till ditt konto måste anges.", + "The email address linked to your account must be entered.": "E-postadressen som är kopplad till ditt konto måste anges.", "Online": "Online", "Unnamed room": "Namnlöst rum", "World readable": "Alla kan läsa", - "Guests can join": "Gäster kan gå med i rummet", + "Guests can join": "Gäster kan gå med", "No rooms to show": "Inga fler rum att visa", "This phone number is already in use": "Detta telefonnummer används redan", "The version of %(brand)s": "Version av %(brand)s", @@ -283,13 +283,13 @@ "You need to be able to invite users to do that.": "Du behöver kunna bjuda in användare för att göra det där.", "You are not in this room.": "Du är inte i det här rummet.", "You do not have permission to do that in this room.": "Du har inte behörighet att göra det i det här rummet.", - "Fetching third party location failed": "Det gick inte att hämta platsdata från tredje part", + "Fetching third party location failed": "Misslyckades att hämta platsdata från tredje part", "All notifications are currently disabled for all targets.": "Alla aviseringar är för tillfället avstängda för alla mål.", "Uploading report": "Laddar upp rapport", "Sunday": "söndag", "Messages sent by bot": "Meddelanden från bottar", "Notification targets": "Aviseringsmål", - "Failed to set direct chat tag": "Det gick inte att markera rummet som direkt-chatt", + "Failed to set direct chat tag": "Misslyckades att markera rummet som direktchatt", "Today": "idag", "You are not receiving desktop notifications": "Du får inte skrivbordsaviseringar", "Friday": "fredag", @@ -299,8 +299,8 @@ "Changelog": "Ändringslogg", "Waiting for response from server": "Väntar på svar från servern", "Leave": "Lämna", - "Uploaded on %(date)s by %(user)s": "%(user)s laddade upp %(date)s", - "Advanced notification settings": "Avancerade aviseringsinställingar", + "Uploaded on %(date)s by %(user)s": "Uppladdad av %(user)s vid %(date)s", + "Advanced notification settings": "Avancerade aviseringsinställningar", "Forget": "Glöm bort", "You cannot delete this image. (%(code)s)": "Du kan inte radera den här bilden. (%(code)s)", "Cancel Sending": "Avbryt sändning", @@ -309,18 +309,18 @@ "Noisy": "Högljudd", "Room not found": "Rummet hittades inte", "Messages containing my display name": "Meddelanden som innehåller mitt visningsnamn", - "Messages in one-to-one chats": "Meddelanden i privata chattar", + "Messages in one-to-one chats": "Meddelanden i en-till-en chattar", "Unavailable": "Otillgänglig", - "View Decrypted Source": "Visa dekrypterad källa", - "Failed to update keywords": "Det gick inte att uppdatera nyckelorden", + "View Decrypted Source": "Visa avkrypterad källa", + "Failed to update keywords": "Kunde inte uppdatera nyckelorden", "remove %(name)s from the directory.": "ta bort %(name)s från katalogen.", "Notifications on the following keywords follow rules which can’t be displayed here:": "Aviseringar för följande nyckelord följer regler som inte kan visas här:", "Please set a password!": "Vänligen välj ett lösenord!", "You have successfully set a password!": "Du har valt ett nytt lösenord!", - "An error occurred whilst saving your email notification preferences.": "Ett fel uppstod då epostaviseringsinställningarna sparades.", - "Explore Room State": "Utforska rumläget", + "An error occurred whilst saving your email notification preferences.": "Ett fel inträffade då e-postaviseringsinställningarna sparades.", + "Explore Room State": "Utforska rumsstatus", "Source URL": "Käll-URL", - "Failed to add tag %(tagName)s to room": "Det gick inte att lägga till etiketten \"%(tagName)s\" till rummet", + "Failed to add tag %(tagName)s to room": "Misslyckades att lägga till etiketten %(tagName)s till rummet", "Filter results": "Filtrera resultaten", "Members": "Medlemmar", "No update available.": "Ingen uppdatering tillgänglig.", @@ -330,7 +330,7 @@ "Keywords": "Nyckelord", "Enable notifications for this account": "Aktivera aviseringar för det här kontot", "Messages containing keywords": "Meddelanden som innehåller nyckelord", - "Error saving email notification preferences": "Ett fel uppstod då epostaviseringsinställningarna sparades", + "Error saving email notification preferences": "Fel när e-postaviseringsinställningarna sparades", "Tuesday": "tisdag", "Enter keywords separated by a comma:": "Skriv in nyckelord, separerade med kommatecken:", "Search…": "Sök…", @@ -356,14 +356,14 @@ "Send logs": "Skicka loggar", "All messages": "Alla meddelanden", "Call invitation": "Inbjudan till samtal", - "Downloading update...": "Laddar ned uppdatering...", + "Downloading update...": "Laddar ned uppdatering…", "You have successfully set a password and an email address!": "Du har framgångsrikt valt ett lösenord och en e-postadress!", "What's new?": "Vad är nytt?", "Notify me for anything else": "Avisera för allt annat", "When I'm invited to a room": "När jag bjuds in till ett rum", - "Can't update user notification settings": "Kan inte uppdatera aviseringsinställningarna", + "Can't update user notification settings": "Kan inte uppdatera användaraviseringsinställningarna", "Notify for all other messages/rooms": "Avisera för alla andra meddelanden/rum", - "Unable to look up room ID from server": "Det gick inte att hämta rums-ID:t från servern", + "Unable to look up room ID from server": "Kunde inte hämta rums-ID:t från servern", "Couldn't find a matching Matrix room": "Kunde inte hitta ett matchande Matrix-rum", "Invite to this room": "Bjud in till rummet", "Thursday": "torsdag", @@ -371,28 +371,28 @@ "Back": "Tillbaka", "Reply": "Svara", "Show message in desktop notification": "Visa meddelande i skrivbordsavisering", - "Unhide Preview": "Visa förhandsvisning", - "Unable to join network": "Det gick inte att ansluta till nätverket", + "Unhide Preview": "Visa förhandsgranskning", + "Unable to join network": "Kunde inte ansluta till nätverket", "Sorry, your browser is not able to run %(brand)s.": "Beklagar, din webbläsare kan inte köra %(brand)s.", "Messages in group chats": "Meddelanden i gruppchattar", "Yesterday": "igår", "Error encountered (%(errorDetail)s).": "Fel påträffat (%(errorDetail)s).", "Low Priority": "Låg prioritet", - "Unable to fetch notification target list": "Det gick inte att hämta aviseringsmållistan", + "Unable to fetch notification target list": "Kunde inte hämta aviseringsmållistan", "Set Password": "Välj lösenord", "Off": "Av", - "%(brand)s does not know how to join a room on this network": "%(brand)s kan inte gå med i ett rum på det här nätverket", + "%(brand)s does not know how to join a room on this network": "%(brand)s vet inte hur den ska gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", - "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", - "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut och logga in på andra enheter.", - "Enable email notifications": "Aktivera epostaviseringar", + "Failed to remove tag %(tagName)s from room": "Misslyckades att radera etiketten %(tagName)s från rummet", + "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut, och logga in på andra enheter.", + "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", - "Failed to change settings": "Det gick inte att spara inställningarna", + "Failed to change settings": "Misslyckades att spara inställningarna", "View Source": "Visa källa", "Thank you!": "Tack!", "Quote": "Citera", "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "Med din nuvarande webbläsare kan appens utseende vara helt fel, och vissa eller alla egenskaper kommer nödvändigtvis inte att fungera. Om du ändå vill försöka så kan du fortsätta, men gör det på egen risk!", - "Checking for an update...": "Letar efter uppdateringar...", + "Checking for an update...": "Letar efter uppdateringar…", "Who can access this room?": "Vilka kan komma åt detta rum?", "Who can read history?": "Vilka kan läsa historik?", "Members only (since the point in time of selecting this option)": "Endast medlemmar (från tidpunkten för när denna inställning valdes)", @@ -410,8 +410,8 @@ "You cannot place VoIP calls in this browser.": "Du kan inte ringa VoIP-samtal i den här webbläsaren.", "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Din e-postadress verkar inte vara kopplad till något Matrix-ID på den här hemservern.", "Restricted": "Begränsad", - "Failed to invite the following users to the %(roomName)s room:": "Det gick inte att bjuda in följande användare till rummet %(roomName)s:", - "Unable to create widget.": "Det gick inte att skapa widgeten.", + "Failed to invite the following users to the %(roomName)s room:": "Misslyckades att bjuda in följande användare till rummet %(roomName)s:", + "Unable to create widget.": "Kunde inte skapa widgeten.", "Ignored user": "Ignorerad användare", "You are now ignoring %(userId)s": "Du ignorerar nu %(userId)s", "Unignored user": "Avignorerad användare", @@ -426,7 +426,7 @@ "Unnamed Room": "Namnlöst rum", "Your browser does not support the required cryptography extensions": "Din webbläsare stödjer inte nödvändiga kryptografitillägg", "Invite": "Bjud in", - "Unignore": "Ignorera inte", + "Unignore": "Avignorera", "Ignore": "Ignorera", "Jump to message": "Hoppa till meddelande", "Mention": "Nämn", @@ -436,8 +436,8 @@ "Send an encrypted reply…": "Skicka ett krypterat svar…", "Send an encrypted message…": "Skicka ett krypterat meddelande…", "You do not have permission to post to this room": "Du har inte behörighet att posta till detta rum", - "Loading...": "Laddar...", - "%(duration)ss": "%(duration)s", + "Loading...": "Laddar…", + "%(duration)ss": "%(duration)ss", "%(duration)sm": "%(duration)sm", "%(duration)sh": "%(duration)sh", "%(duration)sd": "%(duration)sd", @@ -450,7 +450,7 @@ "(~%(count)s results)|other": "(~%(count)s resultat)", "(~%(count)s results)|one": "(~%(count)s resultat)", "Upload avatar": "Ladda upp avatar", - "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (nivå %(powerLevelNumber)s)", + "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (behörighet %(powerLevelNumber)s)", "Unknown Address": "Okänd adress", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sgick med %(count)s gånger", @@ -465,35 +465,35 @@ "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sgick med och lämnade", "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sgick med och lämnade %(count)s gånger", "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sgick med och lämnade", - "And %(count)s more...|other": "Och %(count)s till...", + "And %(count)s more...|other": "Och %(count)s till…", "ex. @bob:example.com": "t.ex. @kalle:exempel.com", "Add User": "Lägg till användare", "Matrix ID": "Matrix-ID", - "Matrix Room ID": "Matrix-rums-ID", - "email address": "epostadress", + "Matrix Room ID": "Matrixrums-ID", + "email address": "e-postadress", "Try using one of the following valid address types: %(validTypesList)s.": "Prova att använda någon av följande giltiga adresstyper: %(validTypesList)s.", "You have entered an invalid address.": "Du har angett en ogiltig adress.", - "Preparing to send logs": "Förbereder att skicka loggar", + "Preparing to send logs": "Förbereder sändning av loggar", "Logs sent": "Loggar skickade", - "Failed to send logs: ": "Det gick inte att skicka loggar: ", + "Failed to send logs: ": "Misslyckades att skicka loggar: ", "Submit debug logs": "Skicka felsökningsloggar", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Felsökningsloggar innehåller användningsdata för applikationen inklusive ditt användarnamn, ID:n eller alias för de rum och grupper du har besökt och användarnamn för andra användare. De innehåller inte meddelanden.", - "An email has been sent to %(emailAddress)s": "Ett epostmeddelande har skickats till %(emailAddress)s", - "Please check your email to continue registration.": "Vänligen kolla din epost för att fortsätta registreringen.", - "Token incorrect": "Ogiltig token", - "A text message has been sent to %(msisdn)s": "Ett textmeddelande har skickats till %(msisdn)s", + "An email has been sent to %(emailAddress)s": "Ett e-postmeddelande har skickats till %(emailAddress)s", + "Please check your email to continue registration.": "Vänligen kolla din e-post för att fortsätta registreringen.", + "Token incorrect": "Felaktig token", + "A text message has been sent to %(msisdn)s": "Ett SMS har skickats till %(msisdn)s", "Please enter the code it contains:": "Vänligen ange koden det innehåller:", "Code": "Kod", "Set a display name:": "Ange ett visningsnamn:", "Upload an avatar:": "Ladda upp en avatar:", - "This server does not support authentication with a phone number.": "Denna server har inte support för autentisering via telefonnummer.", - "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s andra", + "This server does not support authentication with a phone number.": "Denna server stöder inte autentisering via telefonnummer.", + "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s till", "Uploading %(filename)s and %(count)s others|zero": "Laddar upp %(filename)s", - "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s annan", - "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig epostadress", + "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s till", + "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig e-postadress", "Verification Pending": "Avvaktar verifiering", - "Unable to add email address": "Det gick inte att lägga till epostadress", - "Unable to verify email address.": "Det gick inte att verifiera epostadressen.", + "Unable to add email address": "Kunde inte lägga till e-postadress", + "Unable to verify email address.": "Kunde inte verifiera e-postadressen.", "Skip": "Hoppa över", "Username not available": "Användarnamn inte tillgängligt", "Username invalid: %(errMessage)s": "Ogiltigt användarnamn: %(errMessage)s", @@ -508,9 +508,9 @@ "Start automatically after system login": "Starta automatiskt vid systeminloggning", "This will allow you to reset your password and receive notifications.": "Det här låter dig återställa lösenordet och ta emot aviseringar.", "You have no visible notifications": "Du har inga synliga aviseringar", - "Failed to upload image": "Det gick inte att ladda upp bild", + "Failed to upload image": "Misslyckades att ladda upp bild", "New Password": "Nytt lösenord", - "Do you want to set an email address?": "Vill du ange en epostadress?", + "Do you want to set an email address?": "Vill du ange en e-postadress?", "Not a valid %(brand)s keyfile": "Inte en giltig %(brand)s-nyckelfil", "Authentication check failed: incorrect password?": "Autentiseringskontroll misslyckades: felaktigt lösenord?", "Always show encryption icons": "Visa alltid krypteringsikoner", @@ -526,29 +526,29 @@ "You seem to be in a call, are you sure you want to quit?": "Du verkar vara i ett samtal, är du säker på att du vill avsluta?", "Active call": "Aktivt samtal", "%(count)s of your messages have not been sent.|one": "Ditt meddelande skickades inte.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Skicka om alla eller ångra alla nu. Du kan även välja enskilda meddelanden för att skicka om eller ångra.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Skicka om meddelande eller ångra meddelande nu.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Skicka om alla eller avbryt alla nu. Du kan även välja enskilda meddelanden för att skicka om eller avbryta.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Skicka om meddelande eller avbryt meddelande nu.", "Connectivity to the server has been lost.": "Anslutning till servern har brutits.", "Sent messages will be stored until your connection has returned.": "Skickade meddelanden kommer att lagras tills anslutningen är tillbaka.", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "Det är ingen annan här! Vill du bjuda in någon eller sluta varna om det tomma rummet?", "Room": "Rum", - "Clear filter": "Töm filter", + "Clear filter": "Rensa filter", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Försökte ladda en viss punkt i det här rummets tidslinje, men du har inte behörighet att visa det aktuella meddelandet.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", - "Success": "Slutfört", - "Unable to remove contact information": "Det gick inte att ta bort kontaktuppgifter", - "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett epostmeddelande har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", - "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", + "Success": "Framgång", + "Unable to remove contact information": "Kunde inte ta bort kontaktuppgifter", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett e-brev har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", + "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på servern %(hs)s, inte matrix.org.", "This homeserver doesn't offer any login flows which are supported by this client.": "Denna hemserver erbjuder inga inloggningsflöden som stöds av den här klienten.", "Upload new:": "Ladda upp ny:", "Copied!": "Kopierat!", - "Failed to copy": "Det gick inte att kopiera", - "Delete Widget": "Ta bort widget", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Widget tas bort för alla användare i rummet. Är du säker på att du vill ta bort den?", + "Failed to copy": "Misslyckades att kopiera", + "Delete Widget": "Radera widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Att radera en widget tar bort den för alla användare i rummet. Är du säker på att du vill radera den?", "Minimize apps": "Minimera appar", - "Failed to invite the following users to %(groupId)s:": "Det gick inte att bjuda in följande användare till %(groupId)s:", - "Failed to invite users to %(groupId)s": "Det gick inte att bjuda in användare till %(groupId)s", - "This room is not public. You will not be able to rejoin without an invite.": "Detta rum är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", + "Failed to invite the following users to %(groupId)s:": "Misslyckades att bjuda in följande användare till %(groupId)s:", + "Failed to invite users to %(groupId)s": "Misslyckades att bjuda in användare till %(groupId)s", + "This room is not public. You will not be able to rejoin without an invite.": "Det här rummet är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", "Ignores a user, hiding their messages from you": "Ignorerar en användare och döljer dess meddelanden för dig", "Stops ignoring a user, showing their messages going forward": "Slutar ignorera en användare och visar dess meddelanden framöver", "Opens the Developer Tools dialog": "Öppna dialogrutan Utvecklarverktyg", @@ -560,9 +560,9 @@ "File to import": "Fil att importera", "Which officially provided instance you are using, if any": "Vilken officiellt tillhandahållen instans du använder, om någon", "(unknown failure: %(reason)s)": "(okänt fel: %(reason)s)", - "(could not connect media)": "(det gick inte ansluta media)", + "(could not connect media)": "(kunde inte ansluta media)", " (unsupported)": " (stöds ej)", - "Drop file here to upload": "Släpp fil här för att ladda upp", + "Drop file here to upload": "Släpp en fil här för att ladda upp", "Ongoing conference call%(supportedText)s.": "Pågående gruppsamtal%(supportedText)s.", "%(senderName)s sent an image": "%(senderName)s skickade en bild", "%(senderName)s sent a video": "%(senderName)s skickade en video", @@ -574,12 +574,12 @@ "Banned by %(displayName)s": "Bannad av %(displayName)s", "Muted Users": "Dämpade användare", "This room is not accessible by remote Matrix servers": "Detta rum är inte tillgängligt för externa Matrix-servrar", - "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Det gick inte att ladda händelsen som svarades på, antingen finns den inte eller så har du inte behörighet att se den.", - "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Är du säker på att du vill ta bort den här händelsen? Observera att om du tar bort en rumsnamns- eller ämnesändring kan det ångra ändringen.", + "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Kunde inte ladda händelsen som svarades på, antingen så finns den inte eller så har du inte behörighet att se den.", + "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Är du säker på att du vill ta bort (radera) den här händelsen? Observera att om du tar bort en rumsnamns- eller ämnesändring kan det ångra ändringen.", "Send Custom Event": "Skicka anpassad händelse", "You must specify an event type!": "Du måste ange en händelsetyp!", "Event sent!": "Händelse skickad!", - "Failed to send custom event.": "Det gick inte att skicka anpassad händelse.", + "Failed to send custom event.": "Misslyckades att skicka anpassad händelse.", "Event Type": "Händelsetyp", "Event Content": "Händelseinnehåll", "Example": "Exempel", @@ -594,18 +594,18 @@ "Developer Tools": "Utvecklarverktyg", "Clear Storage and Sign Out": "Rensa lagring och logga ut", "Send Logs": "Skicka loggar", - "Refresh": "Uppdatera", - "Unable to restore session": "Det gick inte att återställa sessionen", + "Refresh": "Ladda om", + "Unable to restore session": "Kunde inte återställa sessionen", "We encountered an error trying to restore your previous session.": "Ett fel uppstod vid återställning av din tidigare session.", "If you have previously used a more recent version of %(brand)s, your session may be incompatible with this version. Close this window and return to the more recent version.": "Om du nyligen har använt en senare version av %(brand)s kan din session vara inkompatibel med den här versionen. Stäng det här fönstret och använd senare versionen istället.", "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Att rensa webbläsarens lagring kan lösa problemet, men då loggas du ut och krypterad chatthistorik blir oläslig.", - "Collapse Reply Thread": "Dölj svarstråd", + "Collapse Reply Thread": "Kollapsa svarstråd", "Terms and Conditions": "Villkor", "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "För att fortsätta använda hemservern %(homeserverDomain)s måste du granska och godkänna våra villkor.", "Review terms and conditions": "Granska villkoren", - "Old cryptography data detected": "Gammal krypteringsdata upptäckt", - "Unable to capture screen": "Det gick inte att ta skärmdump", - "Failed to add the following rooms to %(groupId)s:": "Det gick inte att lägga till följande rum till %(groupId)s:", + "Old cryptography data detected": "Gammal kryptografidata upptäckt", + "Unable to capture screen": "Kunde inte ta skärmdump", + "Failed to add the following rooms to %(groupId)s:": "Misslyckades att lägga till följande rum till %(groupId)s:", "Missing roomId.": "Rums-ID saknas.", "This room is not recognised.": "Detta rum känns inte igen.", "Usage": "Användande", @@ -615,21 +615,21 @@ "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s satte framtida rumshistorik till okänd synlighet (%(visibility)s).", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Där denna sida innehåller identifierbar information, till exempel ett rums-, användar- eller grupp-ID, tas datan bort innan den skickas till servern.", "The remote side failed to pick up": "Mottagaren svarade inte", - "Jump to read receipt": "Hoppa till läsindikation", - "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan dekryptera meddelandena.", + "Jump to read receipt": "Hoppa till läskvitto", + "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan avkryptera meddelandena.", "Unknown for %(duration)s": "Okänt i %(duration)s", "Unknown": "Okänt", "e.g. %(exampleValue)s": "t.ex. %(exampleValue)s", "Can't leave Server Notices room": "Kan inte lämna serveraviseringsrummet", "This room is used for important messages from the Homeserver, so you cannot leave it.": "Detta rum används för viktiga meddelanden från hemservern, så du kan inte lämna det.", - "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data från en äldre version av %(brand)s has upptäckts. Detta ska ha orsakat att totalsträckskryptering inte fungerat i den äldre versionen. Krypterade meddelanden som nyligen har skickats medans den äldre versionen användes kanske inte kan dekrypteras i denna version. Detta kan även orsaka att meddelanden skickade med denna version inte fungerar. Om du upplever problem, logga ut och in igen. För att behålla meddelandehistoriken, exportera dina nycklar och importera dem igen.", + "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data från en äldre version av %(brand)s has upptäckts. Detta ska ha orsakat att totalsträckskryptering inte fungerat i den äldre versionen. Krypterade meddelanden som nyligen har skickats medans den äldre versionen användes kanske inte kan avkrypteras i denna version. Detta kan även orsaka att meddelanden skickade med denna version inte fungerar. Om du upplever problem, logga ut och in igen. För att behålla meddelandehistoriken, exportera dina nycklar och importera dem igen.", "Confirm Removal": "Bekräfta borttagning", "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)slämnade och gick med igen %(count)s gånger", "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)slämnade och gick med igen", "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)slämnade och gick med igen %(count)s gånger", "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)slämnade och gick med igen", "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)savböjde sina inbjudningar %(count)s gånger", - "Unable to reject invite": "Det gick inte att avböja inbjudan", + "Unable to reject invite": "Kunde inte avböja inbjudan", "Reject all %(invitedRooms)s invites": "Avböj alla %(invitedRooms)s inbjudningar", "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)savböjde sina inbjudningar", "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)savböjde sin inbjudan %(count)s gånger", @@ -655,10 +655,10 @@ "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)sbytte namn", "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)sbytte namn %(count)s gånger", "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)sbytte namn", - "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)sändrade sin avatar %(count)s gånger", - "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)sändrade sin avatar", - "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)sändrade sin avatar %(count)s gånger", - "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)sändrade sin avatar", + "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)sbytte sin avatar %(count)s gånger", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)sbytte sin avatar", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)sbytte sin avatar %(count)s gånger", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)sbytte sin avatar", "%(items)s and %(count)s others|other": "%(items)s och %(count)s till", "%(items)s and %(count)s others|one": "%(items)s och en till", "collapse": "fäll ihop", @@ -671,20 +671,20 @@ "Show these rooms to non-members on the community page and room list?": "Visa dessa rum för icke-medlemmar på gemenskapssidan och -rumslistan?", "Add rooms to the community": "Lägg till rum i gemenskapen", "Add to community": "Lägg till i gemenskap", - "Failed to invite users to community": "Det gick inte att bjuda in användare till gemenskapen", + "Failed to invite users to community": "Misslyckades att bjuda in användare till gemenskapen", "Mirror local video feed": "Spegla den lokala videoströmmen", "Community Invites": "Community-inbjudningar", "Invalid community ID": "Ogiltigt gemenskaps-ID", "'%(groupId)s' is not a valid community ID": "%(groupId)s är inte ett giltigt gemenskaps-ID", "New community ID (e.g. +foo:%(localDomain)s)": "Nytt gemenskaps-ID (t.ex. +foo:%(localDomain)s)", "Remove from community": "Ta bort från gemenskapen", - "Disinvite this user from community?": "Ta bort användarens inbjudan till gemenskapen?", + "Disinvite this user from community?": "Häv användarens inbjudan till gemenskapen?", "Remove this user from community?": "Ta bort användaren från gemenskapen?", - "Failed to remove user from community": "Det gick inte att ta bort användaren från gemenskapen", + "Failed to remove user from community": "Misslyckades att ta bort användaren från gemenskapen", "Filter community members": "Filtrera gemenskapsmedlemmar", - "Removing a room from the community will also remove it from the community page.": "Om du tar bort ett rum från gemenskapen tas det även bort från gemenskapens sida.", - "Failed to remove room from community": "Det gick inte att ta bort rummet från gemenskapen", - "Only visible to community members": "Endast synlig för gemenskapsmedlemmar", + "Removing a room from the community will also remove it from the community page.": "Om du tar bort ett rum från gemenskapen tas det även bort från gemenskapssidan.", + "Failed to remove room from community": "Misslyckades att ta bort rummet från gemenskapen", + "Only visible to community members": "Endast synligt för gemenskapsmedlemmar", "Filter community rooms": "Filtrera gemenskapsrum", "Community IDs cannot be empty.": "Gemenskaps-ID kan inte vara tomt.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Gemenskaps-ID får endast innehålla tecknen a-z, 0-9 och '=_-./'", @@ -696,21 +696,21 @@ "

    HTML for your community's page

    \n

    \n Use the long description to introduce new members to the community, or distribute\n some important links\n

    \n

    \n You can even use 'img' tags\n

    \n": "

    HTML för din gemenskapssida

    \n

    \n Använd den långa beskrivningen för att introducera nya medlemmar till gemenskapen, eller dela\n några viktiga länkar\n

    \n

    \n Du kan även använda 'img'-taggar\n

    \n", "Add rooms to the community summary": "Lägg till rum i gemenskapsöversikten", "Add users to the community summary": "Lägg till användare i gemenskapsöversikten", - "Failed to update community": "Det gick inte att uppdatera gemenskapen", - "Unable to join community": "Det gick inte att gå med i gemenskapen", + "Failed to update community": "Misslyckades att uppdatera gemenskapen", + "Unable to join community": "Kunde inte gå med i gemenskapen", "Leave Community": "Lämna gemenskapen", - "Unable to leave community": "Det gick inte att lämna gemenskap", + "Unable to leave community": "Kunde inte lämna gemenskapen", "Community Settings": "Gemenskapsinställningar", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Det kan dröja upp till 30 minuter innan ändringar på gemenskapens namn och avatar blir synliga för andra användare.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Dessa rum visas för gemenskapsmedlemmar på gemenskapssidan. Gemenskapsmedlemmar kan gå med i rummen genom att klicka på dem.", - "Add rooms to this community": "Lägg till rum i denna gemenskap", + "Add rooms to this community": "Lägg till rum till denna gemenskap", "%(inviter)s has invited you to join this community": "%(inviter)s har bjudit in dig till denna gemenskap", "Join this community": "Gå med i denna gemenskap", "Leave this community": "Lämna denna gemenskap", "You are an administrator of this community": "Du är administratör för denna gemenskap", "You are a member of this community": "Du är medlem i denna gemenskap", "Who can join this community?": "Vem kan gå med i denna gemenskap?", - "Your community hasn't got a Long Description, a HTML page to show to community members.
    Click here to open settings and give it one!": "Din gemenskap har ingen lång beskrivning eller HTML-sida att visa för medlemmar.
    Klicka här för att öppna inställningarna och lägga till det!", + "Your community hasn't got a Long Description, a HTML page to show to community members.
    Click here to open settings and give it one!": "Din gemenskap har ingen lång beskrivning, en HTML-sida att visa för medlemmar.
    Klicka här för att öppna inställningarna och lägga till en!", "Community %(groupId)s not found": "Gemenskapen %(groupId)s hittades inte", "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "För att skapa ett filter, dra en gemenskapsavatar till filterpanelen längst till vänster på skärmen. Du kan när som helst klicka på en avatar i filterpanelen för att bara se rum och personer som är associerade med den gemenskapen.", "Create a new community": "Skapa en ny gemenskap", @@ -727,56 +727,56 @@ "Everyone": "Alla", "Long Description (HTML)": "Lång beskrivning (HTML)", "Description": "Beskrivning", - "Failed to load %(groupId)s": "Det gick inte att ladda %(groupId)s", - "Failed to withdraw invitation": "Det gick inte att ta bort inbjudan", - "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort %(roomName)s från %(groupId)s?", - "Failed to remove '%(roomName)s' from %(groupId)s": "Det gick inte att ta bort %(roomName)s från %(groupId)s", + "Failed to load %(groupId)s": "Misslyckades att ladda %(groupId)s", + "Failed to withdraw invitation": "Misslyckades att dra tillbaka inbjudan", + "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort '%(roomName)s' från %(groupId)s?", + "Failed to remove '%(roomName)s' from %(groupId)s": "Misslyckades att ta bort '%(roomName)s' från %(groupId)s", "Something went wrong!": "Något gick fel!", "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "Synligheten för '%(roomName)s' i %(groupId)s kunde inte uppdateras.", "Visibility in Room List": "Synlighet i rumslistan", - "Visible to everyone": "Synlig för alla", - "Please select the destination room for this message": "Välj vilket rum meddelandet ska skickas till", - "Disinvite this user?": "Ta bort användarens inbjudan?", - "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Du kommer inte att kunna ångra den här ändringen eftersom du sänker din egen behörighetsnivå, om du är den sista privilegierade användaren i rummet blir det omöjligt att ändra behörigheter.", + "Visible to everyone": "Synligt för alla", + "Please select the destination room for this message": "Välj målrum för detta meddelande", + "Disinvite this user?": "Häv användarens inbjudan?", + "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Du kommer inte att kunna ångra den här ändringen eftersom du degraderar dig själv. Om du är den sista privilegierade användaren i rummet blir det omöjligt att återfå behörigheter.", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Du kommer inte att kunna ångra den här ändringen eftersom du höjer användaren till samma behörighetsnivå som dig själv.", "unknown caller": "okänd uppringare", "To use it, just wait for autocomplete results to load and tab through them.": "För att använda detta, vänta på att autokompletteringen laddas och tabba igenom resultatet.", "Enable inline URL previews by default": "Aktivera inbäddad URL-förhandsvisning som standard", "Enable URL previews for this room (only affects you)": "Aktivera URL-förhandsvisning för detta rum (påverkar bara dig)", "Enable URL previews by default for participants in this room": "Aktivera URL-förhandsvisning som standard för deltagare i detta rum", - "You have enabled URL previews by default.": "Du har aktiverat URL-förhandsvisning som standard.", - "You have disabled URL previews by default.": "Du har inaktiverat URL-förhandsvisning som standard.", - "URL previews are enabled by default for participants in this room.": "URL-förhandsvisning är aktiverat som standard för deltagare i detta rum.", - "URL previews are disabled by default for participants in this room.": "URL-förhandsvisning är inaktiverat som standard för deltagare i detta rum.", + "You have enabled URL previews by default.": "Du har aktiverat URL-förhandsvisning som förval.", + "You have disabled URL previews by default.": "Du har inaktiverat URL-förhandsvisning som förval.", + "URL previews are enabled by default for participants in this room.": "URL-förhandsvisning är aktiverat som förval för deltagare i detta rum.", + "URL previews are disabled by default for participants in this room.": "URL-förhandsvisning är inaktiverat som förval för deltagare i detta rum.", "URL Previews": "URL-förhandsvisning", "Which rooms would you like to add to this summary?": "Vilka rum vill du lägga till i översikten?", "Add to summary": "Lägg till i översikt", - "Failed to add the following rooms to the summary of %(groupId)s:": "Det gick inte att lägga till följande rum i översikten för %(groupId)s:", + "Failed to add the following rooms to the summary of %(groupId)s:": "Misslyckades att lägga till följande rum i översikten för %(groupId)s:", "Add a Room": "Lägg till ett rum", - "Failed to remove the room from the summary of %(groupId)s": "Det gick inte att ta bort rummet från översikten i %(groupId)s", + "Failed to remove the room from the summary of %(groupId)s": "Misslyckades att ta bort rummet från översikten i %(groupId)s", "The room '%(roomName)s' could not be removed from the summary.": "Rummet '%(roomName)s' kunde inte tas bort från översikten.", "Who would you like to add to this summary?": "Vem vill du lägga till i översikten?", - "Failed to add the following users to the summary of %(groupId)s:": "Det gick inte att lägga till följande användare i översikten för %(groupId)s:", + "Failed to add the following users to the summary of %(groupId)s:": "Misslyckades att lägga till följande användare i översikten för %(groupId)s:", "Add a User": "Lägg till en användare", - "Failed to remove a user from the summary of %(groupId)s": "Det gick inte att ta bort en användare från översikten i %(groupId)s", + "Failed to remove a user from the summary of %(groupId)s": "Misslyckades att ta bort en användare från översikten i %(groupId)s", "The user '%(displayName)s' could not be removed from the summary.": "Användaren '%(displayName)s' kunde inte tas bort från översikten.", - "Unable to accept invite": "Det gick inte att acceptera inbjudan", + "Unable to accept invite": "Kunde inte acceptera inbjudan", "Leave %(groupName)s?": "Lämna %(groupName)s?", "Enable widget screenshots on supported widgets": "Aktivera widget-skärmdumpar för widgets som stöder det", "Key request sent.": "Nyckelbegäran skickad.", "Unban": "Avbanna", "Unban this user?": "Avbanna användaren?", - "Unmute": "Ta bort dämpning", + "Unmute": "Avtysta", "You don't currently have any stickerpacks enabled": "Du har för närvarande inga dekalpaket aktiverade", "Stickerpack": "Dekalpaket", "Hide Stickers": "Dölj dekaler", "Show Stickers": "Visa dekaler", - "Error decrypting audio": "Det gick inte att dekryptera ljud", - "Error decrypting image": "Det gick inte att dekryptera bild", - "Error decrypting video": "Det gick inte att dekryptera video", + "Error decrypting audio": "Fel vid avkryptering av ljud", + "Error decrypting image": "Fel vid avkryptering av bild", + "Error decrypting video": "Fel vid avkryptering av video", "Add an Integration": "Lägg till integration", - "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en epostadress, kan du inte återställa ditt lösenord. Är du säker?", + "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 kommer att skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en e-postadress, kan du inte återställa ditt lösenord. Är du säker?", "Popout widget": "Poppa ut widget", "were unbanned %(count)s times|other": "blev avbannade %(count)s gånger", "were unbanned %(count)s times|one": "blev avbannade", @@ -784,7 +784,7 @@ "was unbanned %(count)s times|one": "blev avbannad", "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Detta kommer att göra ditt konto permanent oanvändbart. Du kommer inte att kunna logga in, och ingen kommer att kunna registrera samma användar-ID. Ditt konto kommer att lämna alla rum som det deltar i, och dina kontouppgifter kommer att raderas från identitetsservern. Denna åtgärd går inte att ångra.", "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Att du inaktiverar ditt konto gör inte att meddelanden som du skickat glöms automatiskt. Om du vill att vi ska glömma dina meddelanden, kryssa i rutan nedan.", - "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Meddelandesynlighet i Matrix liknar email. Att vi glömmer dina meddelanden innebär att meddelanden som du skickat inte delas med några nya eller oregistrerade användare, men registrerade användare som redan har tillgång till meddelandena kommer fortfarande ha tillgång till sin kopia.", + "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Meddelandesynlighet i Matrix liknar e-post. Att vi glömmer dina meddelanden innebär att meddelanden som du skickat inte delas med några nya eller oregistrerade användare, men registrerade användare som redan har tillgång till meddelandena kommer fortfarande ha tillgång till sin kopia.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Glöm alla meddelanden som jag har skickat när mitt konto inaktiveras (Varning: detta kommer att göra så att framtida användare får se ofullständiga konversationer)", "To continue, please enter your password:": "För att fortsätta, ange ditt lösenord:", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Om du har anmält en bugg via GitHub, kan felsökningsloggar hjälpa oss spåra problemet. Felsökningsloggarna innehåller användningsdata för applikationen inklusive ditt användarnamn, ID eller alias för rum och grupper du besökt och användarnamn för andra användare. De innehåller inte meddelanden.", @@ -793,8 +793,8 @@ "Learn more about how we use analytics.": "Läs mer om hur vi använder statistik.", "Analytics": "Statistik", "Send analytics data": "Skicka statistik", - "Passphrases must match": "Passfraser måste matcha", - "Passphrase must not be empty": "Lösenfras får inte vara tom", + "Passphrases must match": "Lösenfraser måste matcha", + "Passphrase must not be empty": "Lösenfrasen får inte vara tom", "Confirm passphrase": "Bekräfta lösenfrasen", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s ändrade fastnålade meddelanden för rummet.", "Message Pinning": "Fastnålning av meddelanden", @@ -802,9 +802,9 @@ "No pinned messages.": "Inga fastnålade meddelanden.", "Pinned Messages": "Fastnålade meddelanden", "Pin Message": "Nåla fast meddelande", - "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Den exporterade filen kommer att låta någon som kan läsa den att dekryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker. För att hjälpa till med detta, bör du ange en lösenfras nedan, som kommer att användas för att kryptera exporterad data. Det kommer bara vara möjligt att importera data genom att använda samma lösenfras.", - "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.": "Denna process möjliggör import av krypteringsnycklar som tidigare exporterats från en annan Matrix-klient. Du kommer då kunna dekryptera alla meddelanden som den andra klienten kunde dekryptera.", - "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Den exporterade filen kommer vara skyddad med en lösenfras. Du måste ange lösenfrasen här, för att dekryptera filen.", + "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Den exporterade filen kommer att låta de som kan läsa den att dekryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker. För att hjälpa till med detta, bör du ange en lösenfras nedan, som kommer att användas för att kryptera exporterad data. Det kommer bara vara möjligt att importera data genom att använda samma lösenfras.", + "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.": "Denna process möjliggör import av krypteringsnycklar som tidigare exporterats från en annan Matrix-klient. Du kommer då kunna avkryptera alla meddelanden som den andra klienten kunde avkryptera.", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Den exporterade filen kommer vara skyddad med en lösenfras. Du måste ange lösenfrasen här, för att avkryptera filen.", "Flair": "Emblem", "Showing flair for these communities:": "Visar emblem för dessa gemenskaper:", "This room is not showing flair for any communities": "Detta rum visar inte emblem för några gemenskaper", @@ -827,15 +827,15 @@ "Permission Required": "Behörighet krävs", "You do not have permission to start a conference call in this room": "Du har inte behörighet att starta ett gruppsamtal i detta rum", "This event could not be displayed": "Den här händelsen kunde inte visas", - "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som standard för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", - "The email field must not be blank.": "Epost-fältet får inte vara tomt.", - "The phone number field must not be blank.": "Telefonnummer-fältet får inte vara tomt.", - "The password field must not be blank.": "Lösenords-fältet får inte vara tomt.", - "Failed to remove widget": "Det gick inte att ta bort widget", - "An error ocurred whilst trying to remove the widget from the room": "Ett fel uppstod vid borttagning av widget från rummet", - "Demote yourself?": "Sänk egen behörighetsnivå?", + "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som förval för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", + "The email field must not be blank.": "E-postfältet får inte vara tomt.", + "The phone number field must not be blank.": "Telefonnummerfältet får inte vara tomt.", + "The password field must not be blank.": "Lösenordsfältet får inte vara tomt.", + "Failed to remove widget": "Misslyckades att radera widget", + "An error ocurred whilst trying to remove the widget from the room": "Ett fel inträffade vid borttagning av widget från rummet", + "Demote yourself?": "Degradera dig själv?", "Demote": "Degradera", - "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "När någon postar en URL i sitt meddelande, kan URL-förhandsvisning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", + "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "När någon lägger en URL i sitt meddelande, kan URL-förhandsgranskning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", "You can't send any messages until you review and agree to our terms and conditions.": "Du kan inte skicka några meddelanden innan du granskar och godkänner våra villkor.", "System Alerts": "Systemvarningar", "Sorry, your homeserver is too old to participate in this room.": "Tyvärr, din hemserver är för gammal för att delta i det här rummet.", @@ -843,16 +843,16 @@ "Please contact your service administrator to continue using the service.": "Kontakta din tjänstadministratör för att fortsätta använda tjänsten.", "This homeserver has hit its Monthly Active User limit.": "Hemservern har nått sin månatliga gräns för användaraktivitet.", "This homeserver has exceeded one of its resource limits.": "Hemservern har överskridit en av sina resursgränser.", - "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte för hemservern har nått sin månatliga gräns för användaraktivitet. Kontakta din serviceadministratör för att fortsätta använda servicen.", - "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte för hemservern har överskridit en av sina resursgränser. Kontakta din serviceadministratör för att fortsätta använda servicen.", + "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte eftersom hemservern har nått sin månatliga gräns för användaraktivitet. Vänligen kontakta din serviceadministratör för att fortsätta använda tjänsten.", + "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte eftersom hemservern har överskridit en av sina resursgränser. Vänligen kontakta din serviceadministratör för att fortsätta använda tjänsten.", "Legal": "Juridiskt", - "Please contact your service administrator to continue using this service.": "Kontakta din serviceadministratör för att fortsätta använda servicen.", + "Please contact your service administrator to continue using this service.": "Vänligen kontakta din tjänstadministratör för att fortsätta använda tjänsten.", "This room has been replaced and is no longer active.": "Detta rum har ersatts och är inte längre aktivt.", "The conversation continues here.": "Konversationen fortsätter här.", "Only room administrators will see this warning": "Endast rumsadministratörer kommer att se denna varning", "This room is a continuation of another conversation.": "Detta rum är en fortsättning på en annan konversation.", "Click here to see older messages.": "Klicka här för att se äldre meddelanden.", - "Failed to upgrade room": "Det gick inte att uppgradera rum", + "Failed to upgrade room": "Misslyckades att uppgradera rummet", "The room upgrade could not be completed": "Rumsuppgraderingen kunde inte slutföras", "Upgrade this room to version %(version)s": "Uppgradera detta rum till version %(version)s", "Upgrade Room Version": "Uppgradera rumsversion", @@ -860,15 +860,15 @@ "Update any local room aliases to point to the new room": "Uppdatera lokala rumsalias att peka på det nya rummet", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Hindra användare från att prata i den gamla rumsversionen och posta ett meddelande som rekommenderar användare att flytta till det nya rummet", "Put a link back to the old room at the start of the new room so people can see old messages": "Sätta en länk tillbaka till det gamla rummet i början av det nya rummet så att folk kan se gamla meddelanden", - "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella utgående gruppsessionen i ett krypterat rum att överges", - "Unable to connect to Homeserver. Retrying...": "Det gick inte att ansluta till hemservern. Försöker igen…", + "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella externa gruppsessionen i ett krypterat rum att överges", + "Unable to connect to Homeserver. Retrying...": "Kunde inte ansluta till hemservern. Försöker igen…", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s satte huvudadressen för detta rum till %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s tog bort huvudadressen för detta rum.", "Add some now": "Lägg till några nu", "Please review and accept the policies of this homeserver:": "Granska och acceptera policyn för denna hemserver:", - "Before submitting logs, you must create a GitHub issue to describe your problem.": "Innan du skickar in loggar måste du skapa en GitHub-bugg för att beskriva problemet.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Innan du skickar in loggar måste du skapa ett GitHub-ärende för att beskriva problemet.", "Updating %(brand)s": "Uppdaterar %(brand)s", - "Open Devtools": "Öppna Devtools", + "Open Devtools": "Öppna utvecklingsverktyg", "Show developer tools": "Visa utvecklarverktyg", "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "Du är administratör för denna gemenskap. Du kommer inte kunna gå med igen utan en inbjudan från en annan administratör.", "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Filen '%(fileName)s' överstiger denna hemserverns storleksgräns för uppladdningar", @@ -929,7 +929,7 @@ "Show join/leave messages (invites/kicks/bans unaffected)": "Visa \"gå med\"/lämna-meddelanden (inbjudningar/kickningar/banningar opåverkat)", "Show avatar changes": "Visa avatarändringar", "Show display name changes": "Visa visningsnamnsändringar", - "Show read receipts sent by other users": "Visa läsindikationer som skickats av andra användare", + "Show read receipts sent by other users": "Visa läskvitton som skickats av andra användare", "Show avatars in user and room mentions": "Visa avatarer i användar- och rumsbenämningar", "Enable big emoji in chat": "Aktivera stora emojier i chatt", "Send typing notifications": "Skicka \"skriver\"-statusar", @@ -937,7 +937,7 @@ "Allow Peer-to-Peer for 1:1 calls": "Tillåt peer-to-peer-kommunikation för 1:1-samtal", "Messages containing my username": "Meddelanden som innehåller mitt användarnamn", "Messages containing @room": "Meddelanden som innehåller @room", - "Encrypted messages in one-to-one chats": "Krypterade meddelanden i privata chattar", + "Encrypted messages in one-to-one chats": "Krypterade meddelanden i en-till-en chattar", "Encrypted messages in group chats": "Krypterade meddelanden i gruppchattar", "Dog": "Hund", "Cat": "Katt", @@ -958,7 +958,7 @@ "Tree": "Träd", "Cactus": "Kaktus", "Mushroom": "Svamp", - "Globe": "Jordglob", + "Globe": "Jordklot", "Moon": "Måne", "Cloud": "Moln", "Fire": "Eld", @@ -980,7 +980,7 @@ "Hourglass": "Timglas", "Clock": "Klocka", "Gift": "Present", - "Light bulb": "Glödlampa", + "Light bulb": "Lampa", "Book": "Bok", "Pencil": "Penna", "Paperclip": "Gem", @@ -997,52 +997,52 @@ "Ball": "Boll", "Guitar": "Gitarr", "Trumpet": "Trumpet", - "Bell": "Ringklocka", + "Bell": "Bjällra", "Anchor": "Ankare", "Headphones": "Hörlurar", "Folder": "Mapp", - "Pin": "Knappnål", - "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett mail till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", - "Email Address": "Epostadress", - "Add an email address to configure email notifications": "Lägg till en epostadress för att konfigurera epostaviseringar", - "Unable to verify phone number.": "Det gick inte att verifiera telefonnumret.", + "Pin": "Häftstift", + "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett e-brev till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", + "Email Address": "E-postadress", + "Add an email address to configure email notifications": "Lägg till en e-postadress för att konfigurera e-postaviseringar", + "Unable to verify phone number.": "Kunde inte verifiera telefonnumret.", "Verification code": "Verifieringskod", "Phone Number": "Telefonnummer", "Profile picture": "Profilbild", "Display Name": "Visningsnamn", - "Set a new account password...": "Ange ett nytt lösenord för kontot...", - "Email addresses": "Epostadresser", + "Set a new account password...": "Ange ett nytt lösenord för kontot…", + "Email addresses": "E-postadresser", "Phone numbers": "Telefonnummer", "Language and region": "Språk och region", "Theme": "Tema", "Account management": "Kontohantering", "Deactivating your account is a permanent action - be careful!": "Inaktivering av ditt konto är en permanent åtgärd - var försiktig!", "General": "Allmänt", - "Credits": "Tack", + "Credits": "Medverkande", "For help with using %(brand)s, click here.": "För hjälp med att använda %(brand)s, klicka här.", - "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bot med knappen nedan.", - "Chat with %(brand)s Bot": "Chatta med %(brand)s Bot", - "Help & About": "Hjälp & Om", - "Bug reporting": "Felrapportering", + "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bott med knappen nedan.", + "Chat with %(brand)s Bot": "Chatta med %(brand)s-bott", + "Help & About": "Hjälp & om", + "Bug reporting": "Buggrapportering", "FAQ": "FAQ", "Versions": "Versioner", "Preferences": "Alternativ", "Timeline": "Tidslinje", "Room list": "Rumslista", "Autocomplete delay (ms)": "Autokompletteringsfördröjning (ms)", - "Voice & Video": "Röst & Video", + "Voice & Video": "Röst & video", "Room information": "Rumsinformation", "Internal room ID:": "Internt rums-ID:", "Room version": "Rumsversion", "Room version:": "Rumsversion:", "Developer options": "Utvecklaralternativ", "Room Addresses": "Rumsadresser", - "That doesn't look like a valid email address": "Det verkar inte vara en giltig epostadress", + "That doesn't look like a valid email address": "Det verkar inte vara en giltig e-postadress", "Next": "Nästa", "Clear status": "Rensa status", "Update status": "Uppdatera status", "Set status": "Ange status", - "Set a new status...": "Ange en ny status...", + "Set a new status...": "Ange en ny status…", "Hide": "Dölj", "This homeserver would like to make sure you are not a robot.": "Denna hemserver vill se till att du inte är en robot.", "Server Name": "Servernamn", @@ -1052,16 +1052,16 @@ "Sign in to your Matrix account on %(serverName)s": "Logga in med ditt Matrix-konto på %(serverName)s", "Change": "Ändra", "Create your Matrix account on %(serverName)s": "Skapa ditt Matrix-konto på %(serverName)s", - "Email (optional)": "Epost (valfritt)", + "Email (optional)": "E-post (valfritt)", "Phone (optional)": "Telefon (valfritt)", "Confirm": "Bekräfta", "Other servers": "Andra servrar", "Homeserver URL": "Hemserver-URL", "Identity Server URL": "Identitetsserver-URL", "Free": "Gratis", - "Join millions for free on the largest public server": "Bli medlem gratis på den största offentliga servern", + "Join millions for free on the largest public server": "Gå med miljontals användare gratis på den största publika servern", "Premium": "Premium", - "Premium hosting for organisations Learn more": "Premium-hosting för organisationer Läs mer", + "Premium hosting for organisations Learn more": "Premium-servervärd för organisationer Läs mer", "Other": "Annat", "Find other public servers or use a custom server": "Hitta andra offentliga servrar eller använd en anpassad server", "Your Matrix account on %(serverName)s": "Ditt Matrix-konto på %(serverName)s", @@ -1069,10 +1069,10 @@ "Your password has been reset.": "Ditt lösenord har återställts.", "Set a new password": "Ange ett nytt lösenord", "General failure": "Allmänt fel", - "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med epostadress.", + "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med e-postadress.", "Create account": "Skapa konto", "Registration has been disabled on this homeserver.": "Registrering har inaktiverats på denna hemserver.", - "Unable to query for supported registration methods.": "Det gick inte att fråga efter stödda registreringsmetoder.", + "Unable to query for supported registration methods.": "Kunde inte fråga efter stödda registreringsmetoder.", "Create your account": "Skapa ditt konto", "Retry": "Försök igen", "Don't ask again": "Fråga inte igen", @@ -1098,18 +1098,18 @@ "Modify widgets": "Ändra widgets", "Default role": "Standardroll", "Send messages": "Skicka meddelanden", - "Invite users": "Bjud in användare", + "Invite users": "Bjuda in användare", "Change settings": "Ändra inställningar", "Kick users": "Kicka användare", "Ban users": "Banna användare", "Remove messages": "Ta bort meddelanden", "Notify everyone": "Meddela alla", "Send %(eventType)s events": "Skicka %(eventType)s-händelser", - "Roles & Permissions": "Roller och behörigheter", + "Roles & Permissions": "Roller & behörigheter", "Enable encryption?": "Aktivera kryptering?", - "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "När det är aktiverat kan kryptering för ett rum inte inaktiveras. Meddelanden som skickas i ett krypterat rum kan inte ses av servern, utan endast av deltagarna i rummet. Att aktivera kryptering kan förhindra att många botar och bryggor att fungera korrekt. Läs mer om kryptering.", + "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "När det är aktiverat kan kryptering för ett rum inte inaktiveras. Meddelanden som skickas i ett krypterat rum kan inte ses av servern, utan endast av deltagarna i rummet. Att aktivera kryptering kan förhindra att många bottar och bryggor fungerar korrekt. Läs mer om kryptering.", "Encryption": "Kryptering", - "Once enabled, encryption cannot be disabled.": "Efter aktivering kan kryptering inte inaktiveras igen.", + "Once enabled, encryption cannot be disabled.": "Efter aktivering kan kryptering inte inaktiveras.", "Encrypted": "Krypterat", "Not now": "Inte nu", "Don't ask me again": "Fråga mig inte igen", @@ -1129,35 +1129,35 @@ "Verified!": "Verifierad!", "You've successfully verified this user.": "Du har verifierat den här användaren.", "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Säkra meddelanden med den här användaren är totalsträckskrypterade och kan inte läsas av tredje part.", - "Got It": "Jag fattar", - "Verify this user by confirming the following emoji appear on their screen.": "Verifiera den här användaren genom att bekräfta följande emoji visas på deras skärm.", + "Got It": "Uppfattat", + "Verify this user by confirming the following emoji appear on their screen.": "Verifiera den här användaren genom att bekräfta att följande emojier visas på deras skärm.", "Verify this user by confirming the following number appears on their screen.": "Verifiera den här användaren genom att bekräfta att följande nummer visas på deras skärm.", - "Unable to find a supported verification method.": "Det går inte att hitta en verifieringsmetod som stöds.", - "Delete Backup": "Ta bort säkerhetskopia", + "Unable to find a supported verification method.": "Kunde inte hitta en verifieringsmetod som stöds.", + "Delete Backup": "Radera säkerhetskopia", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Är du säker? Du kommer att förlora dina krypterade meddelanden om dina nycklar inte säkerhetskopieras ordentligt.", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Krypterade meddelanden är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", "Ignored users": "Ignorerade användare", - "Bulk options": "Bulkalternativ", + "Bulk options": "Massalternativ", "Accept all %(invitedRooms)s invites": "Acceptera alla %(invitedRooms)s inbjudningar", - "Security & Privacy": "Säkerhet och sekretess", + "Security & Privacy": "Säkerhet & sekretess", "Upgrade this room to the recommended room version": "Uppgradera detta rum till rekommenderad rumsversion", "Select the roles required to change various parts of the room": "Välj de roller som krävs för att ändra olika delar av rummet", "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Ändringar av vem som kan läsa historiken gäller endast för framtida meddelanden i detta rum. Synligheten för befintlig historik kommer att vara oförändrad.", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Meddelanden i detta rum är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", - "Failed to revoke invite": "Det gick inte att återkalla inbjudan", - "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Kunde inte återkalla inbjudan. Servern kan ha ett tillfälligt problem eller så har du inte tillräckligt med behörigheter för att återkalla inbjudan.", + "Failed to revoke invite": "Misslyckades att återkalla inbjudan", + "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Kunde inte återkalla inbjudan. Servern kan ha ett tillfälligt problem eller så har du inte tillräckliga behörigheter för att återkalla inbjudan.", "Revoke invite": "Återkalla inbjudan", "Invited by %(sender)s": "Inbjuden av %(sender)s", - "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Det uppstod ett fel vid uppdatering av rummets huvudadress. Det kanske inte tillåts av servern eller så inträffade ett tillfälligt fel.", + "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid uppdatering av rummets huvudadress. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", "Main address": "Huvudadress", "Error updating flair": "Fel vid uppdatering av emblem", - "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Det uppstod ett fel vid uppdatering av emblem för detta rum. Servern kanske inte tillåter det eller ett så inträffade tillfälligt fel.", - "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verifiera denna användare för att markera den som betrodd. Att kunna lita på användare ger en extra sinnesfrid när man använder totalsträckskrypterade meddelanden.", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Ett fel inträffade vid uppdatering av emblem för detta rum. Servern kanske inte tillåter det, eller ett så inträffade tillfälligt fel.", + "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verifiera denna användare för att markera den som betrodd. Att lita på användare ger en extra sinnesfrid när man använder totalsträckskrypterade meddelanden.", "A widget would like to verify your identity": "En widget vill verifiera din identitet", "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "En widget på %(widgetUrl)s vill verifiera din identitet. Genom att tillåta detta kommer widgeten att kunna verifiera ditt användar-ID, men inte agera som dig.", "Remember my selection for this widget": "Kom ihåg mitt val för den här widgeten", "Deny": "Neka", - "Unable to load backup status": "Det går inte att ladda backupstatus", + "Unable to load backup status": "Kunde inte ladda status för säkerhetskopia", "Guest": "Gäst", "Could not load user profile": "Kunde inte ladda användarprofil", "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Om du använder 'brödsmulor' eller inte (avatarer ovanför rumslistan)", @@ -1167,18 +1167,18 @@ "Composer": "Meddelandefält", "Key backup": "Nyckelsäkerhetskopiering", "Never lose encrypted messages": "Förlora aldrig krypterade meddelanden", - "Securely back up your keys to avoid losing them. Learn more.": "Säkerhetskopiera dina nycklar på ett säkert sätt för att undvika att förlora dem. Läs mer.", - "Failed to load group members": "Det gick inte att ladda gruppmedlemmar", + "Securely back up your keys to avoid losing them. Learn more.": "Säkerhetskopiera dina nycklar på ett säkert sätt för att undvika att förlora dem. Lär dig mer.", + "Failed to load group members": "Misslyckades att ladda gruppmedlemmar", "Maximize apps": "Maximera appar", "Join": "Gå med", "Rotate counter-clockwise": "Rotera moturs", "Rotate clockwise": "Rotera medurs", "Power level": "Behörighetsnivå", - "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Det gick inte att hitta profiler för de Matrix-IDn som anges nedan - vill du bjuda in dem ändå?", + "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Kunde inte hitta profiler för de Matrix-ID:n som listas nedan - vill du bjuda in dem ändå?", "GitHub issue": "GitHub-ärende", - "Notes": "Noteringar", - "You've previously used %(brand)s on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, %(brand)s needs to resync your account.": "Du har tidigare använt %(brand)s på %(host)s med lazy loading av medlemmar aktiverat. I den här versionen är lazy loading inaktiverat. Eftersom den lokala cachen inte är kompatibel mellan dessa två inställningar behöver %(brand)s synkronisera om ditt konto.", - "If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Om den andra versionen av %(brand)s fortfarande är öppen i en annan flik, stäng den eftersom användning av %(brand)s på samma värd med både lazy loading aktiverad och inaktiverad samtidigt kommer att orsaka problem.", + "Notes": "Anteckningar", + "You've previously used %(brand)s on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, %(brand)s needs to resync your account.": "Du har tidigare använt %(brand)s på %(host)s med fördröjd inladdning av medlemmar aktiverat. I den här versionen är fördröjd inladdning inaktiverat. Eftersom den lokala cachen inte är kompatibel mellan dessa två inställningar behöver %(brand)s synkronisera om ditt konto.", + "If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Om den andra versionen av %(brand)s fortfarande är öppen i en annan flik, stäng den eftersom användning av %(brand)s på samma värd med fördröjd inladdning både aktiverad och inaktiverad samtidigt kommer att orsaka problem.", "Incompatible local cache": "Inkompatibel lokal cache", "Clear cache and resync": "Töm cache och synkronisera om", "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s använder nu 3-5 gånger mindre minne, genom att bara ladda information om andra användare när det behövs. Vänta medan vi återsynkroniserar med servern!", @@ -1186,14 +1186,14 @@ "Manually export keys": "Exportera nycklar manuellt", "You'll lose access to your encrypted messages": "Du kommer att förlora åtkomst till dina krypterade meddelanden", "Are you sure you want to sign out?": "Är du säker på att du vill logga ut?", - "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Om du stöter på några fel eller har feedback du vill dela, vänligen meddela oss på GitHub.", + "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Om du stöter på några buggar eller har återkoppling du vill dela, vänligen meddela oss på GitHub.", "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "För att undvika dubbla ärenden, vänligen granska befintliga ärenden först (och lägg till +1) eller skapa ett nytt ärende om du inte hittar det.", - "Report bugs & give feedback": "Rapportera fel och ge feedback", + "Report bugs & give feedback": "Rapportera fel och ge återkoppling", "Go back": "Gå tillbaka", "Room Settings - %(roomName)s": "Rumsinställningar - %(roomName)s", "Sign out and remove encryption keys?": "Logga ut och ta bort krypteringsnycklar?", "A username can only contain lower case letters, numbers and '=_-./'": "Ett användarnamn får endast innehålla små bokstäver, siffror och '=_-./'", - "Checking...": "Kontrollerar...", + "Checking...": "Kontrollerar…", "To help us prevent this in future, please send us logs.": "För att hjälpa oss att förhindra detta i framtiden, vänligen skicka oss loggar.", "Missing session data": "Sessionsdata saknas", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Vissa sessionsdata, inklusive krypteringsnycklar för meddelanden, saknas. Logga ut och logga in för att åtgärda detta genom återställning av nycklarna från säkerhetskopia.", @@ -1237,31 +1237,31 @@ "Multiple integration managers": "Flera integrationshanterare", "Show hidden events in timeline": "Visa dolda händelser i tidslinjen", "Low bandwidth mode": "Läge för låg bandbredd", - "Send read receipts for messages (requires compatible homeserver to disable)": "Skicka läsindikationer för meddelanden (kräver kompatibel hemserver för att inaktivera)", + "Send read receipts for messages (requires compatible homeserver to disable)": "Skicka läskvitton för meddelanden (kräver kompatibel hemserver för att inaktivera)", "When rooms are upgraded": "När rum uppgraderas", "Accept to continue:": "Acceptera för att fortsätta:", "ID": "ID", "Public Name": "Offentligt namn", "Identity Server URL must be HTTPS": "URL för identitetsserver måste vara HTTPS", "Not a valid Identity Server (status code %(code)s)": "Inte en giltig identitetsserver (statuskod %(code)s)", - "Could not connect to Identity Server": "Det gick inte att ansluta till identitetsserver", - "Checking server": "Kontrollerar server", + "Could not connect to Identity Server": "Kunde inte ansluta till identitetsservern", + "Checking server": "Kontrollerar servern", "Change identity server": "Byt identitetsserver", - "Disconnect from the identity server and connect to instead?": "Koppla från identitetsservern och ansluta till istället?", + "Disconnect from the identity server and connect to instead?": "Koppla ifrån från identitetsservern och anslut till istället?", "Only continue if you trust the owner of the server.": "Fortsätt endast om du litar på serverns ägare.", - "Disconnect identity server": "Koppla från identitetsserver", - "Disconnect from the identity server ?": "Koppla från identitetsserver ?", - "Disconnect": "Koppla från", - "You are still sharing your personal data on the identity server .": "Du delar fortfarande dina personuppgifter på identitetsserver .", + "Disconnect identity server": "Koppla ifrån identitetsservern", + "Disconnect from the identity server ?": "Koppla ifrån från identitetsservern ?", + "Disconnect": "Koppla ifrån", + "You are still sharing your personal data on the identity server .": "Du delar fortfarande dina personuppgifter på identitetsservern .", "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "Vi rekommenderar att du tar bort dina e-postadresser och telefonnummer från identitetsservern innan du kopplar från.", - "Disconnect anyway": "Koppla från ändå", + "Disconnect anyway": "Koppla ifrån ändå", "Identity Server (%(server)s)": "Identitetsserver (%(server)s)", - "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Du använder för närvarande för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan ändra din identitetsserver nedan.", + "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Du använder för närvarande för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan byta din identitetsserver nedan.", "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Om du inte vill använda för att upptäcka och upptäckas av befintliga kontakter som du känner, ange en annan identitetsserver nedan.", "Identity Server": "Identitetsserver", "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "Du använder för närvarande inte en identitetsserver. Lägg till en nedan om du vill upptäcka och bli upptäckbar av befintliga kontakter som du känner.", - "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att koppla från din identitetsserver betyder att du inte kan upptäckas av andra användare och att du inte kommer att kunna bjuda in andra via epost eller telefon.", - "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att använda en identitetsserver är valfritt. Om du väljer att inte använda en identitetsserver kan du inte upptäckas av andra användare och inte heller bjuda in andra via epost eller telefon.", + "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att koppla ifrån din identitetsserver betyder att du inte kan upptäckas av andra användare och att du inte kommer att kunna bjuda in andra via e-post eller telefon.", + "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att använda en identitetsserver är valfritt. Om du väljer att inte använda en identitetsserver kan du inte upptäckas av andra användare och inte heller bjuda in andra via e-post eller telefon.", "Do not use an identity server": "Använd inte en identitetsserver", "Enter a new identity server": "Ange en ny identitetsserver", "Integration Manager": "Integrationshanterare", @@ -1272,23 +1272,23 @@ "View older messages in %(roomName)s.": "Visa äldre meddelanden i %(roomName)s.", "Uploaded sound": "Uppladdat ljud", "Sounds": "Ljud", - "Notification sound": "Notifikationsljud", + "Notification sound": "Aviseringsljud", "Reset": "Återställ", "Set a new custom sound": "Ställ in ett nytt anpassat ljud", "Upgrade the room": "Uppgradera rummet", "Enable room encryption": "Aktivera rumskryptering", "Revoke": "Återkalla", "Share": "Dela", - "Discovery options will appear once you have added an email above.": "Upptäcktsalternativ visas när du har lagt till en e-postadress ovan.", + "Discovery options will appear once you have added an email above.": "Upptäcktsalternativ kommer att visas när du har lagt till en e-postadress ovan.", "Remove %(email)s?": "Ta bort %(email)s?", "Remove %(phone)s?": "Ta bort %(phone)s?", - "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Ett textmeddelande har skickats till +%(msisdn)s. Ange verifieringskoden som det innehåller.", + "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Ett SMS har skickats till +%(msisdn)s. Ange verifieringskoden som det innehåller.", "Edit message": "Redigera meddelande", - "No recent messages by %(user)s found": "Inga nyliga meddelanden av %(user)s hittades", - "Try scrolling up in the timeline to see if there are any earlier ones.": "Pröva att scrolla upp i tidslinjen för att se om det finns några tidigare.", - "Remove recent messages by %(user)s": "Ta bort nyliga meddelanden av %(user)s", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "Du håller på att ta bort %(count)s meddelanden av %(user)s. Detta kan inte ångras. Vill du fortsätta?", - "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "För en stor mängd meddelanden kan det ta lite tid. Vänligen uppdatera inte din klient under tiden.", + "No recent messages by %(user)s found": "Inga nyliga meddelanden från %(user)s hittades", + "Try scrolling up in the timeline to see if there are any earlier ones.": "Pröva att skrolla upp i tidslinjen för att se om det finns några tidigare.", + "Remove recent messages by %(user)s": "Ta bort nyliga meddelanden från %(user)s", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "Du håller på att ta bort %(count)s meddelanden från %(user)s. Detta kan inte ångras. Vill du fortsätta?", + "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "För en stor mängd meddelanden kan det ta lite tid. Vänligen ladda inte om din klient under tiden.", "Remove %(count)s messages|other": "Ta bort %(count)s meddelanden", "Deactivate user?": "Inaktivera användare?", "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Vid inaktivering av användare loggas den ut och förhindras från att logga in igen. Den kommer dessutom att lämna alla rum den befinner sig i. Den här åtgärden kan inte ångras. Är du säker på att du vill inaktivera den här användaren?", @@ -1298,7 +1298,7 @@ "Italics": "Kursiv", "Strikethrough": "Genomstruken", "Code block": "Kodblock", - "Joining room …": "Gå med i rum …", + "Joining room …": "Går med i rummet …", "Loading …": "Laddar …", "Rejecting invite …": "Avvisar inbjudan …", "Join the conversation with an account": "Gå med i konversationen med ett konto", @@ -1307,11 +1307,11 @@ "Prompt before sending invites to potentially invalid matrix IDs": "Fråga innan inbjudningar skickas till potentiellt ogiltiga Matrix-ID:n", "Show all": "Visa alla", "reacted with %(shortName)s": "reagerade med %(shortName)s", - "Edited at %(date)s. Click to view edits.": "Ändrad %(date)s. Klicka för att visa ändringar.", - "edited": "ändrad", + "Edited at %(date)s. Click to view edits.": "Redigerat vid %(date)s. Klicka för att visa redigeringar.", + "edited": "redigerat", "Sign in to your Matrix account on ": "Logga in med ditt Matrix-konto på ", "Please install Chrome, Firefox, or Safari for the best experience.": "Installera Chrome, Firefox, eller Safari för den bästa upplevelsen.", - "Couldn't load page": "Det gick inte att ladda sidan", + "Couldn't load page": "Kunde inte ladda sidan", "Want more than a community? Get your own server": "Vill du ha mer än en gemenskap? Skaffa din egen server", "This homeserver does not support communities": "Denna hemserver stöder inte gemenskaper", "Explore": "Utforska", @@ -1326,9 +1326,9 @@ "Unexpected error resolving homeserver configuration": "Oväntat fel vid inläsning av hemserverkonfiguration", "Unexpected error resolving identity server configuration": "Oväntat fel vid inläsning av identitetsserverkonfiguration", "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Tillåt assistansservern turn.matrix.org för samtal som reserv när din hemserver inte erbjuder en (din IP-adress kommer delas under ett samtal)", - "Unable to load key backup status": "Det går inte att ladda status för nyckelsäkerhetskopiering", - "Restore from Backup": "Återställ från säkerhetskopiering", - "Backing up %(sessionsRemaining)s keys...": "Säkerhetskopierar %(sessionsRemaining)s nycklar...", + "Unable to load key backup status": "Kunde inte ladda status för nyckelsäkerhetskopiering", + "Restore from Backup": "Återställ från säkerhetskopia", + "Backing up %(sessionsRemaining)s keys...": "Säkerhetskopierar %(sessionsRemaining)s nycklar…", "All keys backed up": "Alla nycklar säkerhetskopierade", "Add Email Address": "Lägg till e-postadress", "Add Phone Number": "Lägg till telefonnummer", @@ -1351,17 +1351,17 @@ "Match system theme": "Matcha systemtema", "Decline (%(counter)s)": "Avvisa (%(counter)s)", "not found": "hittades inte", - "Connecting to integration manager...": "Ansluter till integrationshanterare...", - "Cannot connect to integration manager": "Det går inte att ansluta till integrationshanterare", + "Connecting to integration manager...": "Ansluter till integrationshanterare…", + "Cannot connect to integration manager": "Kan inte ansluta till integrationshanteraren", "The integration manager is offline or it cannot reach your homeserver.": "Integrationshanteraren är offline eller kan inte nå din hemserver.", "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare (%(serverName)s) för att hantera bottar, widgets och dekalpaket.", "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare för att hantera bottar, widgets och dekalpaket.", "Manage integrations": "Hantera integrationer", - "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka ruminbjudningar och ställa in behörighetsnivåer via ditt konto.", - "Close preview": "Stäng förhandsvisning", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka rumsinbjudningar och ställa in behörighetsnivåer å dina vägnar.", + "Close preview": "Stäng förhandsgranskning", "Room %(name)s": "Rum %(name)s", "Recent rooms": "Senaste rummen", - "Loading room preview": "Laddar förhandsvisning av rummet", + "Loading room preview": "Laddar förhandsgranskning av rummet", "Re-join": "Gå med igen", "Try to join anyway": "Försök att gå med ändå", "Join the discussion": "Gå med i diskussionen", @@ -1373,13 +1373,13 @@ "You're previewing %(roomName)s. Want to join it?": "Du förhandsgranskar %(roomName)s. Vill du gå med i det?", "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s kan inte förhandsvisas. Vill du gå med i det?", "This room doesn't exist. Are you sure you're at the right place?": "Detta rum finns inte. Är du säker på att du är på rätt plats?", - "Try again later, or ask a room admin to check if you have access.": "Försök igen senare, eller be en rumsadministratör om att kontrollera om du har åtkomst.", - "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s returnerades när du försökte komma åt rummet. Om du tror att du ser det här meddelandet felaktigt, vänligen skicka in en felrapport.", - "Failed to connect to integration manager": "Det gick inte att ansluta till integrationshanterare", + "Try again later, or ask a room admin to check if you have access.": "Försök igen senare, eller be en rumsadministratör att kolla om du har åtkomst.", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s returnerades när du försökte komma åt rummet. Om du tror att du ser det här meddelandet felaktigt, vänligen skicka in en buggrapport.", + "Failed to connect to integration manager": "Kunde inte ansluta till integrationshanterare", "React": "Reagera", "Message Actions": "Meddelandeåtgärder", "Show image": "Visa bild", - "You have ignored this user, so their message is hidden. Show anyways.": "Du har ignorerat denna användare, så deras meddelande är dolt. Visa ändå.", + "You have ignored this user, so their message is hidden. Show anyways.": "Du har ignorerat den här användaren, så dess meddelande är dolt. Visa ändå.", "You verified %(name)s": "Du verifierade %(name)s", "You cancelled verifying %(name)s": "Du avbröt verifiering av %(name)s", "%(name)s cancelled verifying": "%(name)s avbröt verifiering", @@ -1391,18 +1391,18 @@ "You sent a verification request": "Du skickade en verifieringsbegäran", "Reactions": "Reaktioner", " reacted with %(content)s": " reagerade med %(content)s", - "Frequently Used": "Ofta använd", - "Smileys & People": "Smileys & Människor", - "Animals & Nature": "Djur & Natur", - "Food & Drink": "Mat & Dryck", + "Frequently Used": "Ofta använda", + "Smileys & People": "Smileys & personer", + "Animals & Nature": "Djur & natur", + "Food & Drink": "Mat & dryck", "Activities": "Aktiviteter", - "Travel & Places": "Resor & Platser", + "Travel & Places": "Resor & platser", "Objects": "Objekt", "Symbols": "Symboler", "Flags": "Flaggor", "Quick Reactions": "Snabbreaktioner", "Cancel search": "Avbryt sökningen", - "Any of the following data may be shared:": "Någon av följande data kan delas:", + "Any of the following data may be shared:": "Vissa av följande data kan delas:", "Your display name": "Ditt visningsnamn", "Your avatar URL": "Din avatar-URL", "Your user ID": "Ditt användar-ID", @@ -1410,13 +1410,13 @@ "%(brand)s URL": "%(brand)s-URL", "Room ID": "Rums-ID", "Widget ID": "Widget-ID", - "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Att använda denna widget kan dela data med %(widgetDomain)s och din Integrationshanterare.", + "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Att använda denna widget kan dela data med %(widgetDomain)s och din integrationshanterare.", "Using this widget may share data with %(widgetDomain)s.": "Att använda denna widget kan dela data med %(widgetDomain)s.", "Widgets do not use message encryption.": "Widgets använder inte meddelandekryptering.", "Widget added by": "Widget tillagd av", - "This widget may use cookies.": "Denna widget kan använda cookies.", + "This widget may use cookies.": "Denna widget kan använda kakor.", "More options": "Fler alternativ", - "Please create a new issue on GitHub so that we can investigate this bug.": "Vänligen skapa ett nytt ärende på GitHub så att vi kan undersöka detta fel.", + "Please create a new issue on GitHub so that we can investigate this bug.": "Vänligen skapa ett nytt ärende på GitHub så att vi kan undersöka denna bugg.", "Rotate Left": "Rotera vänster", "Rotate Right": "Rotera höger", "Language Dropdown": "Språkmeny", @@ -1424,30 +1424,30 @@ "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)sgjorde inga ändringar", "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)sgjorde inga ändringar %(count)s gånger", "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)sgjorde inga ändringar", - "e.g. my-room": "t.ex. mitt rum", + "e.g. my-room": "t.ex. mitt-rum", "Some characters not allowed": "Vissa tecken är inte tillåtna", - "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Använd en identitetsserver för att bjuda in via epost. Använd standard (%(defaultIdentityServerName)s) eller hantera i Inställningar.", - "Use an identity server to invite by email. Manage in Settings.": "Använd en identitetsserver för att bjuda in via epost. Hantera i Inställningar.", + "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Använd en identitetsserver för att bjuda in via e-post. Använd förval (%(defaultIdentityServerName)s) eller hantera i inställningarna.", + "Use an identity server to invite by email. Manage in Settings.": "Använd en identitetsserver för att bjuda in via e-post. Hantera i inställningarna.", "Close dialog": "Stäng dialogrutan", - "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Berätta vad som gick fel eller, bättre, skapa ett GitHub-ärende som beskriver problemet.", + "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Berätta vad som gick fel, eller skapa ännu hellre ett GitHub-ärende som beskriver problemet.", "View Servers in Room": "Visa servrar i rum", "Recent Conversations": "Senaste konversationerna", "Suggestions": "Förslag", "Show more": "Visa mer", "Direct Messages": "Direktmeddelanden", "Go": "Gå", - "Waiting for partner to confirm...": "Väntar på att kompanjon ska bekräfta...", + "Waiting for partner to confirm...": "Väntar på att partnern ska bekräfta…", "Incoming Verification Request": "Inkommande verifieringsbegäran", "Integrations are disabled": "Integrationer är inaktiverade", - "Enable 'Manage Integrations' in Settings to do this.": "Aktivera \"Hantera integrationer\" i Inställningar för att göra detta.", - "Integrations not allowed": "Integrationer inte tillåtna", - "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Ditt %(brand)s tillåter dig inte att använda en Integrationshanterare för att göra detta. Vänligen kontakta en administratör.", + "Enable 'Manage Integrations' in Settings to do this.": "Aktivera \"Hantera integrationer\" i inställningarna för att göra detta.", + "Integrations not allowed": "Integrationer är inte tillåtna", + "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Din %(brand)s tillåter dig inte att använda en integrationshanterare för att göra detta. Vänligen kontakta en administratör.", "Your homeserver doesn't seem to support this feature.": "Din hemserver verkar inte stödja den här funktionen.", - "Message edits": "Meddelandedigeringar", - "Preview": "Förhandsvisa", + "Message edits": "Meddelanderedigeringar", + "Preview": "Förhandsgranska", "The message you are trying to send is too large.": "Meddelandet du försöker skicka är för stort.", - "Find others by phone or email": "Hitta andra via telefon eller epost", - "Be found by phone or email": "Bli hittad via telefon eller epost", + "Find others by phone or email": "Hitta andra via telefon eller e-post", + "Be found by phone or email": "Bli hittad via telefon eller e-post", "Terms of Service": "Användarvillkor", "To continue you need to accept the terms of this service.": "För att fortsätta måste du acceptera villkoren för denna tjänst.", "Service": "Tjänst", @@ -1470,24 +1470,24 @@ "Unknown (user, session) pair:": "Okänt (användare, session)-par:", "Session already verified!": "Sessionen är redan verifierad!", "WARNING: Session already verified, but keys do NOT MATCH!": "VARNING: Sessionen har redan verifierats, men nycklarna MATCHAR INTE!", - "Unable to revoke sharing for email address": "Det gick inte att återkalla delning för e-postadress", - "Unable to share email address": "Det gick inte att dela e-postadress", + "Unable to revoke sharing for email address": "Kunde inte återkalla delning för e-postadress", + "Unable to share email address": "Kunde inte dela e-postadress", "Your email address hasn't been verified yet": "Din e-postadress har inte verifierats än", "Click the link in the email you received to verify and then click continue again.": "Klicka på länken i e-postmeddelandet för att bekräfta och klicka sedan på Fortsätt igen.", "Verify the link in your inbox": "Verifiera länken i din inkorg", "Complete": "Färdigställ", - "Unable to revoke sharing for phone number": "Det gick inte att återkalla delning för telefonnummer", - "Unable to share phone number": "Det gick inte att dela telefonnummer", - "Please enter verification code sent via text.": "Ange verifieringskod skickad via textmeddelande.", - "Discovery options will appear once you have added a phone number above.": "Upptäcktsalternativ visas när du har lagt till ett telefonnummer ovan.", + "Unable to revoke sharing for phone number": "Kunde inte återkalla delning för telefonnummer", + "Unable to share phone number": "Kunde inte dela telefonnummer", + "Please enter verification code sent via text.": "Ange verifieringskod skickad via SMS.", + "Discovery options will appear once you have added a phone number above.": "Upptäcktsalternativ kommer att visas när du har lagt till ett telefonnummer ovan.", "Verify session": "Verifiera sessionen", "Session name": "Sessionsnamn", "Session key": "Sessionsnyckel", - "Automatically invite users": "Bjud in användare automatiskt", + "Automatically invite users": "Bjuda in användare automatiskt", "Upgrade private room": "Uppgradera privat rum", - "Upgrade public room": "Uppgradera publikt rum", + "Upgrade public room": "Uppgradera offentligt rum", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Att uppgradera ett rum är en avancerad åtgärd och rekommenderas vanligtvis när ett rum är instabilt på grund av buggar, saknade funktioner eller säkerhetsproblem.", - "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Detta påverkar vanligtvis bara hur rummet bearbetas på servern. Om du har problem med %(brand)s, rapportera ett fel.", + "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Detta påverkar vanligtvis bara hur rummet bearbetas på servern. Om du har problem med %(brand)s, vänligen rapportera ett fel.", "You'll upgrade this room from to .": "Du kommer att uppgradera detta rum från till .", "This will allow you to return to your account after signing out, and sign in on other sessions.": "Detta gör att du kan återgå till ditt konto efter att du har loggat ut, och logga in på andra sessioner.", "Help": "Hjälp", @@ -1504,7 +1504,7 @@ "Navigation": "Navigering", "Calls": "Samtal", "Room List": "Rumslista", - "Autocomplete": "Komplettera automatiskt", + "Autocomplete": "Autokomplettera", "Alt": "Alt", "Alt Gr": "Alt Gr", "Shift": "Shift", @@ -1522,12 +1522,12 @@ "Space": "Mellanslag", "End": "End", "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Du har blivit utloggad från alla dina sessioner och kommer inte längre att motta pushnotiser. För att återaktivera pushnotiser, logga in igen på varje enhet.", - "Use Single Sign On to continue": "Använd single sign-on för att fortsätta", - "Confirm adding this email address by using Single Sign On to prove your identity.": "Bekräfta tilläggning av e-postadressen genom att använda single sign-on för att bevisa din identitet.", - "Single Sign On": "Single sign-on", + "Use Single Sign On to continue": "Använd externt konto för att fortsätta", + "Confirm adding this email address by using Single Sign On to prove your identity.": "Bekräfta tilläggning av e-postadressen genom att använda externt konto för att bevisa din identitet.", + "Single Sign On": "Externt konto", "Confirm adding email": "Bekräfta tilläggning av e-postadressen", "Click the button below to confirm adding this email address.": "Klicka på knappen nedan för att bekräfta tilläggning av e-postadressen.", - "Confirm adding this phone number by using Single Sign On to prove your identity.": "Bekräfta tilläggning av telefonnumret genom att använda single sign-on för att bevisa din identitet.", + "Confirm adding this phone number by using Single Sign On to prove your identity.": "Bekräfta tilläggning av telefonnumret genom att använda externt konto för att bevisa din identitet.", "Confirm adding phone number": "Bekräfta tilläggning av telefonnumret", "Click the button below to confirm adding this phone number.": "Klicka på knappen nedan för att bekräfta tilläggning av telefonnumret.", "Are you sure you want to cancel entering passphrase?": "Är du säker på att du vill avbryta inmatning av lösenfrasen?", @@ -1665,5 +1665,732 @@ "IRC display name width": "Bredd för IRC-visningsnamn", "Enable experimental, compact IRC style layout": "Aktivera experimentellt kompakt IRC-likt arrangemang", "Uploading logs": "Laddar upp loggar", - "Downloading logs": "Laddar ner loggar" + "Downloading logs": "Laddar ner loggar", + "My Ban List": "Min bannlista", + "This is your list of users/servers you have blocked - don't leave the room!": "Det här är din lista med användare och server du har blockerat - lämna inte rummet!", + "Unknown caller": "Okänd uppringare", + "Incoming voice call": "Inkommande röstsamtal", + "Incoming video call": "Inkommande videosamtal", + "Incoming call": "Inkommande samtal", + "Verify this session by completing one of the following:": "Verifiera den här sessionen genom att fullfölja en av följande:", + "Scan this unique code": "Skanna den här unika koden", + "or": "eller", + "Compare unique emoji": "Jämför unika emojier", + "Compare a unique set of emoji if you don't have a camera on either device": "Jämför en unik uppsättning emojier om du inte har en kamera på någon av enheterna", + "Start": "Starta", + "Confirm the emoji below are displayed on both sessions, in the same order:": "Bekräfta att emojierna nedan visas på båda sessionerna, is samma ordning:", + "Verify this session by confirming the following number appears on its screen.": "Verifiera den här sessionen genom att bekräfta att följande nummer visas på dess skärm.", + "Waiting for your other session, %(deviceName)s (%(deviceId)s), to verify…": "Väntar på att din andra session, %(deviceName)s (%(deviceId)s), ska verifiera…", + "Waiting for your other session to verify…": "Väntar på att din andra session ska verifiera…", + "Waiting for %(displayName)s to verify…": "Väntar på att %(displayName)s ska verifiera…", + "Cancelling…": "Avbryter…", + "They match": "De matchar", + "They don't match": "De matchar inte", + "To be secure, do this in person or use a trusted way to communicate.": "För att vara säker, gör det här personligen eller använd en annan pålitlig kommunikationsform.", + "Lock": "Lås", + "Your server isn't responding to some requests.": "Din server svarar inte på vissa förfrågningar.", + "From %(deviceName)s (%(deviceId)s)": "Från %(deviceName)s (%(deviceId)s)", + "This bridge was provisioned by .": "Den här bryggan tillhandahålls av .", + "This bridge is managed by .": "Den här bryggan tillhandahålls av .", + "Workspace: %(networkName)s": "Arbetsyta: %(networkName)s", + "Channel: %(channelName)s": "Kanal: %(channelName)s", + "Show less": "Visa mindre", + "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Att byta lösenord återställer just nu alla krypteringsnycklar på alla sessioner, vilket gör krypterad chatthistorik oläslig om du inte först exporterar dina rumsnycklar och sedan importerar dem igen efteråt. Detta kommer att förbättras i framtiden.", + "Your homeserver does not support cross-signing.": "Din hemserver stöder inte korssignering.", + "Cross-signing and secret storage are ready for use.": "Korssignering och hemlig lagring är klara att använda.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Korssignering är klar att använda, men hemlig lagring används just nu inte för att säkerhetskopiera dina nycklar.", + "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Ditt konto har en korssigneringsidentitet i hemlig lagring, men den är inte betrodd av den här sessionen än.", + "Cross-signing and secret storage are not yet set up.": "Korssignering och hemlig lagring har inte blivit uppsatta än.", + "Reset cross-signing and secret storage": "Återställ korssignering och hemlig lagring", + "Bootstrap cross-signing and secret storage": "Sätt upp korssignering och hemlig lagring", + "well formed": "välformaterad", + "unexpected type": "oväntad typ", + "Cross-signing public keys:": "Publika nycklar för korssignering:", + "in memory": "i minne", + "Cross-signing private keys:": "Privata nycklar för korssignering:", + "in secret storage": "i hemlig lagring", + "Master private key:": "Privat huvudnyckel:", + "cached locally": "cachad lokalt", + "not found locally": "inte hittad lokalt", + "Self signing private key:": "Privat nyckel för självsignering:", + "User signing private key:": "Privat nyckel för användarsignering:", + "Session backup key:": "Sessionssäkerhetskopieringsnyckel:", + "Secret storage public key:": "Publik nyckel för hemlig lagring:", + "in account data": "i kontodata", + "Homeserver feature support:": "Hemserverns funktionsstöd:", + "exists": "existerar", + "Your homeserver does not support session management.": "Din hemservers stöder inte sessionshantering.", + "Unable to load session list": "Kunde inte ladda sessionslistan", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Bekräfta radering av dessa sessioner genom att använda externt konto för att bekräfta din identitet.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Bekräfta radering av denna session genom att använda externt konto för att bekräfta din identitet.", + "Confirm deleting these sessions": "Bekräfta radering av dessa sessioner", + "Click the button below to confirm deleting these sessions.|other": "Klicka på knappen nedan för att bekräfta radering av dessa sessioner.", + "Click the button below to confirm deleting these sessions.|one": "Klicka på knappen nedan för att bekräfta radering av denna session.", + "Delete sessions|other": "Radera sessioner", + "Delete sessions|one": "Radera session", + "Delete %(count)s sessions|other": "Radera %(count)s sessioner", + "Delete %(count)s sessions|one": "Radera %(count)s session", + "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Verifiera individuellt varje session som används av en användare för att markera den som betrodd, och lita inte på korssignerade enheter.", + "Securely cache encrypted messages locally for them to appear in search results, using ": "Cacha krypterade meddelanden säkert lokalt för att de ska visas i sökresultat, med hjälp av ", + " to store messages from ": " för att lagra meddelanden från ", + "rooms.": "rum.", + "Manage": "Hantera", + "Securely cache encrypted messages locally for them to appear in search results.": "Cachar krypterade meddelanden säkert lokalt för att de ska visas i sökresultat.", + "Enable": "Aktivera", + "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s saknar vissa komponenter som krävs som krävs för att säkert cacha krypterade meddelanden lokalt. Om du vill experimentera med den här funktionen, bygg en anpassad %(brand)s Skrivbord med sökkomponenter tillagda.", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s kan inte säkert cacha krypterade meddelanden lokalt när den kör i en webbläsare. Använd %(brand)s Skrivbord för att krypterade meddelanden ska visas i sökresultaten.", + "This session is backing up your keys. ": "Den här sessionen säkerhetskopierar dina nycklar. ", + "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "Den här servern säkerhetskopierar inte dina nycklar, men du har en existerande säkerhetskopia du kan återställa ifrån och lägga till till i framtiden.", + "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Anslut den här sessionen till nyckelsäkerhetskopiering innan du loggar ut för att undvika att du blir av med nycklar som kanske bara finns på den här sessionen.", + "Connect this session to Key Backup": "Anslut den här sessionen till nyckelsäkerhetskopiering", + "not stored": "inte lagrad", + "Backup has a valid signature from this user": "Säkerhetskopian har en giltig signatur från den här användaren", + "Backup has a invalid signature from this user": "Säkerhetskopian har en ogiltig signatur från den här användaren", + "Backup has a signature from unknown user with ID %(deviceId)s": "Säkerhetskopian har en signatur från en okänd användare med ID %(deviceId)s", + "Backup has a signature from unknown session with ID %(deviceId)s": "Säkerhetskopian har en signatur från en okänd session med ID %(deviceId)s", + "Backup has a valid signature from this session": "Säkerhetskopian har en giltig signatur från den här sessionen", + "Backup has an invalid signature from this session": "Säkerhetskopian har en ogiltig signatur från den här sessionen", + "Backup has a valid signature from verified session ": "Säkerhetskopian har en giltig signatur från den verifierade sessionen ", + "Backup has a valid signature from unverified session ": "Säkerhetskopian har en giltig signatur från den overifierade sessionen ", + "Backup has an invalid signature from verified session ": "Säkerhetskopian har en ogiltig signatur från den verifierade sessionen ", + "Backup has an invalid signature from unverified session ": "Säkerhetskopian har en ogiltig signatur från den overifierade sessionen ", + "Backup is not signed by any of your sessions": "Säkerhetskopian är inte signerad av någon av dina sessioner", + "This backup is trusted because it has been restored on this session": "Den här säkerhetskopian är betrodd för att den har återställts på den här sessionen", + "Backup version: ": "Säkerhetskopiaversion: ", + "Algorithm: ": "Algoritm: ", + "Backup key stored: ": "Säkerhetskopianyckel lagrad: ", + "Your keys are not being backed up from this session.": "Dina nycklar säkerhetskopieras inte från den här sessionen.", + "Back up your keys before signing out to avoid losing them.": "Säkerhetskopiera dina nycklar innan du loggar ut för att undvika att du blir av med dem.", + "Start using Key Backup": "Börja använda nyckelsäkerhetskopiering", + "Clear notifications": "Rensa aviseringar", + "There are advanced notifications which are not shown here.": "Det finns avancerade aviseringar som inte visas här.", + "You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "Du kanske har konfigurerat dem i en annan klient än %(brand)s. Du kan inte ändra dem i %(brand)s men de används ändå.", + "Enable desktop notifications for this session": "Aktivera skrivbordsaviseringar för den här sessionen", + "Enable audible notifications for this session": "Aktivera ljudaviseringar för den här sessionen", + "Upgrade to your own domain": "Uppgradera till din egen domän", + "Terms of service not accepted or the identity server is invalid.": "Användarvillkoren accepterades inte eller identitetsservern är inte giltig.", + "The identity server you have chosen does not have any terms of service.": "Identitetsservern du har valt har inga användarvillkor.", + "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "Du bör ta bort din personliga information från identitetsservern innan du kopplar ifrån. Tyvärr är identitetsservern för närvarande offline eller kan inte nås.", + "You should:": "Du bör:", + "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "Kolla dina webbläsartillägg efter någonting som kanske blockerar identitetsservern (t.ex. Privacy Badger)", + "contact the administrators of identity server ": "kontakta administratören för identitetsservern ", + "wait and try again later": "vänta och försöka igen senare", + "New version available. Update now.": "Ny version tillgänglig. Uppdatera nu.", + "Hey you. You're the best!": "Hallå där. Du är bäst!", + "Size must be a number": "Storleken måste vara ett nummer", + "Custom font size can only be between %(min)s pt and %(max)s pt": "Anpassad teckenstorlek kan bara vara mellan %(min)s pt och %(max)s pt", + "Use between %(min)s pt and %(max)s pt": "Använd mellan %(min)s pt och %(max)s pt", + "Invalid theme schema.": "Ogiltigt temaschema.", + "Error downloading theme information.": "Fel vid nedladdning av temainformation.", + "Theme added!": "Tema tillagt!", + "Custom theme URL": "Anpassad tema-URL", + "Add theme": "Lägg till tema", + "Message layout": "Meddelandearrangemang", + "Compact": "Kompakt", + "Modern": "Modernt", + "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Sätt namnet för ett teckensnitt installerat på ditt system så kommer %(brand)s att försöka använda det.", + "Customise your appearance": "Anpassa ditt utseende", + "Appearance Settings only affect this %(brand)s session.": "Utseende inställningar påverkar bara den här %(brand)s-sessionen.", + "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Ditt lösenord ändrades framgångsrikt. Du kommer inte motta pushnotiser på andra sessioner till du loggar in på dem igen", + "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Samtyck till identitetsserverns (%(serverName)s) användarvillkor för att låta dig själv vara upptäckbar med e-postadress eller telefonnummer.", + "Clear cache and reload": "Rensa cache och ladda om", + "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy.": "För att rapportera ett Matrix-relaterat säkerhetsproblem, vänligen läs Matrix.orgs riktlinjer för säkerhetspublicering.", + "Keyboard Shortcuts": "Tangentbordsgenvägar", + "Customise your experience with experimental labs features. Learn more.": "Anpassa din upplevelse med experimentella funktioner. Lär dig mer.", + "Ignored/Blocked": "Ignorerade/blockerade", + "Error adding ignored user/server": "Fel vid tilläggning av användare/server", + "Something went wrong. Please try again or view your console for hints.": "Någonting gick fel. Vänligen försök igen eller kolla i din konsol efter ledtrådar.", + "Error subscribing to list": "Fel vid prenumeration på listan", + "Please verify the room ID or address and try again.": "Vänligen verifiera rummets ID eller adress och försök igen.", + "Error removing ignored user/server": "Fel vid borttagning av ignorerad användare/server", + "Error unsubscribing from list": "Fel vid avprenumeration från listan", + "Please try again or view your console for hints.": "Vänligen försök igen eller kolla din konsol efter ledtrådar.", + "None": "Ingen", + "Ban list rules - %(roomName)s": "Bannlistregler - %(roomName)s", + "Server rules": "Serverregler", + "User rules": "Användarregler", + "You have not ignored anyone.": "Du har inte ignorerat någon.", + "You are currently ignoring:": "Du ignorerar just nu:", + "You are not subscribed to any lists": "Du prenumererar inte på några listor", + "Unsubscribe": "Avprenumerera", + "View rules": "Visa regler", + "You are currently subscribed to:": "Du prenumerera just nu på:", + "⚠ These settings are meant for advanced users.": "⚠ Dessa inställningar är till för avancerade användare.", + "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Lägg till användare och servrar du vill ignorera här. Använd asterisker för att få %(brand)s att matchar vilka tecken som helt. Till exempel, @bot:* kommer att ignorera alla användare med namnet 'bot' på vilken server som helst.", + "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignorering av användare görs genom bannlistor som innehåller regler för vilka som bannas. Att prenumerera på en bannlista betyder att användare/servrar blockerade av den listan kommer att döljas för dig.", + "Personal ban list": "Personlig bannlista", + "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Din personliga bannlista innehåller alla användare/servrar du personligen inte vill se meddelanden ifrån. Efter att du ignorerar din första användare/server så kommer ett nytt rom att dyka upp i din rumslista med namnet 'Min bannlista' - stanna i det här rummet för att hålla bannlistan verksam.", + "Server or user ID to ignore": "Server- eller användar-ID att ignorera", + "eg: @bot:* or example.org": "t.ex.: @bot:* eller example.org", + "Subscribed lists": "Prenumererade listor", + "Subscribing to a ban list will cause you to join it!": "Att prenumerera till en bannlista kommer att få dig att gå med i den!", + "If this isn't what you want, please use a different tool to ignore users.": "Om det här inte är det du vill, använd ett annat verktyg för att ignorera användare.", + "Room ID or address of ban list": "Rums-ID eller adress för bannlista", + "Subscribe": "Prenumerera", + "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", + "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", + "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", + "Session ID:": "Sessions-ID:", + "Session key:": "Sessionsnyckel:", + "Message search": "Meddelandesök", + "Cross-signing": "Korssignering", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Din serveradministratör har inaktiverat totalsträckskryptering som förval för privata rum och direktmeddelanden.", + "Where you’re logged in": "Var du har loggat in", + "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Hantera namn för och logga ut ur dina sessioner nedan eller verifiera dem i din användarprofil.", + "A session's public name is visible to people you communicate with": "En sessions publika namn visas för personer du kommunicerar med", + "This room is bridging messages to the following platforms. Learn more.": "Det här rummet bryggar meddelanden till följande plattformar. Lär dig mer.", + "This room isn’t bridging messages to any platforms. Learn more.": "Det här rummet bryggar inte meddelanden till några plattformar. Lär dig mer.", + "Bridges": "Bryggor", + "Browse": "Bläddra", + "Error changing power level requirement": "Fel vid ändring av behörighetskrav", + "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "Ett fel inträffade vid ändring av rummets krav på behörighetsnivå. Försäkra att du har tillräcklig behörighet och försök igen.", + "Error changing power level": "Fel vid ändring av behörighetsnivå", + "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "Ett fel inträffade vid ändring av användarens behörighetsnivå. Försäkra att du har tillräcklig behörighet och försök igen.", + "To link to this room, please add an address.": "För att länka till det här rummet, lägg till en adress.", + "This user has not verified all of their sessions.": "Den här användaren har inte verifierat alla sina sessioner.", + "You have not verified this user.": "Du har inte verifierat den här användaren.", + "You have verified this user. This user has verified all of their sessions.": "Du har verifierat den här användaren. Den här användaren har verifierat alla sina sessioner.", + "Someone is using an unknown session": "Någon använder en okänd session", + "This room is end-to-end encrypted": "Det här rummet är totalsträckskrypterat", + "Everyone in this room is verified": "Alla i det här rummet är verifierade", + "Mod": "Mod", + "Your key share request has been sent - please check your other sessions for key share requests.": "Din nyckeldelningsbegäran har skickats - vänligen kolla dina andra sessioner för nyckeldelningsbegäran.", + "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Nyckeldelningsbegäran skickas till dina andra sessioner automatiskt. Om du avvisade eller avfärdade nyckeldelningsbegäran på dina andra sessioner, klicka här för att begära nycklarna för den här sessionen igen.", + "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Om dina andra sessioner inte har nyckeln för det här meddelandet så kommer du inte kunna avkryptera dem.", + "Re-request encryption keys from your other sessions.": "Återförfråga krypteringsnycklar från dina andra sessioner.", + "This message cannot be decrypted": "Det här meddelandet kan inte avkrypteras", + "Encrypted by an unverified session": "Krypterat av en overifierad session", + "Unencrypted": "Okrypterat", + "Encrypted by a deleted session": "Krypterat av en raderad session", + "The authenticity of this encrypted message can't be guaranteed on this device.": "Det krypterade meddelandets äkthet kan inte garanteras på den här enheten.", + "Scroll to most recent messages": "Skrolla till de senaste meddelandena", + "Emoji picker": "Emojiväljare", + "Send a reply…": "Skicka ett svar…", + "Send a message…": "Skicka ett meddelande…", + "No recently visited rooms": "Inga nyligen besökta rum", + "People": "Personer", + "Add room": "Lägg till rum", + "Explore community rooms": "Utforska gemenskapens rum", + "Explore public rooms": "Utforska offentliga rum", + "Custom Tag": "Anpassad etikett", + "Can't see what you’re looking for?": "Kan du inte se det du letar efter?", + "Explore all public rooms": "Utforska alla offentliga rum", + "%(count)s results|other": "%(count)s resultat", + "You were kicked from %(roomName)s by %(memberName)s": "Du blev kickad från %(roomName)s av %(memberName)s", + "Reason: %(reason)s": "Anledning: %(reason)s", + "Forget this room": "Glöm det här rummet", + "You were banned from %(roomName)s by %(memberName)s": "Du blev bannad från %(roomName)s av %(memberName)s", + "Something went wrong with your invite to %(roomName)s": "Någonting gick fel med din inbjudan till %(roomName)s", + "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "Ett fel (%(errcode)s) returnerades vid försöket att validera din inbjudan. Du kan försöka att ge den här informationen till rumsadministratören.", + "You can only join it with a working invite.": "Du kan bara gå med i det med en fungerande inbjudan.", + "You can still join it because this is a public room.": "Du kan fortfarande gå med eftersom det här är ett offentligt rum.", + "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Denna inbjudan till %(roomName)s skickades till %(email)s vilken inte är associerad med det här kontot", + "Link this email with your account in Settings to receive invites directly in %(brand)s.": "Länka den här e-postadressen med ditt konto in inställningarna för att motta inbjudningar direkt i %(brand)s.", + "This invite to %(roomName)s was sent to %(email)s": "Denna inbjudan till %(roomName)s skickades till %(email)s", + "Use an identity server in Settings to receive invites directly in %(brand)s.": "Använd en identitetsserver i inställningarna för att motta inbjudningar direkt i %(brand)s.", + "Share this email in Settings to receive invites directly in %(brand)s.": "Dela denna e-postadress i inställningarna för att motta inbjudningar direkt i %(brand)s.", + "Reject & Ignore user": "Avvisa och ignorera användare", + "Appearance": "Utseende", + "Show rooms with unread messages first": "Visa rum med olästa meddelanden först", + "Show previews of messages": "Visa förhandsvisningar av meddelanden", + "Sort by": "Sortera efter", + "Activity": "Aktivitet", + "A-Z": "A-Ö", + "List options": "Lista alternativ", + "Jump to first unread room.": "Hoppa till första olästa rum.", + "Jump to first invite.": "Hoppa till första inbjudan.", + "Show %(count)s more|other": "Visa %(count)s till", + "Show %(count)s more|one": "Visa %(count)s till", + "Use default": "Använd förval", + "Mentions & Keywords": "Benämningar & nyckelord", + "Notification options": "Aviseringsinställningar", + "Forget Room": "Glöm rum", + "Favourited": "Favoritmarkerade", + "Leave Room": "Lämna rum", + "Room options": "Rumsinställningar", + "%(count)s unread messages including mentions.|other": "%(count)s olästa meddelanden inklusive omnämnanden.", + "%(count)s unread messages including mentions.|one": "1 oläst omnämnande.", + "%(count)s unread messages.|other": "%(count)s olästa meddelanden.", + "%(count)s unread messages.|one": "1 oläst meddelande.", + "Unread messages.": "Olästa meddelanden.", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Att uppgradera det här rummet kommer att stänga den nuvarande instansen av rummet och skapa ett uppgraderat rum med samma namn.", + "This room has already been upgraded.": "Det här rummet har redan uppgraderats.", + "This room is running room version , which this homeserver has marked as unstable.": "Det här rummet kör rumsversion , vilket den här hemservern har markerat som instabil.", + "Unknown Command": "Okänt kommando", + "Unrecognised command: %(commandText)s": "Okänt kommando: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "Du kan använda /help för att lista tillgängliga kommandon. Menade du att skicka detta som ett meddelande?", + "Hint: Begin your message with // to start it with a slash.": "Tips: Börja ditt meddelande med // för att starta det med ett snedstreck.", + "Send as message": "Skicka som meddelande", + "Mark all as read": "Markera alla som lästa", + "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid uppdatering av rummets alternativa adresser. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", + "Error creating address": "Fel vid skapande av adress", + "There was an error creating that address. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid skapande av adressen. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", + "You don't have permission to delete the address.": "Du har inte behörighet att radera den där adressen.", + "There was an error removing that address. It may no longer exist or a temporary error occurred.": "Ett fel inträffade vid borttagning av adressen. Den kanske inte längre existerar, eller så inträffade ett tillfälligt fel.", + "Error removing address": "Fel vi borttagning av adress", + "Local address": "Lokal adress", + "Published Addresses": "Publicerade adresser", + "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Publicerade adresser kan användas av vem som helst på vilken server som helst för att gå med i ditt rum. För att publicera en adress måste den ställas in som en lokal adress först.", + "Other published addresses:": "Andra publicerade adresser:", + "No other published addresses yet, add one below": "Inga andra publicerade adresser än, lägg till en nedan", + "New published address (e.g. #alias:server)": "Ny publicerad adress (t.ex. #alias:server)", + "Local Addresses": "Lokala adresser", + "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "Ange adresser för det här rummet så att användare kan hitta det här rummet via din hemserver (%(localDomain)s)", + "Waiting for you to accept on your other session…": "Väntar på att du ska acceptera din andra session…", + "Waiting for %(displayName)s to accept…": "Väntar på att %(displayName)s ska acceptera…", + "Accepting…": "Accepterar…", + "Start Verification": "Starta verifiering", + "Messages in this room are end-to-end encrypted.": "Meddelanden i det här rummet är totalsträckskrypterade.", + "Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Dina meddelanden är säkrade och endast du och mottagaren har de unika nycklarna för att låsa upp dem.", + "Messages in this room are not end-to-end encrypted.": "Meddelanden i detta rum är inte totalsträckskrypterade.", + "In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.": "I krypterade rum är dina meddelanden säkrade och endast du och mottagaren har de unika nycklarna för att låsa upp dem.", + "Verify User": "Verifiera användare", + "For extra security, verify this user by checking a one-time code on both of your devices.": "För extra säkerhet, verifiera den här användaren genom att kolla en engångskod på båda era enheter.", + "Your messages are not secure": "Dina meddelanden är inte säkra", + "One of the following may be compromised:": "Någon av följande kan vara äventyrad:", + "Your homeserver": "Din hemserver", + "The homeserver the user you’re verifying is connected to": "Hemservern som användaren du verifierar är ansluten till", + "Yours, or the other users’ internet connection": "Din eller den andra användarens internetanslutning", + "Yours, or the other users’ session": "Din eller den andra användarens session", + "Trusted": "Betrodd", + "Not trusted": "Inte betrodd", + "%(count)s verified sessions|other": "%(count)s verifierade sessioner", + "%(count)s verified sessions|one": "1 verifierad session", + "Hide verified sessions": "Dölj verifierade sessioner", + "%(count)s sessions|other": "%(count)s sessioner", + "%(count)s sessions|one": "%(count)s session", + "Hide sessions": "Dölj sessioner", + "Direct message": "Direktmeddelande", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "Du håller på att ta bort 1 meddelande från %(user)s. Detta kan inte ångras. Vill du fortsätta?", + "Remove %(count)s messages|one": "Ta bort 1 meddelande", + "%(role)s in %(roomName)s": "%(role)s i %(roomName)s", + "Failed to deactivate user": "Misslyckades att inaktivera användaren", + "This client does not support end-to-end encryption.": "Den här klienten stöder inte totalsträckskryptering.", + "Security": "Säkerhet", + "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "Sessionen du försöker verifiera stöder inte skanning av en QR-kod eller emoji-verifiering, vilket är vad %(brand)s stöder. Försök med en annan klient.", + "Verify by scanning": "Verifiera med skanning", + "Ask %(displayName)s to scan your code:": "Be %(displayName)s att skanna din kod:", + "If you can't scan the code above, verify by comparing unique emoji.": "Om du inte kan skanna koden ovan, verifiera genom att jämföra unika emojier.", + "Verify by comparing unique emoji.": "Verifiera genom att jämföra unika emojier.", + "Verify by emoji": "Verifiera med emoji", + "Almost there! Is your other session showing the same shield?": "Nästan klar! Visar din andra session samma sköld?", + "Almost there! Is %(displayName)s showing the same shield?": "Nästan klar! Visar %(displayName)s samma sköld?", + "Verify all users in a room to ensure it's secure.": "Verifiera alla användare i ett rum för att försäkra att det är säkert.", + "In encrypted rooms, verify all users to ensure it’s secure.": "I krypterade rum, verifiera alla användare för att försäkra att det är säkert.", + "You've successfully verified your device!": "Du har verifierat din enhet framgångsrikt!", + "You've successfully verified %(deviceName)s (%(deviceId)s)!": "Du har verifierat %(deviceName)s (%(deviceId)s) framgångsrikt!", + "You've successfully verified %(displayName)s!": "Du har verifierat %(displayName)s framgångsrikt!", + "Verified": "Verifierad", + "Got it": "Uppfattat", + "Start verification again from the notification.": "Starta verifiering igen från aviseringen.", + "Start verification again from their profile.": "Starta verifiering igen från deras profil.", + "Verification timed out.": "Verifieringen löpte ut.", + "You cancelled verification on your other session.": "Du avbröt verifieringen på din andra session.", + "%(displayName)s cancelled verification.": "%(displayName)s avbröt verifiering.", + "You cancelled verification.": "Du avbröt verifiering.", + "Verification cancelled": "Verifiering avbruten", + "Compare emoji": "Jämför emoji", + "Encryption enabled": "Kryptering aktiverad", + "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Meddelanden i det här rummet är totalsträckskrypterade. Lär dig mer och verifiera den här användaren i deras användarprofil.", + "Encryption not enabled": "Kryptering är inte aktiverad", + "The encryption used by this room isn't supported.": "Krypteringen som används i det här rummet stöds inte.", + "You declined": "Du avslog", + "%(name)s declined": "%(name)s avslog", + "Accepting …": "Accepterar …", + "Declining …": "Avslår …", + "Message deleted": "Meddelande raderat", + "Message deleted by %(name)s": "Meddelande raderat av %(name)s", + "Message deleted on %(date)s": "Meddelande raderat vid %(date)s", + "Edited at %(date)s": "Redigerat vid %(date)s", + "Click to view edits": "Klicka för att visa redigeringar", + "Can't load this message": "Kan inte ladda det här meddelandet", + "Submit logs": "Skicka loggar", + "Categories": "Kategorier", + "Information": "Information", + "QR Code": "QR-kod", + "Room address": "Rumsadress", + "Please provide a room address": "Vänligen välj en rumsadress", + "This address is available to use": "Adressen är tillgänglig", + "This address is already in use": "Adressen är upptagen", + "Sign in with single sign-on": "Logga in med externt konto", + "Enter a server name": "Ange ett servernamn", + "Looks good": "Ser bra ut", + "Can't find this server or its room list": "Kan inte hitta den här servern eller dess rumslista", + "All rooms": "Alla rum", + "Your server": "Din server", + "Are you sure you want to remove %(serverName)s": "Är du säker på att du vill ta bort %(serverName)s", + "Remove server": "Ta bort server", + "Matrix": "Matrix", + "Add a new server": "Lägg till en ny server", + "Enter the name of a new server you want to explore.": "Ange namnet för en ny server du vill utforska.", + "Server name": "Servernamn", + "Add a new server...": "Lägg till en ny server…", + "%(networkName)s rooms": "%(networkName)s-rum", + "Matrix rooms": "Matrix-rum", + "Preparing to download logs": "Förbereder nedladdning av loggar", + "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Påminnelse: Din webbläsare stöds inte, så din upplevelse kan vara oförutsägbar.", + "Download logs": "Ladda ner loggar", + "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Om det finns ytterligare sammanhang som kan hjälpa för att analysera problemet, till exempel vad du gjorde vid den tiden, rums-ID:n, användar-ID:n o.s.v., vänligen inkludera dessa saker här.", + "Unable to load commit detail: %(msg)s": "Kunde inte ladda commit-detalj: %(msg)s", + "Add another email": "Lägg till en till e-postadress", + "People you know on %(brand)s": "Personer du känner på %(brand)s", + "Show": "Visa", + "Send %(count)s invites|other": "Skicka %(count)s inbjudningar", + "Send %(count)s invites|one": "Skicka %(count)s inbjudan", + "Invite people to join %(communityName)s": "Bjud in folk att gå med i %(communityName)s", + "Removing…": "Tar bort…", + "Destroy cross-signing keys?": "Förstöra korssigneringsnycklar?", + "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Radering av korssigneringsnycklar är permanent. Alla du har verifierat med kommer att se säkerhetsvarningar. Du vill troligen inte göra detta, såvida du inte har tappat bort alla enheter du kan korssignera från.", + "Clear cross-signing keys": "Rensa korssigneringsnycklar", + "Clear all data in this session?": "Rensa all data i den här sessionen?", + "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Rensning av all data från den här sessionen är permanent. Krypterade meddelande kommer att förloras om inte deras nycklar har säkerhetskopierats.", + "Clear all data": "Rensa all data", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Ett fel inträffade vid skapande av din gemenskap. Namnet kan vara upptaget eller så kan servern inte hantera din begäran.", + "Community ID: +:%(domain)s": "Gemenskaps-ID: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Använd detta när du hänvisar andra till din gemenskap. Gemenskaps-ID:t kan inte ändras.", + "You can change this later if needed.": "Du kan ändra detta senare om det behövs.", + "What's the name of your community or team?": "Vad är namnet på din gemenskap eller ditt lag?", + "Enter name": "Ange namn", + "Add image (optional)": "Lägg till bild (valfritt)", + "An image will help people identify your community.": "En bild hjälper folk att identifiera din gemenskap.", + "Please enter a name for the room": "Vänligen ange ett namn för rummet", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privata rum kan endast hittas och gås med i med inbjudan. Offentliga rum kan hittas och gås med i av vem som helst.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privata rum kan endast hittas och gås med i med inbjudan. Offentliga rum kan hittas och gås med i av vem som helst i den här gemenskapen.", + "You can’t disable this later. Bridges & most bots won’t work yet.": "Du kan inte inaktivera det här senare. Bryggor och bottar kommer inte fungera än.", + "Enable end-to-end encryption": "Aktivera totalsträckskryptering", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Du kanske vill aktivera detta om rummet endast kommer att användas för samarbete med interna lag på din hemserver. Detta kan inte ändras senare.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Du kanske vill inaktivera detta om rummet kommer att användas för samarbete med externa lag som har sin egen hemserver. Detta kan inte ändras senare.", + "Create a public room": "Skapa ett offentligt rum", + "Create a private room": "Skapa ett privat rum", + "Create a room in %(communityName)s": "Skapa ett rum i %(communityName)s", + "Topic (optional)": "Ämne (valfritt)", + "Make this room public": "Gör det här rummet offentligt", + "Hide advanced": "Dölj avancerat", + "Show advanced": "Visa avancerat", + "Block anyone not part of %(serverName)s from ever joining this room.": "Blockera alla som inte är medlem i %(serverName)s från att någonsin gå med i det här rummet.", + "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "För att undvika att förlora din chatthistorik måste du exportera dina rumsnycklar innan du loggar ut. Du behöver gå tillbaka till den nyare versionen av %(brand)s för att göra detta", + "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Du har tidigare använt en nyare version av %(brand)s med den här sessionen. Om du vill använda den här versionen igen med totalsträckskryptering behöver du logga ut och logga in igen.", + "Incompatible Database": "Inkompatibel databas", + "Continue With Encryption Disabled": "Fortsätt med kryptering inaktiverad", + "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda externt konto för att bevisa din identitet.", + "Are you sure you want to deactivate your account? This is irreversible.": "Är du säker på att du vill inaktivera ditt konto? Detta är oåterkalleligt.", + "Confirm account deactivation": "Bekräfta kontoinaktivering", + "Security & privacy": "Säkerhet & sekretess", + "There was a problem communicating with the server. Please try again.": "Ett problem inträffade vid kommunikation med servern. Vänligen försök igen.", + "Server did not require any authentication": "Servern krävde inte någon auktorisering", + "Verification Requests": "Verifikationsförfrågningar", + "Confirm to continue": "Bekräfta för att fortsätta", + "Server did not return valid authentication information.": "Servern returnerade inte giltig autentiseringsinformation.", + "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Att verifiera den här användaren kommer att markera dess session som betrodd, och markera din session som betrodd för denne.", + "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifiera denna enhet för att markera den som betrodd. Att lita på denna enhet och andra användare ger en extra sinnesfrid när man använder totalsträckskrypterade meddelanden.", + "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Att verifiera den här enheten kommer att markera den som betrodd, användare som har verifierat dig kommer att lita på den här enheten.", + "To continue, use Single Sign On to prove your identity.": "För att fortsätta, använd externt konto för att bevisa din identitet.", + "Click the button below to confirm your identity.": "Klicka på knappen nedan för att bekräfta din identitet.", + "Failed to invite the following users to chat: %(csvUsers)s": "Misslyckades att bjuda in följande användare till chatten: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "Vi kunde inte skapa ditt DM. Vänligen kolla användarna du försöker bjuda in och försök igen.", + "Something went wrong trying to invite the users.": "Någonting gick fel vid försök att bjuda in användarna.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "Vi kunde inte bjuda in de användarna. Vänligen kolla användarna du vill bjuda in och försök igen.", + "Failed to find the following users": "Misslyckades att hitta följande användare", + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Följande användare kanske inte existerar eller är ogiltiga, och kan inte bjudas in: %(csvNames)s", + "Recently Direct Messaged": "Nyligen direktmeddelade", + "Start a conversation with someone using their name, username (like ) or email address.": "Starta en konversation med någon med deras namn, användarnamn (som ) eller e-postadress.", + "Invite someone using their name, username (like ), email address or share this room.": "Bjud in någon med deras namn, användarnamn (som ) eller e-postadress eller dela det här rummet.", + "a new master key signature": "en ny huvudnyckelsignatur", + "a new cross-signing key signature": "en ny korssigneringssignatur", + "a device cross-signing signature": "en enhets korssigneringssignatur", + "a key signature": "en nyckelsignatur", + "%(brand)s encountered an error during upload of:": "%(brand)s stötte på ett fel vid uppladdning av:", + "Upload completed": "Uppladdning slutförd", + "Cancelled signature upload": "Avbröt signaturuppladdning", + "Unable to upload": "Kunde inte ladda upp", + "Signature upload success": "Signaturuppladdning lyckades", + "Signature upload failed": "Signaturuppladdning misslyckades", + "Confirm by comparing the following with the User Settings in your other session:": "Bekräfta genom att jämföra följande med användarinställningarna i din andra session:", + "Confirm this user's session by comparing the following with their User Settings:": "Bekräfta den här användarens session genom att jämföra följande med deras användarinställningar:", + "If they don't match, the security of your communication may be compromised.": "Om de inte matchar så kan din kommunikations säkerhet vara äventyrad.", + "Your account is not secure": "Ditt konto är inte säkert", + "Your password": "Ditt lösenord", + "This session, or the other session": "Den här sessionen, eller den andra sessionen", + "The internet connection either session is using": "Internetuppkopplingen en av enheterna använder", + "We recommend you change your password and recovery key in Settings immediately": "Vi rekommenderar att du ändrar ditt lösenord och din återställningsnyckel i inställningarna omedelbart", + "New session": "Ny session", + "Use this session to verify your new one, granting it access to encrypted messages:": "Använd den här sessionen för att verifiera en ny och ge den åtkomst till krypterade meddelanden:", + "If you didn’t sign in to this session, your account may be compromised.": "Om det inte var du som loggade in i den här sessionen så kan ditt konto vara äventyrat.", + "This wasn't me": "Det var inte jag", + "Please fill why you're reporting.": "Vänligen fyll i varför du rapporterar.", + "Report Content to Your Homeserver Administrator": "Rapportera innehåll till din hemserveradministratör", + "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Att rapportera det här meddelandet kommer att skicka dess unika 'händelse-ID' till administratören för din hemserver. Om meddelanden i det här rummet är krypterade kommer din hemserveradministratör inte att kunna läsa meddelandetexten eller se några filer eller bilder.", + "Send report": "Skicka rapport", + "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Att uppgradera det här rummet kräver att den nuvarande instansen a rummet stängs och ett nytt rum skapas i dess plats. För att ge rumsmedlemmar den bästa möjliga upplevelsen kommer vi:", + "You're all caught up.": "Du är ikapp.", + "Server isn't responding": "Servern svarar inte", + "Your server isn't responding to some of your requests. Below are some of the most likely reasons.": "Din server svarar inte på vissa av dina förfrågningar. Nedan är några av de troligaste anledningarna.", + "The server (%(serverName)s) took too long to respond.": "Servern (%(serverName)s) tog för lång tid att svara.", + "Your firewall or anti-virus is blocking the request.": "Din brandvägg eller ditt anti-virus blockerar förfrågan.", + "A browser extension is preventing the request.": "Ett webbläsartillägg förhindrar förfrågan.", + "The server is offline.": "Servern är offline.", + "The server has denied your request.": "Servern nekade din förfrågan.", + "Your area is experiencing difficulties connecting to the internet.": "Ditt område upplever störningar i internetuppkopplingen.", + "A connection error occurred while trying to contact the server.": "Ett fel inträffade vid försök att kontakta servern.", + "The server is not configured to indicate what the problem is (CORS).": "Servern är inte inställd på att indikera vad problemet är (CORS).", + "Recent changes that have not yet been received": "Nyliga ändringar har inte mottagits än", + "Copy": "Kopiera", + "Command Help": "Kommandohjälp", + "Upload all": "Ladda upp alla", + "Verify other session": "Verifiera annan session", + "Verification Request": "Verifikationsförfrågan", + "Wrong file type": "Fel filtyp", + "Looks good!": "Ser bra ut!", + "Wrong Recovery Key": "Fel återställningsnyckel", + "Invalid Recovery Key": "Ogiltig återställningsnyckel", + "Security Phrase": "Säkerhetsfras", + "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Kunde inte komma åt hemlig lagring. Vänligen verifiera att du matade in rätt återställningslösenfras.", + "Enter your Security Phrase or to continue.": "Ange din säkerhetsfras eller för att fortsätta.", + "Security Key": "Säkerhetsnyckel", + "Use your Security Key to continue.": "Använd din säkerhetsnyckel för att fortsätta.", + "Restoring keys from backup": "Återställer nycklar från säkerhetskopia", + "Fetching keys from server...": "Hämtar nycklar från servern…", + "%(completed)s of %(total)s keys restored": "%(completed)s av %(total)s nycklar återställda", + "Recovery key mismatch": "Återställningsnyckeln matchade inte", + "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Säkerhetskopian kunde inte avkrypteras med den här återställningsnyckeln: vänligen verifiera att du matade in rätt återställningsnyckel.", + "Incorrect recovery passphrase": "Fel återställningslösenfras", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Säkerhetskopian kunde inte avkrypteras med den här återställningslösenfrasen: vänligen verifiera att du matade in rätt återställningslösenfras.", + "Unable to restore backup": "Kunde inte återställa säkerhetskopia", + "No backup found!": "Ingen säkerhetskopia hittad!", + "Keys restored": "Nycklar återställda", + "Failed to decrypt %(failedCount)s sessions!": "Misslyckades att avkryptera %(failedCount)s sessioner!", + "Successfully restored %(sessionCount)s keys": "Återställde framgångsrikt %(sessionCount)s nycklar", + "Enter recovery passphrase": "Ange återställningslösenfras", + "Warning: you should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningslösenfras.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Om du har glömt din återställningslösenfras kan du använda din återställningsnyckel eller ställa in nya återställningsalternativ", + "Enter recovery key": "Skriv in återställningsnyckel", + "This looks like a valid recovery key!": "Det här ser ut som en giltig återställningsnyckel!", + "Not a valid recovery key": "Inte en giltig återställningsnyckel", + "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningsnyckel.", + "If you've forgotten your recovery key you can ": "Om du har glömt din återställningsnyckel så kan du ", + "Resend edit": "Skicka redigering igen", + "Resend %(unsentCount)s reaction(s)": "Skicka %(unsentCount)s reaktion(er) igen", + "Resend removal": "Skicka borttagning igen", + "Share Permalink": "Dela permalänk", + "Report Content": "Rapportera innehåll", + "This room is public": "Det här rummet är offentligt", + "Away": "Borta", + "Country Dropdown": "Land-dropdown", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Du kan använda de anpassade serveralternativen för att logga in på andra Matrix-servrar genom att ange en annan hemserver-URL. Detta gör att du kan använda %(brand)s med ett befintligt Matrix-konto på en annan hemserver.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Saknar publik nyckel för captcha i hemserverns konfiguration. Vänligen rapportera detta till din hemservers administratör.", + "Please review and accept all of the homeserver's policies": "Vänligen granska och acceptera alla hemserverns policyer", + "Unable to validate homeserver/identity server": "Kunde inte validera hemserver/identitetsserver", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Ange platsen för din Element Matrix Services-hemserver. Den kan använda ditt eget domännamn eller vara en underdomän till element.io.", + "Enter password": "Skriv in lösenord", + "Nice, strong password!": "Bra, säkert lösenord!", + "Password is allowed, but unsafe": "Lösenordet är tillåtet men osäkert", + "Keep going...": "Fortsätt…", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ingen identitetsserver är konfigurerad så du kan inte lägga till en e-postadress för att återställa ditt lösenord i framtiden.", + "Use an email address to recover your account": "Använd en a-postadress för att återställa ditt konto", + "Enter email address (required on this homeserver)": "Skriv in e-postadress (krävs på den här hemservern)", + "Doesn't look like a valid email address": "Det ser inte ut som en giltig e-postadress", + "Passwords don't match": "Lösenorden matchar inte", + "Other users can invite you to rooms using your contact details": "Andra användare kan bjuda in dig till rum med dina kontaktuppgifter", + "Enter phone number (required on this homeserver)": "Skriv in telefonnummer (krävs på den här hemservern)", + "Doesn't look like a valid phone number": "Det ser inte ut som ett giltigt telefonnummer", + "Use lowercase letters, numbers, dashes and underscores only": "Använd endast små bokstäver, siffror, bindestreck och understreck", + "Enter username": "Skriv in användarnamn", + "Create your Matrix account on ": "Skapa ditt Matrix-konto på ", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Sätt en e-postadress för kontoåterställning. Använd valfritt en e-postadress eller ett telefonnummer för kunna upptäckas av existerande kontakter.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Sätt en e-postadress för kontoåterställning. Använd valfritt en e-postadress för kunna upptäckas av existerande kontakter.", + "Enter your custom homeserver URL What does this mean?": "Skriv in din hemserver-URL Vad betyder det här?", + "Enter your custom identity server URL What does this mean?": "Skriv in din anpassade identitetsserver-URL Vad betyder det här?", + "Sign in with SSO": "Logga in med SSO", + "No files visible in this room": "Inga filer synliga i det här rummet", + "Attach files from chat or just drag and drop them anywhere in a room.": "Bifoga filer från chatten eller dra och släpp dem vart som helst i rummet.", + "Welcome to %(appName)s": "Välkommen till %(appName)s", + "Liberate your communication": "Befria din kommunikation", + "Send a Direct Message": "Skicka ett direktmeddelande", + "Explore Public Rooms": "Utforska offentliga rum", + "Create a Group Chat": "Skapa en gruppchatt", + "Explore rooms": "Utforska rum", + "Self-verification request": "Självverifieringsförfrågan", + "%(creator)s created and configured the room.": "%(creator)s skapade och konfigurerade rummet.", + "You’re all caught up": "Du är ikapp", + "You have no visible notifications in this room.": "Du har inga synliga aviseringar i det här rummet.", + "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s misslyckades att hämta protokollistan från hemservern. Hemservern kan vara för gammal för att stödja tredjepartsnätverk.", + "%(brand)s failed to get the public room list.": "%(brand)s misslyckades att hämta listan över offentliga rum.", + "The homeserver may be unavailable or overloaded.": "Hemservern kan vara otillgänglig eller överbelastad.", + "Delete the room address %(alias)s and remove %(name)s from the directory?": "Radera rumsadressen %(alias)s och ta bort %(name)s från den här katalogen?", + "delete the address.": "radera adressen.", + "View": "Visa", + "Find a room…": "Hitta ett rum…", + "Find a room… (e.g. %(exampleRoom)s)": "Hitta ett rum… (t.ex. %(exampleRoom)s)", + "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Om du inte hittar rummet du letar efter, be om en inbjudan eller skapa ett nytt rum.", + "Explore rooms in %(communityName)s": "Utforska rum i %(communityName)s", + "Search rooms": "Sök bland rum", + "You have %(count)s unread notifications in a prior version of this room.|other": "Du har %(count)s olästa aviseringar i en tidigare version av det här rummet.", + "You have %(count)s unread notifications in a prior version of this room.|one": "Du har %(count)s oläst avisering i en tidigare version av det här rummet.", + "Create community": "Skapa gemenskap", + "Failed to find the general chat for this community": "Misslyckades att hitta den allmänna chatten för den här gemenskapen", + "Notification settings": "Aviseringsinställningar", + "All settings": "Alla inställningar", + "Feedback": "Återkoppling", + "Community settings": "Gemenskapsinställningar", + "User settings": "Användarinställningar", + "Switch to light mode": "Byt till ljust läge", + "Switch to dark mode": "Byt till mörkt läge", + "Switch theme": "Byt tema", + "User menu": "Användarmeny", + "Community and user menu": "Gemenskaps- och användarmeny", + "Verify this login": "Verifiera den här inloggningen", + "Session verified": "Session verifierad", + "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Om du ändrar lösenordet så kommer alla nycklar för totalsträckskryptering att återställas på alla dina sessioner, vilket gör krypterad chatthistorik oläslig. Ställ in nyckelsäkerhetskopiering eller exportera dina rumsnycklar från en annan session innan du återställer lösenordet.", + "Your Matrix account on ": "Ditt Matrix-konto på ", + "No identity server is configured: add one in server settings to reset your password.": "Ingen identitetsserver konfigurerad: lägg till en i serverinställningarna för att återställa ditt konto.", + "A verification email will be sent to your inbox to confirm setting your new password.": "Ett e-brev för verifiering skickas till din inkorg för att bekräfta ditt nya lösenord.", + "Invalid homeserver discovery response": "Ogiltigt hemserverupptäcktssvar", + "Failed to get autodiscovery configuration from server": "Misslyckades att få konfiguration för autoupptäckt från servern", + "Invalid base_url for m.homeserver": "Ogiltig base_url för m.homeserver", + "Homeserver URL does not appear to be a valid Matrix homeserver": "Hemserver-URL:en verkar inte vara en giltig Matrix-hemserver", + "Invalid identity server discovery response": "Ogiltigt identitetsserverupptäcktssvar", + "Invalid base_url for m.identity_server": "Ogiltig base_url för m.identity_server", + "Identity server URL does not appear to be a valid identity server": "Identitetsserver-URL:en verkar inte vara en giltig Matrix-identitetsserver", + "This account has been deactivated.": "Det här kontot har avaktiverats.", + "Failed to perform homeserver discovery": "Misslyckades att genomföra hemserverupptäckt", + "Privacy": "Sekretess", + "There was an error updating your community. The server is unable to process your request.": "Ett fel inträffade vid uppdatering av din gemenskap. Serven kan inte behandla din begäran.", + "Update community": "Uppdatera gemenskap", + "May include members not in %(communityName)s": "Kan inkludera medlemmar som inte är i %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Starta en konversation med någon med deras namn, användarnamn (som ) eller e-postadress. Detta kommer inte att bjuda in dem till %(communityName)s. För att bjuda in någon till %(communityName)s, klicka här.", + "Syncing...": "Synkar…", + "Signing In...": "Loggar in…", + "If you've joined lots of rooms, this might take a while": "Om du har gått med i många rum kan det här ta ett tag", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Ditt nya konto (%(newAccountId)s) är registrerat, men du är redan inloggad på ett annat konto (%(loggedInUserId)s).", + "Continue with previous account": "Fortsätt med de tidigare kontot", + "Log in to your new account.": "Logga in i ditt nya konto.", + "You can now close this window or log in to your new account.": "Du kan nu stänga det här fönstret eller logga in i ditt nya konto.", + "Registration Successful": "Registrering lyckades", + "Use Recovery Key or Passphrase": "Använd återställningsnyckel eller -lösenfras", + "Use Recovery Key": "Använd återställningsnyckel", + "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Bekräfta din identitet genom att verifiera den här inloggningen från en av dina andra sessioner och ge den åtkomst till krypterade meddelanden.", + "This requires the latest %(brand)s on your other devices:": "Det här kräver den senaste %(brand)s på dina andra enheter:", + "%(brand)s Web": "%(brand)s webb", + "%(brand)s Desktop": "%(brand)s skrivbord", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android", + "or another cross-signing capable Matrix client": "eller en annan Matrix-klient som stöder korssignering", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Din nya session har nu verifierats. Den har tillgång till dina krypterade meddelanden, och andra användare kommer att se den som betrodd.", + "Your new session is now verified. Other users will see it as trusted.": "Din nya session har nu verifierats. Andra användare kommer att se den som betrodd.", + "Without completing security on this session, it won’t have access to encrypted messages.": "Utan att slutföra säkerheten på den här sessionen får den inte tillgång till krypterade meddelanden.", + "Failed to re-authenticate due to a homeserver problem": "Misslyckades att återautentisera p.g.a. ett hemserverproblem", + "Failed to re-authenticate": "Misslyckades att återautentisera", + "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Återfå tillgång till ditt konto och återställ krypteringsnycklar som lagrats i den här sessionen. Utan dem kan du inte läsa alla dina säkra meddelanden i någon session.", + "Enter your password to sign in and regain access to your account.": "Ange ditt lösenord för att logga in och återfå tillgång till ditt konto.", + "Forgotten your password?": "Glömt ditt lösenord?", + "Sign in and regain access to your account.": "Logga in och återfå tillgång till ditt konto.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "Du kan inte logga in på ditt konto. Vänligen kontakta din hemserveradministratör för mer information.", + "You're signed out": "Du är utloggad", + "Clear personal data": "Rensa personlig information", + "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Varning: Din personliga information (inklusive krypteringsnycklar) lagras fortfarande i den här sessionen. Rensa den om du är färdig med den här sessionen, eller vill logga in på ett annat konto.", + "Command Autocomplete": "Autokomplettering av kommandon", + "DuckDuckGo Results": "DuckDuckGo-resultat", + "Emoji Autocomplete": "Autokomplettering av emoji", + "Notification Autocomplete": "Autokomplettering av aviseringar", + "Room Autocomplete": "Autokomplettering av rum", + "User Autocomplete": "Autokomplettering av användare", + "Confirm encryption setup": "Bekräfta krypteringsinställning", + "Click the button below to confirm setting up encryption.": "Klicka på knappen nedan för att bekräfta inställning av kryptering.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Skydda mot att förlora åtkomst till krypterade meddelanden och data genom att säkerhetskopiera krypteringsnycklar på din server.", + "Generate a Security Key": "Generera en säkerhetsnyckel", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Vi kommer att generera en säkerhetsnyckel som du kan lagra någonstans säkert, som en lösenordshanterare eller ett kassaskåp.", + "Enter a Security Phrase": "Ange en säkerhetsfras", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Använd en hemlig fras endast du känner till, och spara valfritt en säkerhetsnyckel att använda för säkerhetskopiering.", + "Enter your account password to confirm the upgrade:": "Ange ditt kontolösenord för att bekräfta uppgraderingen:", + "Restore your key backup to upgrade your encryption": "Återställ din nyckelsäkerhetskopia för att uppgradera din kryptering", + "Restore": "Återställ", + "You'll need to authenticate with the server to confirm the upgrade.": "Du kommer behöva autentisera mot servern för att bekräfta uppgraderingen.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Uppgradera den här sessionen för att låta den verifiera andra sessioner, för att ge dem åtkomst till krypterade meddelanden och markera dem som betrodda för andra användare.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Ange en säkerhetsfras endast du känner till, eftersom den används för att hålla din data säker. För att vara säker bör inte återanvända ditt kontolösenord.", + "Enter a recovery passphrase": "Ange en återställningslösenfras", + "Great! This recovery passphrase looks strong enough.": "Strålande! Den här återställningslösenfrasen ser stark nog ut.", + "That matches!": "Det matchar!", + "Use a different passphrase?": "Använd en annan lösenfras?", + "That doesn't match.": "Det matchar inte.", + "Go back to set it again.": "Gå tillbaka och sätt den igen.", + "Enter your recovery passphrase a second time to confirm it.": "Ange din återställningslösenfras en gång till för att bekräfta den.", + "Confirm your recovery passphrase": "Bekräfta din återställningslösenfras", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Förvara din säkerhetsnyckel någonstans säkert, som en lösenordshanterare eller ett kassaskåp, eftersom den används för att skydda dina krypterade data.", + "Download": "Ladda ner", + "Unable to query secret storage status": "Kunde inte fråga efter status på hemlig lagring", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Om du avbryter nu så kan du förlora krypterade meddelanden och data om du förlorar åtkomst till dina inloggningar.", + "You can also set up Secure Backup & manage your keys in Settings.": "Du kan även ställa in säker säkerhetskopiering och hantera dina nycklar i inställningarna.", + "Set up Secure Backup": "Ställ in säker säkerhetskopiering", + "Upgrade your encryption": "Uppgradera din kryptering", + "Set a Security Phrase": "Sätt en säkerhetsfras", + "Confirm Security Phrase": "Bekräfta säkerhetsfras", + "Save your Security Key": "Spara din säkerhetsnyckel", + "Unable to set up secret storage": "Kunde inte sätta upp hemlig lagring", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "Vi lagrar en krypterad kopia av dina nycklar på vår server. Säkra din säkerhetskopia med en återställningslösenfras.", + "For maximum security, this should be different from your account password.": "För maximal säkerhet bör det här skilja sig från ditt kontolösenord.", + "Set up with a recovery key": "Sätt en återställningsnyckel", + "Please enter your recovery passphrase a second time to confirm.": "Vänligen ange din återställningslösenfras en gång till för att bekräfta.", + "Repeat your recovery passphrase...": "Repetera din återställningslösenfras…", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Din återställningsnyckel är ett skyddsnät - du kan använda den för att återfå åtkomst till dina krypterade meddelanden om du glömmer din återställningslösenfras.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Förvara en kopia av den någonstans säkert, som en lösenordshanterare eller till och med ett kassaskåp.", + "Your recovery key": "Din återställningsnyckel", + "Your recovery key has been copied to your clipboard, paste it to:": "Din återställningsnyckel har kopierats till ditt klippbord, klistra in den i:", + "Your recovery key is in your Downloads folder.": "Din återställningsnyckel är i din Hämtningar-mapp.", + "Print it and store it somewhere safe": "Skriv ut den och förvara den någonstans säkert", + "Save it on a USB key or backup drive": "Spara den på ett USB-minne eller en säkerhetskopieringshårddisk", + "Copy it to your personal cloud storage": "Kopiera den till din personliga molnlagring", + "Your keys are being backed up (the first backup could take a few minutes).": "Dina nycklar säkerhetskopieras (den första säkerhetskopieringen kan ta några minuter).", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Om du inte ställer in säker meddelandeåterställning kommer du inte kunna återställa din krypterade meddelandehistorik om du loggar ut eller använder en annan session.", + "Set up Secure Message Recovery": "Ställ in säker meddelandeåterställning", + "Secure your backup with a recovery passphrase": "Säkra din säkerhetskopia med en återställningslösenfras", + "Make a copy of your recovery key": "Ta en kopia av din återställningsnyckel", + "Starting backup...": "Startar säkerhetskopiering…", + "Success!": "Framgång!", + "Create key backup": "Skapa nyckelsäkerhetskopia", + "Unable to create key backup": "Kunde inte skapa nyckelsäkerhetskopia", + "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Om du inte ställer in säker meddelandeåterställning så kommer du förlora åtkomst till din säkra meddelandehistorik när du loggar ut.", + "If you don't want to set this up now, you can later in Settings.": "Om du inte vill ställa in det här nu så kan du göra det senare i inställningarna.", + "New Recovery Method": "Ny återställningsmetod", + "A new recovery passphrase and key for Secure Messages have been detected.": "En ny återställningslösenfras och -nyckel för säkra meddelanden har hittats.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Om du inte har ställt in den nya återställningsmetoden kan en angripare försöka komma åt ditt konto. Ändra ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna.", + "This session is encrypting history using the new recovery method.": "Den här sessionen krypterar historik med den nya återställningsmetoden.", + "Go to Settings": "Gå till inställningarna", + "Set up Secure Messages": "Ställ in säkra meddelanden", + "Recovery Method Removed": "Återställningsmetod borttagen", + "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Den här sessionen har detekterat att din återställningslösenfras och -nyckel för säkra meddelanden har tagits bort.", + "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "Om du gjorde det av misstag kan du ställa in säkra meddelanden på den här sessionen som krypterar sessionens meddelandehistorik igen med en ny återställningsmetod.", + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Om du inte tog bort återställningsmetoden kan en angripare försöka komma åt ditt konto. Ändra ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna.", + "If disabled, messages from encrypted rooms won't appear in search results.": "Om den är inaktiverad visas inte meddelanden från krypterade rum i sökresultaten.", + "Disable": "Inaktivera", + "Not currently indexing messages for any room.": "Indexerar för närvarande inte meddelanden för något rum.", + "Currently indexing: %(currentRoom)s": "Indexerar för närvarande: %(currentRoom)s", + "%(brand)s is securely caching encrypted messages locally for them to appear in search results:": "%(brand)s cachar säkert krypterade meddelanden lokalt för att de ska visas i sökresultat:", + "Message downloading sleep time(ms)": "Vilotid för meddelandenedladdning (ms)", + "Navigate recent messages to edit": "Navigera nyliga meddelanden att redigera", + "Jump to start/end of the composer": "Hoppa till början/slut av meddelanderedigeraren", + "Navigate composer history": "Navigera redigerarhistorik", + "Cancel replying to a message": "Avbryt svar på ett meddelande", + "Toggle microphone mute": "Växla mikrofontystning", + "Toggle video on/off": "Växla video på/av", + "Scroll up/down in the timeline": "Skrolla upp/ner i tidslinjen", + "Dismiss read marker and jump to bottom": "Avfärda läsmarkering och hoppa till botten", + "Jump to oldest unread message": "Hoppa till äldsta olästa meddelandet", + "Upload a file": "Ladda upp en fil", + "Navigate up/down in the room list": "Navigera upp/ner i rumslistan", + "Select room from the room list": "Välj rum från rumslistan", + "Collapse room list section": "Kollapsa rumslistsektionen", + "Expand room list section": "Expandera rumslistsektionen", + "Clear room list filter field": "Rensa filterfältet för rumslistan", + "Previous/next unread room or DM": "Förra/nästa olästa rum eller DM", + "Previous/next room or DM": "Förra/nästa rum eller DM", + "Toggle the top left menu": "Växla menyn högst upp till vänster", + "Close dialog or context menu": "Stäng dialogrutan eller snabbmenyn", + "Activate selected button": "Aktivera den valda knappen", + "Toggle right panel": "Växla högerpanelen", + "Toggle this dialog": "Växla den här dialogrutan", + "Move autocomplete selection up/down": "Flytta autokompletteringssektionen upp/ner", + "Cancel autocomplete": "Stäng autokomplettering", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lägger till ( ͡° ͜ʖ ͡°) i början på ett textmeddelande" } diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index ad845dfb78..6aa980cd72 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2428,5 +2428,39 @@ "Error leaving room": "離開聊天室時發生錯誤", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "社群 v2 原型。需要相容的家伺服器。高度實驗性,小心使用。", "Explore rooms in %(communityName)s": "在 %(communityName)s 中探索聊天室", - "Set up Secure Backup": "設定安全備份" + "Set up Secure Backup": "設定安全備份", + "Information": "資訊", + "Add another email": "新增其他電子郵件", + "People you know on %(brand)s": "在 %(brand)s 上您認識的人們", + "Send %(count)s invites|other": "傳送 %(count)s 個邀請", + "Send %(count)s invites|one": "傳送 %(count)s 個邀請", + "Invite people to join %(communityName)s": "邀請夥伴加入 %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "建立您的社群時發生錯誤。名稱已被使用或伺服器無法處理您的請求。", + "Community ID: +:%(domain)s": "社群 ID:+:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "在將您的社群推薦給其他人時使用此功能。社群 ID 無法變更。", + "You can change this later if needed.": "若需要,您可以在稍後變更這個。", + "What's the name of your community or team?": "您的社群或團隊的名稱是什麼?", + "Enter name": "輸入名稱", + "Add image (optional)": "新增圖片(選擇性)", + "An image will help people identify your community.": "圖片可以協助人們辨識您的社群。", + "Create community": "建立社群", + "Explore community rooms": "探索社群聊天室", + "Create a room in %(communityName)s": "在 %(communityName)s 中建立聊天室", + "Cross-signing and secret storage are ready for use.": "交叉簽章與秘密儲存空間已可使用。", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "交叉簽章已準備好使用,但目前未使用秘密儲存空間備份您的金鑰。", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何人都可以找到並加入。", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何在此社群的人都可以找到並加入。", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "如果聊天室僅用於與在您的家伺服器上的內部團隊協作的話,可以啟用此功能。這無法在稍後變更。", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "如果聊天室會用於與有自己的家伺服器的外部團隊協作的話,可以停用此功能。這無法在稍後變更。", + "Block anyone not part of %(serverName)s from ever joining this room.": "阻止任何不屬於 %(serverName)s 的人加入此聊天室。", + "There was an error updating your community. The server is unable to process your request.": "更新您的社群時發生錯誤。伺服器無法處理您的請求。", + "Update community": "更新社群", + "May include members not in %(communityName)s": "可能包含不在 %(communityName)s 中的成員", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "使用某人的名稱、使用者名稱(如 )或電子郵件地址開始與他們對話。這不會邀請他們加入 %(communityName)s。要邀請某人加入 %(communityName)s,請點擊此處。", + "Failed to find the general chat for this community": "找不到此社群的一般聊天紀錄", + "Community settings": "社群設定", + "User settings": "使用者設定", + "Community and user menu": "社群與使用者選單", + "Privacy": "隱私", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "把 ( ͡° ͜ʖ ͡°) 加在純文字訊息前" } diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index 2234fc5bdf..1e75bf3bdf 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -105,6 +105,9 @@ export default class Resizer { if (this.classNames.resizing) { this.container.classList.add(this.classNames.resizing); } + if (this.config.onResizeStart) { + this.config.onResizeStart(); + } const {sizer, distributor} = this._createSizerAndDistributor(resizeHandle); distributor.start(); @@ -119,6 +122,9 @@ export default class Resizer { if (this.classNames.resizing) { this.container.classList.remove(this.classNames.resizing); } + if (this.config.onResizeStop) { + this.config.onResizeStop(); + } distributor.finish(); body.removeEventListener("mouseup", finishResize, false); document.removeEventListener("mouseleave", finishResize, false); diff --git a/src/resizer/sizer.js b/src/resizer/sizer.js index 50861d34d5..4ce9232457 100644 --- a/src/resizer/sizer.js +++ b/src/resizer/sizer.js @@ -56,6 +56,18 @@ export default class Sizer { return this.vertical ? this.container.offsetTop : this.container.offsetLeft; } + /** @return {number} container offset to document */ + _getPageOffset() { + let element = this.container; + let offset = 0; + while (element) { + const pos = this.vertical ? element.offsetTop : element.offsetLeft; + offset = offset + pos; + element = element.offsetParent; + } + return offset; + } + setItemSize(item, size) { if (this.vertical) { item.style.height = `${Math.round(size)}px`; @@ -80,9 +92,9 @@ export default class Sizer { offsetFromEvent(event) { const pos = this.vertical ? event.pageY : event.pageX; if (this.reverse) { - return (this._getOffset() + this.getTotalSize()) - pos; + return (this._getPageOffset() + this.getTotalSize()) - pos; } else { - return pos - this._getOffset(); + return pos - this._getPageOffset(); } } } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 95861e11df..9e0f36b1ba 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -566,7 +566,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "lastRightPanelPhaseForRoom": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - default: RightPanelPhases.RoomMemberInfo, + default: RightPanelPhases.RoomSummary, }, "lastRightPanelPhaseForGroup": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, @@ -607,4 +607,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, + "Widgets.pinned": { + supportedLevels: LEVELS_ROOM_OR_ACCOUNT, + default: {}, + }, }; diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index d51439459c..ea2f158ef6 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -18,11 +18,10 @@ import { SettingLevel } from "./SettingLevel"; export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM = Symbol("any room"); +const IRRELEVANT_ROOM: string = null; interface RoomWatcherMap { - // @ts-ignore - TS wants string-only keys but we know better - https://github.com/Microsoft/TypeScript/issues/1863 - [roomId: string | symbol]: CallbackFn[]; + [roomId: string]: CallbackFn[]; } /** @@ -69,7 +68,7 @@ export class WatchManager { if (!inRoomId) { // Fire updates to all the individual room watchers too, as they probably // care about the change higher up. - callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], [])); + callbacks.push(...Object.values(roomWatchers).flat(1)); } else if (roomWatchers[IRRELEVANT_ROOM]) { callbacks.push(...roomWatchers[IRRELEVANT_ROOM]); } diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts index 34445d007b..c539fcdb40 100644 --- a/src/stores/RightPanelStore.ts +++ b/src/stores/RightPanelStore.ts @@ -33,6 +33,8 @@ interface RightPanelStoreState { lastRoomPhase: RightPanelPhases; lastGroupPhase: RightPanelPhases; + previousPhase?: RightPanelPhases; + // Extra information about the last phase lastRoomPhaseParams: {[key: string]: any}; } @@ -89,6 +91,10 @@ export default class RightPanelStore extends Store { return this.state.lastGroupPhase; } + get previousPhase(): RightPanelPhases | null { + return RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.previousPhase) ? this.state.previousPhase : null; + } + get visibleRoomPanelPhase(): RightPanelPhases { return this.isOpenForRoom ? this.roomPanelPhase : null; } @@ -176,23 +182,27 @@ export default class RightPanelStore extends Store { if (targetPhase === this.state.lastGroupPhase) { this.setState({ showGroupPanel: !this.state.showGroupPanel, + previousPhase: null, }); } else { this.setState({ lastGroupPhase: targetPhase, showGroupPanel: true, + previousPhase: this.state.lastGroupPhase, }); } } else { if (targetPhase === this.state.lastRoomPhase && !refireParams) { this.setState({ showRoomPanel: !this.state.showRoomPanel, + previousPhase: null, }); } else { this.setState({ lastRoomPhase: targetPhase, showRoomPanel: true, lastRoomPhaseParams: refireParams || {}, + previousPhase: this.state.lastRoomPhase, }); } } diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 9045b17193..11b51dfc2d 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -22,6 +22,8 @@ export enum RightPanelPhases { NotificationPanel = 'NotificationPanel', RoomMemberInfo = 'RoomMemberInfo', EncryptionPanel = 'EncryptionPanel', + RoomSummary = 'RoomSummary', + Widget = 'Widget', Room3pidMemberInfo = 'Room3pidMemberInfo', // Group stuff @@ -34,6 +36,7 @@ export enum RightPanelPhases { // These are the phases that are safe to persist (the ones that don't require additional // arguments). export const RIGHT_PANEL_PHASES_NO_ARGS = [ + RightPanelPhases.RoomSummary, RightPanelPhases.NotificationPanel, RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index a5e7b12da0..7dd093d45e 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -93,13 +93,13 @@ class WidgetEchoStore extends EventEmitter { if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {}; this._roomWidgetEcho[roomId][widgetId] = state; - this.emit('update'); + this.emit('update', roomId, widgetId); } removeRoomWidgetEcho(roomId, widgetId) { delete this._roomWidgetEcho[roomId][widgetId]; if (Object.keys(this._roomWidgetEcho[roomId]).length === 0) delete this._roomWidgetEcho[roomId]; - this.emit('update'); + this.emit('update', roomId, widgetId); } } diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts new file mode 100644 index 0000000000..377512223a --- /dev/null +++ b/src/stores/WidgetStore.ts @@ -0,0 +1,209 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +import { ActionPayload } from "../dispatcher/payloads"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; +import defaultDispatcher from "../dispatcher/dispatcher"; +import SettingsStore from "../settings/SettingsStore"; +import WidgetEchoStore from "../stores/WidgetEchoStore"; +import WidgetUtils from "../utils/WidgetUtils"; +import {SettingLevel} from "../settings/SettingLevel"; +import {WidgetType} from "../widgets/WidgetType"; +import {UPDATE_EVENT} from "./AsyncStore"; + +interface IState {} + +export interface IApp { + id: string; + type: string; + roomId: string; + eventId: string; + creatorUserId: string; + waitForIframeLoad?: boolean; + // eslint-disable-next-line camelcase + avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765 +} + +interface IRoomWidgets { + widgets: IApp[]; + pinned: Record; +} + +// TODO consolidate WidgetEchoStore into this +// TODO consolidate ActiveWidgetStore into this +export default class WidgetStore extends AsyncStoreWithClient { + private static internalInstance = new WidgetStore(); + + private widgetMap = new Map(); + private roomMap = new Map(); + + private constructor() { + super(defaultDispatcher, {}); + + SettingsStore.watchSetting("Widgets.pinned", null, this.onPinnedWidgetsChange); + WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); + } + + public static get instance(): WidgetStore { + return WidgetStore.internalInstance; + } + + private initRoom(roomId: string) { + if (!this.roomMap.has(roomId)) { + this.roomMap.set(roomId, { + pinned: {}, + widgets: [], + }); + } + } + + protected async onReady(): Promise { + this.matrixClient.on("RoomState.events", this.onRoomStateEvents); + this.matrixClient.getRooms().forEach((room: Room) => { + const pinned = SettingsStore.getValue("Widgets.pinned", room.roomId); + + if (pinned || WidgetUtils.getRoomWidgets(room).length) { + this.initRoom(room.roomId); + } + + if (pinned) { + this.getRoom(room.roomId).pinned = pinned; + } + + this.loadRoomWidgets(room); + }); + this.emit(UPDATE_EVENT); + } + + protected async onNotReady(): Promise { + this.matrixClient.off("RoomState.events", this.onRoomStateEvents); + this.widgetMap = new Map(); + this.roomMap = new Map(); + await this.reset({}); + } + + // We don't need this, but our contract says we do. + protected async onAction(payload: ActionPayload) { + return; + } + + private onWidgetEchoStoreUpdate = (roomId: string, widgetId: string) => { + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit(UPDATE_EVENT); + }; + + private generateApps(room: Room): IApp[] { + return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => { + return WidgetUtils.makeAppConfig( + ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), + ); + }); + } + + private loadRoomWidgets(room: Room) { + const roomInfo = this.roomMap.get(room.roomId); + roomInfo.widgets = []; + this.generateApps(room).forEach(app => { + this.widgetMap.set(app.id, app); + roomInfo.widgets.push(app); + }); + this.emit(room.roomId); + } + + private onRoomStateEvents = (ev: MatrixEvent) => { + if (ev.getType() !== "im.vector.modular.widgets") return; + const roomId = ev.getRoomId(); + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit(UPDATE_EVENT); + }; + + public getRoomId = (widgetId: string) => { + const app = this.widgetMap.get(widgetId); + if (!app) return null; + return app.roomId; + } + + public getRoom = (roomId: string) => { + return this.roomMap.get(roomId); + }; + + private onPinnedWidgetsChange = (settingName: string, roomId: string) => { + this.initRoom(roomId); + this.getRoom(roomId).pinned = SettingsStore.getValue(settingName, roomId); + this.emit(roomId); + this.emit(UPDATE_EVENT); + }; + + public isPinned(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + + let pinned = roomInfo && roomInfo.pinned[widgetId]; + // Jitsi widgets should be pinned by default + if (pinned === undefined && WidgetType.JITSI.matches(this.widgetMap.get(widgetId).type)) pinned = true; + return pinned; + } + + public canPin(widgetId: string) { + // only allow pinning up to a max of two as we do not yet have grid splits + // the only case it will go to three is if you have two and then a Jitsi gets added + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + return roomInfo && Object.keys(roomInfo.pinned).length < 2; + } + + public pinWidget(widgetId: string) { + this.setPinned(widgetId, true); + } + + public unpinWidget(widgetId: string) { + this.setPinned(widgetId, false); + } + + private setPinned(widgetId: string, value: boolean) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned[widgetId] = value; + + // Clean up the pinned record + Object.keys(roomInfo).forEach(wId => { + if (!roomInfo.widgets.some(w => w.id === wId)) { + delete roomInfo.pinned[wId]; + } + }); + + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned); + this.emit(roomId); + this.emit(UPDATE_EVENT); + } + + public getApps(room: Room, pinned?: boolean): IApp[] { + const roomInfo = this.getRoom(room.roomId); + if (!roomInfo) return []; + if (pinned) { + return roomInfo.widgets.filter(app => this.isPinned(app.id)); + } + return roomInfo.widgets; + } +} + +window.mxWidgetStore = WidgetStore.instance; diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index 5467716576..512946828b 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -31,6 +31,19 @@ export default class ResizeNotifier extends EventEmitter { // with default options, will call fn once at first call, and then every x ms // if there was another call in that timespan this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200); + this._isResizing = false; + } + + get isResizing() { + return this._isResizing; + } + + startResizing() { + this._isResizing = true; + } + + stopResizing() { + this._isResizing = false; } _noisyMiddlePanel() { diff --git a/src/utils/ShieldUtils.ts b/src/utils/ShieldUtils.ts index 878ed3959c..5fe653fed0 100644 --- a/src/utils/ShieldUtils.ts +++ b/src/utils/ShieldUtils.ts @@ -18,7 +18,13 @@ interface Room { roomId: string; } -export async function shieldStatusForRoom(client: Client, room: Room): Promise { +export enum E2EStatus { + Warning = "warning", + Verified = "verified", + Normal = "normal" +} + +export async function shieldStatusForRoom(client: Client, room: Room): Promise { const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId); const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId); @@ -33,7 +39,7 @@ export async function shieldStatusForRoom(client: Client, room: Room): Promise { + console.error("Failed to get screenshot", err); + }).then((screenshot) => { + dis.dispatch({ + action: 'picture_snapshot', + file: screenshot, + }, true); + }); + } } diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js index 466d1ed57d..3e510ffee9 100644 --- a/src/utils/permalinks/Permalinks.js +++ b/src/utils/permalinks/Permalinks.js @@ -332,6 +332,9 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string { if (permalinkParts.roomIdOrAlias) { const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : ''; permalink = `#/room/${permalinkParts.roomIdOrAlias}${eventIdPart}`; + if (permalinkParts.viaServers.length > 0) { + permalink += new SpecPermalinkConstructor().encodeServerCandidates(permalinkParts.viaServers); + } } else if (permalinkParts.groupId) { permalink = `#/group/${permalinkParts.groupId}`; } else if (permalinkParts.userId) { diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts index a52f8182aa..ca8de4468a 100644 --- a/src/widgets/Jitsi.ts +++ b/src/widgets/Jitsi.ts @@ -34,6 +34,30 @@ export class Jitsi { return this.domain || 'jitsi.riot.im'; } + /** + * Checks for auth needed by looking up a well-known file + * + * If the file does not exist, we assume no auth. + * + * See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification + */ + public async getJitsiAuth(): Promise { + if (!this.preferredDomain) { + return null; + } + let data; + try { + const response = await fetch(`https://${this.preferredDomain}/.well-known/element/jitsi`); + data = await response.json(); + } catch (error) { + return null; + } + if (data.auth) { + return data.auth; + } + return null; + } + public start() { const cli = MatrixClientPeg.get(); cli.on("WellKnown.client", this.update); diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 15603e9437..672cbf2a56 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -34,6 +34,7 @@ export enum KnownWidgetActions { GetCapabilities = "capabilities", SendEvent = "send_event", UpdateVisibility = "visibility", + GetOpenIDCredentials = "get_openid", ReceiveOpenIDCredentials = "openid_credentials", SetAlwaysOnScreen = "set_always_on_screen", ClientReady = "im.vector.ready", @@ -64,6 +65,13 @@ export interface FromWidgetRequest extends WidgetRequest { response: any; } +export interface OpenIDCredentials { + accessToken: string; + tokenType: string; + matrixServerName: string; + expiresIn: number; +} + /** * Handles Element <--> Widget interactions for embedded/standalone widgets. * @@ -73,10 +81,12 @@ export interface FromWidgetRequest extends WidgetRequest { * the given promise resolves. */ export class WidgetApi extends EventEmitter { - private origin: string; + private readonly origin: string; private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {}; - private readyPromise: Promise; + private readonly readyPromise: Promise; private readyPromiseResolve: () => void; + private openIDCredentialsCallback: () => void; + public openIDCredentials: OpenIDCredentials; /** * Set this to true if your widget is expecting a ready message from the client. False otherwise (default). @@ -120,6 +130,10 @@ export class WidgetApi extends EventEmitter { // Acknowledge that we're shut down now this.replyToRequest(payload, {}); }); + } else if (payload.action === KnownWidgetActions.ReceiveOpenIDCredentials) { + // Save OpenID credentials + this.setOpenIDCredentials(payload); + this.replyToRequest(payload, {}); } else { console.warn(`[WidgetAPI] Got unexpected action: ${payload.action}`); } @@ -134,6 +148,32 @@ export class WidgetApi extends EventEmitter { }); } + public setOpenIDCredentials(value: WidgetRequest) { + const data = value.data; + if (data.state === 'allowed') { + this.openIDCredentials = { + accessToken: data.access_token, + tokenType: data.token_type, + matrixServerName: data.matrix_server_name, + expiresIn: data.expires_in, + } + } else if (data.state === 'blocked') { + this.openIDCredentials = null; + } + if (['allowed', 'blocked'].includes(data.state) && this.openIDCredentialsCallback) { + this.openIDCredentialsCallback() + } + } + + public requestOpenIDCredentials(credentialsResponseCallback: () => void) { + this.openIDCredentialsCallback = credentialsResponseCallback; + this.callAction( + KnownWidgetActions.GetOpenIDCredentials, + {}, + this.setOpenIDCredentials, + ); + } + public waitReady(): Promise { return this.readyPromise; } diff --git a/test/end-to-end-tests/src/usecases/memberlist.js b/test/end-to-end-tests/src/usecases/memberlist.js index e974eea95b..ed7f0e389b 100644 --- a/test/end-to-end-tests/src/usecases/memberlist.js +++ b/test/end-to-end-tests/src/usecases/memberlist.js @@ -16,6 +16,7 @@ limitations under the License. */ const assert = require('assert'); +const {openRoomSummaryCard} = require("./rightpanel"); async function openMemberInfo(session, name) { const membersAndNames = await getMembersInMemberlist(session); @@ -63,17 +64,11 @@ module.exports.verifyDeviceForUser = async function(session, name, expectedDevic }; async function getMembersInMemberlist(session) { - const memberPanelButton = await session.query(".mx_RightPanel_membersButton"); - try { - await session.query(".mx_RightPanel_headerButton_highlight", 500); - // Right panel is open - toggle it to ensure it's the member list - // Sometimes our tests have this opened to MemberInfo - await memberPanelButton.click(); - await memberPanelButton.click(); - } catch (e) { - // Member list is closed - open it - await memberPanelButton.click(); - } + await openRoomSummaryCard(session); + const memberPanelButton = await session.query(".mx_RoomSummaryCard_icon_people"); + // We are back at the room summary card + await memberPanelButton.click(); + const memberNameElements = await session.queryAll(".mx_MemberList .mx_EntityTile_name"); return Promise.all(memberNameElements.map(async (el) => { return {label: el, displayName: await session.innerText(el)}; diff --git a/test/end-to-end-tests/src/usecases/rightpanel.js b/test/end-to-end-tests/src/usecases/rightpanel.js new file mode 100644 index 0000000000..ae6bb2c771 --- /dev/null +++ b/test/end-to-end-tests/src/usecases/rightpanel.js @@ -0,0 +1,43 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +module.exports.openRoomRightPanel = async function(session) { + try { + await session.query('.mx_RoomHeader .mx_RightPanel_headerButton_highlight[aria-label="Room Info"]'); + } catch (e) { + // If the room summary is not yet open, open it + const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); + await roomSummaryButton.click(); + } +}; + +module.exports.goBackToRoomSummaryCard = async function(session) { + for (let i = 0; i < 5; i++) { + try { + const backButton = await session.query(".mx_BaseCard_back", 500); + // Right panel is open to the wrong thing - go back up to the Room Summary Card + // Sometimes our tests have this opened to MemberInfo + await backButton.click(); + } catch (e) { + break; // stop trying to go further back + } + } +}; + +module.exports.openRoomSummaryCard = async function(session) { + await module.exports.openRoomRightPanel(session); + await module.exports.goBackToRoomSummaryCard(session); +}; diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 11e2f52c6e..abd4488db2 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -16,6 +16,7 @@ limitations under the License. */ const assert = require('assert'); +const {openRoomSummaryCard} = require("./rightpanel"); const {acceptDialog} = require('./dialog'); async function setSettingsToggle(session, toggle, enabled) { @@ -45,7 +46,10 @@ async function findTabs(session) { /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); - const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[aria-label=Settings]"); + + await openRoomSummaryCard(session); + + const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); await settingsButton.click(); //find tabs diff --git a/yarn.lock b/yarn.lock index 5bd2be1567..ec099bbf7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7557,6 +7557,11 @@ retry@^0.10.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +rfc4648@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd" + integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"