Merge branch 'develop' into dbkr/module_experiments
7
.github/CODEOWNERS
vendored
@ -18,9 +18,10 @@
|
||||
/playwright/e2e/settings/encryption-user-tab/ @element-hq/element-crypto-web-reviewers
|
||||
|
||||
|
||||
/src/models/Call.ts @element-hq/element-call-reviewers
|
||||
/src/call-types.ts @element-hq/element-call-reviewers
|
||||
/src/components/views/voip @element-hq/element-call-reviewers
|
||||
/src/models/Call.ts @element-hq/element-call-reviewers
|
||||
/src/call-types.ts @element-hq/element-call-reviewers
|
||||
/src/components/views/voip @element-hq/element-call-reviewers
|
||||
/playwright/e2e/voip/element-call.spec.ts @element-hq/element-call-reviewers
|
||||
|
||||
# Ignore translations as those will be updated by GHA for Localazy download
|
||||
/src/i18n/strings
|
||||
|
||||
19
.github/workflows/build.yml
vendored
@ -10,8 +10,7 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
# develop pushes and repository_dispatch handled in build_develop.yaml
|
||||
env:
|
||||
# These must be set for fetchdep.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
# This must be set for fetchdep.sh to get the right branch
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
@ -45,7 +44,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
# Disable cache on Windows as it is slower than not caching
|
||||
# https://github.com/actions/setup-node/issues/975
|
||||
@ -56,15 +55,7 @@ jobs:
|
||||
- run: yarn config set network-timeout 300000
|
||||
|
||||
- name: Fetch layered build
|
||||
id: layered_build
|
||||
env:
|
||||
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
run: |
|
||||
scripts/layered.sh
|
||||
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD)
|
||||
echo "VERSION=$VECTOR_SHA--js-$JSSDK_SHA" >> $GITHUB_OUTPUT
|
||||
run: ./scripts/layered.sh
|
||||
|
||||
- name: Copy config
|
||||
run: cp element.io/develop/config.json config.json
|
||||
@ -72,9 +63,7 @@ jobs:
|
||||
- name: Build
|
||||
env:
|
||||
CI_PACKAGE: true
|
||||
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||
run: |
|
||||
yarn build
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
|
||||
2
.github/workflows/build_develop.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
repository: matrix-org/matrix-js-sdk
|
||||
path: matrix-js-sdk
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
cache-dependency-path: element-web/yarn.lock
|
||||
|
||||
19
.github/workflows/end-to-end-tests.yaml
vendored
@ -54,21 +54,16 @@ jobs:
|
||||
with:
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Fetch layered build
|
||||
id: layered_build
|
||||
env:
|
||||
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
run: |
|
||||
scripts/layered.sh
|
||||
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD)
|
||||
echo "VERSION=$VECTOR_SHA--js-$JSSDK_SHA" >> $GITHUB_OUTPUT
|
||||
run: scripts/layered.sh
|
||||
|
||||
- name: Copy config
|
||||
run: cp element.io/develop/config.json config.json
|
||||
@ -76,9 +71,7 @@ jobs:
|
||||
- name: Build
|
||||
env:
|
||||
CI_PACKAGE: true
|
||||
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||
run: |
|
||||
yarn build
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
@ -89,7 +82,7 @@ jobs:
|
||||
|
||||
- name: Calculate runner variables
|
||||
id: runner-vars
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
||||
@ -140,7 +133,7 @@ jobs:
|
||||
name: webapp
|
||||
path: webapp
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
cache-dependency-path: yarn.lock
|
||||
@ -207,7 +200,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
if: inputs.skip != true
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
4
.github/workflows/issue_closed.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
name: Tidy closed issues
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
id: main
|
||||
with:
|
||||
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
|
||||
@ -142,7 +142,7 @@ jobs:
|
||||
});
|
||||
}
|
||||
}
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
name: Close duplicate as Not Planned
|
||||
if: steps.main.outputs.closeAsNotPlanned
|
||||
with:
|
||||
|
||||
2
.github/workflows/pending-reviews.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+"
|
||||
RELEASE_BLOCKERS_URL: "https://github.com/pulls?q=is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+sort%3Aupdated-desc+label%3AX-Release-Blocker+"
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||
ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||
|
||||
@ -8,7 +8,7 @@ jobs:
|
||||
name: Check PR base branch
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const baseBranch = context.payload.pull_request.base.ref;
|
||||
|
||||
@ -26,7 +26,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
13
.github/workflows/static_analysis.yaml
vendored
@ -12,8 +12,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# These must be set for fetchdep.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
# This must be set for fetchdep.sh to get the right branch
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
permissions: {} # No permissions required
|
||||
@ -25,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@ -70,7 +69,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@ -88,7 +87,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@ -106,7 +105,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@ -124,7 +123,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
||||
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "yarn"
|
||||
|
||||
4
.github/workflows/triage-labelled.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'good first issue') ||
|
||||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
|
||||
2
.github/workflows/triage-stale.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
||||
- uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10
|
||||
with:
|
||||
operations-per-run: 100
|
||||
|
||||
|
||||
49
.github/workflows/triage-unlabelled.yml
vendored
@ -5,44 +5,25 @@ on:
|
||||
types: [unlabeled]
|
||||
permissions: {}
|
||||
jobs:
|
||||
Move_Unabeled_Issue_On_Project_Board:
|
||||
move_no_longer_needs_info_issues:
|
||||
name: Move no longer X-Needs-Info issues to Triaged
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
repository-projects: read
|
||||
if: >
|
||||
${{
|
||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
|
||||
env:
|
||||
BOARD_NAME: "Issue triage"
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
REPO: ${{ github.event.repository.name }}
|
||||
ISSUE: ${{ github.event.issue.number }}
|
||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info')
|
||||
steps:
|
||||
- name: Check if issue is already in "${{ env.BOARD_NAME }}"
|
||||
run: |
|
||||
json=$(curl -s -H 'Content-Type: application/json' -H "Authorization: bearer ${{ secrets.GITHUB_TOKEN }}" -X POST -d '{"query": "query($issue: Int!, $owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { issue(number: $issue) { projectCards { nodes { project { name } isArchived } } } } } ", "variables" : "{ \"issue\": '${ISSUE}', \"owner\": \"'${OWNER}'\", \"repo\": \"'${REPO}'\" }" }' https://api.github.com/graphql)
|
||||
if echo $json | jq '.data.repository.issue.projectCards.nodes | length'; then
|
||||
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].project.name') =~ "${BOARD_NAME}" ]]; then
|
||||
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].isArchived') == 'true' ]]; then
|
||||
echo "Issue is already in Project '$BOARD_NAME', but is archived - skipping workflow";
|
||||
echo "SKIP_ACTION=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Issue is already in Project '$BOARD_NAME', proceeding";
|
||||
echo "ALREADY_IN_BOARD=true" >> $GITHUB_ENV
|
||||
fi
|
||||
else
|
||||
echo "Issue is not in project '$BOARD_NAME', cancelling this workflow"
|
||||
echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV
|
||||
fi
|
||||
fi
|
||||
- name: Move issue
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36
|
||||
if: ${{ env.ALREADY_IN_BOARD == 'true' && env.SKIP_ACTION != 'true' }}
|
||||
- id: set_fields
|
||||
uses: nipe0324/update-project-v2-item-field@c4af58452d1c5a788c1ea4f20e073fa722ec4a6b #v2.0.2
|
||||
with:
|
||||
project: Issue triage
|
||||
column: Triaged
|
||||
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
skip-update-script: |
|
||||
const isIssue = item.type === 'ISSUE'
|
||||
const status = item.fieldValues['Status']
|
||||
return !isIssue || status !== 'Needs info'
|
||||
field-name: Status
|
||||
field-value: "Triaged"
|
||||
env:
|
||||
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
|
||||
|
||||
remove_Z-Labs_label:
|
||||
name: Remove Z-Labs label when features behind labs flags are removed
|
||||
@ -62,7 +43,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
|
||||
contains(github.event.issue.labels.*.name, 'Z-Labs')
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
|
||||
2
.github/workflows/update-jitsi.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
2
.github/workflows/update-topics.yaml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: Matrix
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import type { ArgTypes, Preview, Decorator } from "@storybook/react-vite";
|
||||
import { addons } from "storybook/preview-api";
|
||||
import type { ArgTypes, Preview, Decorator, ReactRenderer, StrictArgs } from "@storybook/react-vite";
|
||||
|
||||
import "../res/css/shared.pcss";
|
||||
import "./preview.css";
|
||||
import React, { useLayoutEffect } from "react";
|
||||
import { FORCE_RE_RENDER } from "storybook/internal/core-events";
|
||||
import { setLanguage } from "../src/shared-components/utils/i18n";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { StoryContext } from "storybook/internal/csf";
|
||||
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
@ -59,29 +58,9 @@ const withThemeProvider: Decorator = (Story, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
const LanguageSwitcher: React.FC<{
|
||||
language: string;
|
||||
}> = ({ language }) => {
|
||||
useLayoutEffect(() => {
|
||||
const changeLanguage = async (language: string) => {
|
||||
await setLanguage(language);
|
||||
// Force the component to re-render to apply the new language
|
||||
addons.getChannel().emit(FORCE_RE_RENDER);
|
||||
};
|
||||
changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const withLanguageProvider: Decorator = (Story, context) => {
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher language={context.globals.language} />
|
||||
<Story />
|
||||
</>
|
||||
);
|
||||
};
|
||||
async function languageLoader(context: StoryContext<ReactRenderer, StrictArgs>): Promise<void> {
|
||||
await setLanguage(context.globals.language);
|
||||
}
|
||||
|
||||
const withTooltipProvider: Decorator = (Story) => {
|
||||
return (
|
||||
@ -93,7 +72,7 @@ const withTooltipProvider: Decorator = (Story) => {
|
||||
|
||||
const preview: Preview = {
|
||||
tags: ["autodocs"],
|
||||
decorators: [withThemeProvider, withLanguageProvider, withTooltipProvider],
|
||||
decorators: [withThemeProvider, withTooltipProvider],
|
||||
parameters: {
|
||||
options: {
|
||||
storySort: {
|
||||
@ -108,6 +87,7 @@ const preview: Preview = {
|
||||
test: "error",
|
||||
},
|
||||
},
|
||||
loaders: [languageLoader],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
38
CHANGELOG.md
@ -1,3 +1,41 @@
|
||||
Changes in [1.12.0](https://github.com/element-hq/element-web/releases/tag/v1.12.0) (2025-09-23)
|
||||
================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Remove remaining support for outdated .well-known settings ([#30702](https://github.com/element-hq/element-web/pull/30702)). Contributed by @richvdh.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Add decline button to call notification toast (use new notification event) ([#30729](https://github.com/element-hq/element-web/pull/30729)). Contributed by @toger5.
|
||||
* Use the new room list by default ([#30640](https://github.com/element-hq/element-web/pull/30640)). Contributed by @langleyd.
|
||||
* "Verify this device" redesign ([#30596](https://github.com/element-hq/element-web/pull/30596)). Contributed by @uhoreg.
|
||||
* Set Element Call "intents" when starting and answering DM calls. ([#30730](https://github.com/element-hq/element-web/pull/30730)). Contributed by @Half-Shot.
|
||||
* Add axe compliance for new room list ([#30700](https://github.com/element-hq/element-web/pull/30700)). Contributed by @langleyd.
|
||||
* Stop ringing and remove toast if another device answers a RTC call. ([#30728](https://github.com/element-hq/element-web/pull/30728)). Contributed by @Half-Shot.
|
||||
* Automatically adjust history visibility when making a room private ([#30713](https://github.com/element-hq/element-web/pull/30713)). Contributed by @Half-Shot.
|
||||
* Release announcement for new room list ([#30675](https://github.com/element-hq/element-web/pull/30675)). Contributed by @dbkr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport staging] Room list: make the filter resize correctly ([#30795](https://github.com/element-hq/element-web/pull/30795)). Contributed by @RiotRobot.
|
||||
* [Backport staging] Avoid flicker of the room list filter on resize ([#30794](https://github.com/element-hq/element-web/pull/30794)). Contributed by @RiotRobot.
|
||||
* Don't show release announcements while toasts are displayed ([#30770](https://github.com/element-hq/element-web/pull/30770)). Contributed by @dbkr.
|
||||
* Fix enabling key backup not working if there is an untrusted key backup ([#30707](https://github.com/element-hq/element-web/pull/30707)). Contributed by @Half-Shot.
|
||||
* Force `preload` to be false when setting an intent on an Element Call. ([#30759](https://github.com/element-hq/element-web/pull/30759)). Contributed by @Half-Shot.
|
||||
* Fix handling of 413 server response when uploading media ([#30737](https://github.com/element-hq/element-web/pull/30737)). Contributed by @hughns.
|
||||
* Make landmark navigation work with new room list ([#30747](https://github.com/element-hq/element-web/pull/30747)). Contributed by @dbkr.
|
||||
* Prevent voice message from displaying spurious errors ([#30736](https://github.com/element-hq/element-web/pull/30736)). Contributed by @florianduros.
|
||||
* Align default avatar and fix colors in composer pills ([#30739](https://github.com/element-hq/element-web/pull/30739)). Contributed by @florianduros.
|
||||
* Use configured URL for link to desktop app in message search settings ([#30742](https://github.com/element-hq/element-web/pull/30742)). Contributed by @t3chguy.
|
||||
* Fix history visibility when creating space rooms ([#30745](https://github.com/element-hq/element-web/pull/30745)). Contributed by @dbkr.
|
||||
* Check HTML-encoded quotes when handling translations for embedded pages (such as welcome.html) ([#30743](https://github.com/element-hq/element-web/pull/30743)). Contributed by @Half-Shot.
|
||||
* Fix local room encryption status always not enabled ([#30461](https://github.com/element-hq/element-web/pull/30461)). Contributed by @BillCarsonFr.
|
||||
* fix: make url in topic in room intro clickable ([#30686](https://github.com/element-hq/element-web/pull/30686)). Contributed by @florianduros.
|
||||
* Block change recovery key button while a change is ongoing. ([#30664](https://github.com/element-hq/element-web/pull/30664)). Contributed by @Half-Shot.
|
||||
* Hide advanced settings during room creation when `UIFeature.advancedSettings=false` ([#30684](https://github.com/element-hq/element-web/pull/30684)). Contributed by @florianduros.
|
||||
* A11y: improve accessibility of pinned messages ([#30558](https://github.com/element-hq/element-web/pull/30558)). Contributed by @florianduros.
|
||||
|
||||
|
||||
Changes in [1.11.112](https://github.com/element-hq/element-web/releases/tag/v1.11.112) (2025-09-16)
|
||||
====================================================================================================
|
||||
Fix [CVE-2025-59161](https://www.cve.org/CVERecord?id=CVE-2025-59161) / [GHSA-m6c8-98f4-75rr](https://github.com/element-hq/element-web/security/advisories/GHSA-m6c8-98f4-75rr)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
|
||||
# syntax=docker.io/docker/dockerfile:1.18-labs@sha256:79cdc14e1c220efb546ad14a8ebc816e3277cd72d27195ced5bebdd226dd1025
|
||||
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:f7f28d1962d93cc096ea6327378d990284757fec281ce48e42436e7b4b167fa2 AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:f8c398a3ad2612293e8827915c056ed0f5cc708b0f676274bb6c732e3c10f93d AS builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
|
||||
RUN cp /src/config.sample.json /src/webapp/config.json
|
||||
|
||||
# App
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:0d019e980f83728002de7a6d8819d0d4af7179046d3946b8b37749953fbb28e6
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:14b127ed799301a21a1798516443c675237120c76b9a738d43c5e4747de4b1c9
|
||||
|
||||
# Need root user to install packages & manipulate the usr directory
|
||||
USER root
|
||||
|
||||
69
docs/MVVM-v1.md
Normal file
@ -0,0 +1,69 @@
|
||||
# MVVM
|
||||
|
||||
_Deprecated_, see [MVVM.md](./MVVM.md) for the current version.
|
||||
|
||||
General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
|
||||
|
||||
1. Model: This is where the business logic and data resides.
|
||||
2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
|
||||
3. View: This is the UI code itself and depends on the view model.
|
||||
|
||||
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
|
||||
|
||||
### Practical guidelines for MVVM in element-web
|
||||
|
||||
#### Model
|
||||
|
||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||
|
||||
#### View Model
|
||||
|
||||
1. View model is always a custom react hook named like `useFooViewModel()`.
|
||||
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
||||
```ts
|
||||
inteface FooViewState {
|
||||
somethingUseful: string;
|
||||
somethingElse: BarType;
|
||||
update: () => Promise<void>
|
||||
...
|
||||
}
|
||||
```
|
||||
3. Any react state that your UI needs must be in the view model.
|
||||
|
||||
#### View
|
||||
|
||||
1. Views are simple react components (eg: `FooView`).
|
||||
2. Views usually start by calling the view model hook, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = (props: IProps) => {
|
||||
const vm = useFooViewModel();
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
3. Views are also allowed to accept the view model as a prop, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
4. Multiple views can share the same view model if necessary.
|
||||
|
||||
### Benefits
|
||||
|
||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||
|
||||
### Example
|
||||
|
||||
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
||||
98
docs/MVVM.md
@ -10,58 +10,80 @@ If you do MVVM right, your view should be dumb i.e it gets data from the view mo
|
||||
|
||||
### Practical guidelines for MVVM in element-web
|
||||
|
||||
A first documentation and implementation of MVVM was done in [MVVM-v1.md](MVVM-v1.md). This v1 version is now deprecated and this document describes the current implementation.
|
||||
|
||||
#### Model
|
||||
|
||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||
|
||||
#### View Model
|
||||
|
||||
1. View model is always a custom react hook named like `useFooViewModel()`.
|
||||
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
||||
```ts
|
||||
inteface FooViewState {
|
||||
somethingUseful: string;
|
||||
somethingElse: BarType;
|
||||
update: () => Promise<void>
|
||||
...
|
||||
}
|
||||
```
|
||||
3. Any react state that your UI needs must be in the view model.
|
||||
|
||||
#### View
|
||||
|
||||
1. Views are simple react components (eg: `FooView`).
|
||||
2. Views usually start by calling the view model hook, eg:
|
||||
1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/src/shared-components). Develop it in storybook!
|
||||
2. Views are simple react components (eg: `FooView`).
|
||||
3. Views use [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) internally where the view model is the external store.
|
||||
4. Views should define the interface of the view model they expect:
|
||||
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = (props: IProps) => {
|
||||
const vm = useFooViewModel();
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
// Snapshot is the return type of your view model
|
||||
interface FooViewSnapshot {
|
||||
value: string;
|
||||
}
|
||||
|
||||
// To call function on the view model
|
||||
interface FooViewActions {
|
||||
doSomething: () => void;
|
||||
}
|
||||
|
||||
// ViewModel is a type defining the methods needed for `useSyncExternalStore`
|
||||
// https://github.com/element-hq/element-web/blob/develop/src/shared-components/ViewModel.ts
|
||||
type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions;
|
||||
|
||||
interface FooViewProps {
|
||||
vm: FooViewModel;
|
||||
}
|
||||
|
||||
function FooView({ vm }: FooViewProps) {
|
||||
// useViewModel is a helper function that uses useSyncExternalStore under the hood
|
||||
const { value } = useViewModel(vm);
|
||||
return (
|
||||
<button type="button" onClick={() => vm.doSomething()}>
|
||||
{value}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
3. Views are also allowed to accept the view model as a prop, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
|
||||
5. Multiple views can share the same view model if necessary.
|
||||
6. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/shared-components/audio/AudioPlayerView/AudioPlayerView.tsx)
|
||||
|
||||
#### View Model
|
||||
|
||||
1. A View model is a class extending [`BaseViewModel`](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/base/BaseViewModel.ts).
|
||||
2. Implements the interface defined in the view (e.g `FooViewModel` in the example above).
|
||||
3. View models define a snapshot type that defines the data the view will consume. The snapshot is immutable and can only be changed by calling `this.snapshot.set(...)` in the view model. This will trigger a re-render in the view.
|
||||
|
||||
```ts
|
||||
interface Props {
|
||||
propsValue: string;
|
||||
}
|
||||
|
||||
class FooViewModel extends BaseViewModel<FooViewSnapshot, Props> implements FooViewModel {
|
||||
constructor(props: Props) {
|
||||
// Call super with initial snapshot
|
||||
super(props, { value: "initial" });
|
||||
}
|
||||
|
||||
public doSomething() {
|
||||
// Call this.snapshot.set to update the snapshot
|
||||
this.snapshot.set({ value: "changed" });
|
||||
}
|
||||
}
|
||||
```
|
||||
4. Multiple views can share the same view model if necessary.
|
||||
|
||||
4. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/audio/AudioPlayerViewModel.ts)
|
||||
|
||||
### Benefits
|
||||
|
||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||
|
||||
### Example
|
||||
|
||||
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
||||
|
||||
@ -41,7 +41,7 @@ const config: Config = {
|
||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
||||
},
|
||||
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
|
||||
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error)).+$"],
|
||||
collectCoverageFrom: [
|
||||
"<rootDir>/src/**/*.{js,ts,tsx}",
|
||||
// getSessionLock is piped into a different JS context via stringification, and the coverage functionality is
|
||||
|
||||
15
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.11.112",
|
||||
"version": "1.12.0",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@ -75,11 +75,11 @@
|
||||
"resolutions": {
|
||||
"**/pretty-format/react-is": "19.1.1",
|
||||
"@playwright/test": "1.54.2",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react": "19.1.13",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"oidc-client-ts": "3.3.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"caniuse-lite": "1.0.30001724",
|
||||
"caniuse-lite": "1.0.30001741",
|
||||
"testcontainers": "^11.0.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
||||
@ -98,7 +98,7 @@
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@vector-im/compound-design-tokens": "^6.0.0",
|
||||
"@vector-im/compound-web": "^8.1.2",
|
||||
"@vector-im/matrix-wysiwyg": "2.39.0",
|
||||
"@vector-im/matrix-wysiwyg": "2.40.0",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
"@zxcvbn-ts/language-en": "^3.0.2",
|
||||
@ -142,7 +142,7 @@
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"posthog-js": "1.261.0",
|
||||
"posthog-js": "1.265.1",
|
||||
"qrcode": "1.5.4",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "^19.0.0",
|
||||
@ -159,7 +159,7 @@
|
||||
"tar-js": "^0.3.0",
|
||||
"temporal-polyfill": "^0.3.0",
|
||||
"ua-parser-js": "1.0.40",
|
||||
"uuid": "^11.0.0",
|
||||
"uuid": "^13.0.0",
|
||||
"what-input": "^5.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -223,7 +223,7 @@
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/pako": "^2.0.0",
|
||||
"@types/qrcode": "^1.3.5",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react": "19.1.13",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
@ -232,7 +232,6 @@
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/tar-js": "^0.3.5",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
||||
"@typescript-eslint/parser": "^8.19.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
|
||||
@ -24,7 +24,7 @@ const startDMWithBob = async (page: Page, bob: Bot) => {
|
||||
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
|
||||
await page.getByRole("menuitem", { name: "Start chat" }).click();
|
||||
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
|
||||
await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
|
||||
await page.getByRole("option", { name: bob.credentials.displayName }).click();
|
||||
await expect(
|
||||
page.locator(".mx_InviteDialog_userTile_pill .mx_InviteDialog_userTile_name").getByText("Bob"),
|
||||
).toBeVisible();
|
||||
|
||||
@ -201,6 +201,30 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
await enterRecoveryKeyAndCheckVerified(page, app, recoveryKey);
|
||||
});
|
||||
|
||||
test("After cancelling verify with another device, I can try again #29882", async ({ page, app, credentials }) => {
|
||||
// Regression test for https://github.com/element-hq/element-web/issues/29882
|
||||
|
||||
// Log in without verifying
|
||||
await logIntoElement(page, credentials);
|
||||
const authPage = page.locator(".mx_AuthPage");
|
||||
await authPage.getByRole("button", { name: "Skip verification for now" }).click();
|
||||
await authPage.getByRole("button", { name: "I'll verify later" }).click();
|
||||
await page.waitForSelector(".mx_MatrixChat");
|
||||
|
||||
// Start to verify with "Use another device" but cancel
|
||||
const settings = await app.settings.openUserSettings("Encryption");
|
||||
await settings.getByRole("button", { name: "Verify this device" }).click();
|
||||
await page.getByRole("button", { name: "Use another device" }).click();
|
||||
await page.locator("#mx_Dialog_Container").getByRole("button", { name: "Close dialog" }).click();
|
||||
|
||||
// Start again
|
||||
await settings.getByRole("button", { name: "Verify this device" }).click();
|
||||
|
||||
// We should be offered to use another device again.
|
||||
// (In the bug, we were immediately told that verification has been cancelled.)
|
||||
await expect(page.getByRole("button", { name: "Use another device" })).toBeVisible();
|
||||
});
|
||||
|
||||
/** Helper for the three tests above which verify by recovery key */
|
||||
async function enterRecoveryKeyAndCheckVerified(page: Page, app: ElementAppPage, recoveryKey: string) {
|
||||
await page.getByRole("button", { name: "Use recovery key" }).click();
|
||||
|
||||
@ -50,11 +50,9 @@ test.describe("Invite dialog", function () {
|
||||
await expect(other.locator(".mx_InviteDialog_identityServer")).toBeVisible();
|
||||
|
||||
// Assert that the bot id is rendered properly
|
||||
await expect(
|
||||
other.locator(".mx_InviteDialog_tile_nameStack_userId").getByText(bot.credentials.userId),
|
||||
).toBeVisible();
|
||||
await expect(other.getByRole("option", { name: botName }).getByText(bot.credentials.userId)).toBeVisible();
|
||||
|
||||
await other.locator(".mx_InviteDialog_tile_nameStack_name").getByText(botName).click();
|
||||
await other.getByRole("option", { name: botName }).click();
|
||||
|
||||
await expect(
|
||||
other.locator(".mx_InviteDialog_userTile_pill .mx_InviteDialog_userTile_name").getByText(botName),
|
||||
@ -94,10 +92,8 @@ test.describe("Invite dialog", function () {
|
||||
|
||||
await other.getByTestId("invite-dialog-input").fill(bot.credentials.userId);
|
||||
|
||||
await expect(
|
||||
other.locator(".mx_InviteDialog_tile_nameStack").getByText(bot.credentials.userId),
|
||||
).toBeVisible();
|
||||
await other.locator(".mx_InviteDialog_tile_nameStack").getByText(botName).click();
|
||||
await expect(other.getByRole("option", { name: botName }).getByText(bot.credentials.userId)).toBeVisible();
|
||||
await other.getByRole("option", { name: botName }).click();
|
||||
|
||||
await expect(
|
||||
other.locator(".mx_InviteDialog_userTile_pill .mx_InviteDialog_userTile_name").getByText(botName),
|
||||
|
||||
339
playwright/e2e/voip/element-call.spec.ts
Normal file
@ -0,0 +1,339 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { EventType, Preset } from "matrix-js-sdk/src/matrix";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import type { Credentials } from "../../plugins/homeserver";
|
||||
import type { Bot } from "../../pages/bot";
|
||||
|
||||
function assertCommonCallParameters(
|
||||
url: URLSearchParams,
|
||||
hash: URLSearchParams,
|
||||
user: Credentials,
|
||||
room: { roomId: string },
|
||||
): void {
|
||||
expect(url.has("widgetId")).toEqual(true);
|
||||
expect(url.has("parentUrl")).toEqual(true);
|
||||
|
||||
expect(hash.get("perParticipantE2EE")).toEqual("false");
|
||||
expect(hash.get("userId")).toEqual(user.userId);
|
||||
expect(hash.get("deviceId")).toEqual(user.deviceId);
|
||||
expect(hash.get("roomId")).toEqual(room.roomId);
|
||||
expect(hash.get("preload")).toEqual("false");
|
||||
}
|
||||
|
||||
async function sendRTCState(bot: Bot, roomId: string, notification?: "ring" | "notification") {
|
||||
const resp = await bot.sendStateEvent(
|
||||
roomId,
|
||||
"org.matrix.msc3401.call.member",
|
||||
{
|
||||
application: "m.call",
|
||||
call_id: "",
|
||||
device_id: "OiDFxsZrjz",
|
||||
expires: 180000000,
|
||||
foci_preferred: [
|
||||
{
|
||||
livekit_alias: roomId,
|
||||
livekit_service_url: "https://example.org",
|
||||
type: "livekit",
|
||||
},
|
||||
],
|
||||
focus_active: {
|
||||
focus_selection: "oldest_membership",
|
||||
type: "livekit",
|
||||
},
|
||||
scope: "m.room",
|
||||
},
|
||||
`_@${bot.credentials.userId}_OiDFxsZrjz_m.call`,
|
||||
);
|
||||
if (!notification) {
|
||||
return;
|
||||
}
|
||||
await bot.sendEvent(roomId, null, "org.matrix.msc4075.rtc.notification", {
|
||||
"lifetime": 30000,
|
||||
"m.mentions": {
|
||||
room: true,
|
||||
user_ids: [],
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: resp.event_id,
|
||||
rel_type: "org.matrix.msc4075.rtc.notification.parent",
|
||||
},
|
||||
"notification_type": notification,
|
||||
"sender_ts": 1758611895996,
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("Element Call", () => {
|
||||
test.use({
|
||||
config: {
|
||||
element_call: {
|
||||
use_exclusively: false,
|
||||
},
|
||||
features: {
|
||||
feature_group_calls: true,
|
||||
},
|
||||
},
|
||||
displayName: "Alice",
|
||||
botCreateOpts: {
|
||||
autoAcceptInvites: true,
|
||||
displayName: "Bob",
|
||||
},
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page, user, app }) => {
|
||||
// Mock a widget page. It doesn't need to actually be Element Call.
|
||||
await page.route("/widget.html", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: "<p> Hello world </p>",
|
||||
});
|
||||
});
|
||||
await app.settings.setValue(
|
||||
"Developer.elementCallUrl",
|
||||
null,
|
||||
SettingLevel.DEVICE,
|
||||
new URL("/widget.html#", page.url()).toString(),
|
||||
);
|
||||
});
|
||||
|
||||
test.describe("Group Chat", () => {
|
||||
test.use({
|
||||
room: async ({ page, app, user, bot }, use) => {
|
||||
const roomId = await app.client.createRoom({ name: "TestRoom", invite: [bot.credentials.userId] });
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
test("should be able to start a video call", async ({ page, user, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Video call" }).click();
|
||||
await page.getByRole("menuitem", { name: "Element Call" }).click();
|
||||
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
// Ensure we set the correct parameters for ECall.
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
expect(hash.get("intent")).toEqual("start_call");
|
||||
expect(hash.get("skipLobby")).toEqual(null);
|
||||
});
|
||||
|
||||
test("should be able to skip lobby by holding down shift", async ({ page, user, bot, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Video call" }).click();
|
||||
await page.keyboard.down("Shift");
|
||||
await page.getByRole("menuitem", { name: "Element Call" }).click();
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
expect(hash.get("intent")).toEqual("start_call");
|
||||
expect(hash.get("skipLobby")).toEqual("true");
|
||||
});
|
||||
|
||||
test("should be able to join a call in progress", async ({ page, user, bot, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
// Allow bob to create a call
|
||||
await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50);
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
// Fake a start of a call
|
||||
await sendRTCState(bot, room.roomId);
|
||||
const button = page.getByTestId("join-call-button");
|
||||
await expect(button).toBeInViewport({ timeout: 5000 });
|
||||
// And test joining
|
||||
await button.click();
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
console.log(frameUrlStr);
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
|
||||
expect(hash.get("intent")).toEqual("join_existing");
|
||||
expect(hash.get("skipLobby")).toEqual(null);
|
||||
});
|
||||
|
||||
[true, false].forEach((skipLobbyToggle) => {
|
||||
test(
|
||||
`should be able to join a call via incoming call toast (skipLobby=${skipLobbyToggle})`,
|
||||
{ tag: ["@screenshot"] },
|
||||
async ({ page, user, bot, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
// Allow bob to create a call
|
||||
await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50);
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
// Fake a start of a call
|
||||
await sendRTCState(bot, room.roomId, "notification");
|
||||
const toast = page.locator(".mx_Toast_toast");
|
||||
const button = toast.getByRole("button", { name: "Join" });
|
||||
if (skipLobbyToggle) {
|
||||
await toast.getByRole("switch").check();
|
||||
await expect(toast).toMatchScreenshot("incoming-call-group-video-toast-checked.png");
|
||||
} else {
|
||||
await toast.getByRole("switch").uncheck();
|
||||
await expect(toast).toMatchScreenshot("incoming-call-group-video-toast-unchecked.png");
|
||||
}
|
||||
|
||||
// And test joining
|
||||
await button.click();
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
console.log(frameUrlStr);
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
|
||||
expect(hash.get("intent")).toEqual("join_existing");
|
||||
expect(hash.get("skipLobby")).toEqual(skipLobbyToggle.toString());
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("DMs", () => {
|
||||
test.use({
|
||||
room: async ({ page, app, user, bot }, use) => {
|
||||
const roomId = await app.client.createRoom({
|
||||
preset: "trusted_private_chat" as Preset.TrustedPrivateChat,
|
||||
invite: [bot.credentials.userId],
|
||||
});
|
||||
await app.client.setAccountData("m.direct" as EventType.Direct, {
|
||||
[bot.credentials.userId]: [roomId],
|
||||
});
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
|
||||
test("should be able to start a video call", async ({ page, user, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Video call" }).click();
|
||||
await page.getByRole("menuitem", { name: "Element Call" }).click();
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
expect(hash.get("intent")).toEqual("start_call_dm");
|
||||
expect(hash.get("skipLobby")).toEqual(null);
|
||||
});
|
||||
|
||||
test("should be able to skip lobby by holding down shift", async ({ page, user, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Video call" }).click();
|
||||
await page.keyboard.down("Shift");
|
||||
await page.getByRole("menuitem", { name: "Element Call" }).click();
|
||||
await page.keyboard.up("Shift");
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
expect(hash.get("intent")).toEqual("start_call_dm");
|
||||
expect(hash.get("skipLobby")).toEqual("true");
|
||||
});
|
||||
|
||||
test("should be able to join a call in progress", async ({ page, user, bot, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
// Allow bob to create a call
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
// Fake a start of a call
|
||||
await sendRTCState(bot, room.roomId);
|
||||
const button = page.getByTestId("join-call-button");
|
||||
await expect(button).toBeInViewport({ timeout: 5000 });
|
||||
// And test joining
|
||||
await button.click();
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
console.log(frameUrlStr);
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
|
||||
expect(hash.get("intent")).toEqual("join_existing_dm");
|
||||
expect(hash.get("skipLobby")).toEqual(null);
|
||||
});
|
||||
|
||||
[true, false].forEach((skipLobbyToggle) => {
|
||||
test(
|
||||
`should be able to join a call via incoming call toast (skipLobby=${skipLobbyToggle})`,
|
||||
{ tag: ["@screenshot"] },
|
||||
async ({ page, user, bot, room, app }) => {
|
||||
await app.viewRoomById(room.roomId);
|
||||
// Allow bob to create a call
|
||||
await expect(page.getByText("Bob joined the room")).toBeVisible();
|
||||
// Fake a start of a call
|
||||
await sendRTCState(bot, room.roomId, "ring");
|
||||
const toast = page.locator(".mx_Toast_toast");
|
||||
const button = toast.getByRole("button", { name: "Join" });
|
||||
if (skipLobbyToggle) {
|
||||
await toast.getByRole("switch").check();
|
||||
await expect(toast).toMatchScreenshot("incoming-call-dm-video-toast-checked.png");
|
||||
} else {
|
||||
await toast.getByRole("switch").uncheck();
|
||||
await expect(toast).toMatchScreenshot("incoming-call-dm-video-toast-unchecked.png");
|
||||
}
|
||||
|
||||
// And test joining
|
||||
await button.click();
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
console.log(frameUrlStr);
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, room);
|
||||
|
||||
expect(hash.get("intent")).toEqual("join_existing_dm");
|
||||
expect(hash.get("skipLobby")).toEqual(skipLobbyToggle.toString());
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Video Rooms", () => {
|
||||
test.use({
|
||||
config: {
|
||||
features: {
|
||||
feature_video_rooms: true,
|
||||
feature_element_call_video_rooms: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
test("should be able to create and join a video room", async ({ page, user }) => {
|
||||
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
|
||||
await page.getByRole("menuitem", { name: "New video room" }).click();
|
||||
await page.getByRole("textbox", { name: "Name" }).fill("Test room");
|
||||
await page.getByRole("button", { name: "Create video room" }).click();
|
||||
await expect(page).toHaveURL(new RegExp(`/#/room/`));
|
||||
const roomId = new URL(page.url()).hash.slice("#/room/".length);
|
||||
|
||||
const frameUrlStr = await page.locator("iframe").getAttribute("src");
|
||||
await expect(frameUrlStr).toBeDefined();
|
||||
// Ensure we set the correct parameters for ECall.
|
||||
const url = new URL(frameUrlStr);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertCommonCallParameters(url.searchParams, hash, user, { roomId });
|
||||
expect(hash.get("intent")).toEqual("join_existing");
|
||||
expect(hash.get("skipLobby")).toEqual("false");
|
||||
expect(hash.get("returnToLobby")).toEqual("true");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -469,6 +469,27 @@ export class Client {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a power level to one or multiple users.
|
||||
* Will apply changes atop of current power level event.
|
||||
* @param roomId - the room to update power levels in
|
||||
* @param userId - the ID of the user or users to update power levels of
|
||||
* @param powerLevel - the numeric power level to update given users to
|
||||
*/
|
||||
public async setPowerLevel(
|
||||
roomId: string,
|
||||
userId: string | string[],
|
||||
powerLevel: number,
|
||||
): Promise<ISendEventResponse> {
|
||||
const client = await this.prepareClient();
|
||||
return client.evaluate(
|
||||
async (client, { roomId, userId, powerLevel }) => {
|
||||
return client.setPowerLevel(roomId, userId, powerLevel);
|
||||
},
|
||||
{ roomId, userId, powerLevel },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves the given room.
|
||||
* @param roomId ID of the room to leave
|
||||
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 260 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
@ -10,7 +10,7 @@ import {
|
||||
type StartedPostgreSqlContainer,
|
||||
} from "@element-hq/element-web-playwright-common/lib/testcontainers";
|
||||
|
||||
const TAG = "main@sha256:a29fa92aca82fd4cdf6b84abaa14935f111f281f9bffeb30fdb8fe2353c0108c";
|
||||
const TAG = "main@sha256:09f64cd1633f1c82756b8e7d83cec4575b15782709674b0a69a4ad2a931e4e4f";
|
||||
|
||||
/**
|
||||
* MatrixAuthenticationServiceContainer which freezes the docker digest to
|
||||
|
||||
@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
|
||||
|
||||
const TAG = "develop@sha256:40ef11b61d70bda94266324d159cc7d807e64b26ad03788e386d5084abb3c198";
|
||||
const TAG = "develop@sha256:0fd823705517826336ed5831093b1cf0b00f535884926cee994100dcddf15d1f";
|
||||
|
||||
/**
|
||||
* SynapseContainer which freezes the docker digest to stabilise tests,
|
||||
|
||||
@ -602,6 +602,7 @@ legend {
|
||||
.mx_AccessibleButton,
|
||||
.mx_IdentityServerPicker button,
|
||||
.mx_AccessSecretStorageDialog button,
|
||||
.mx_InviteDialog_section button,
|
||||
[class|="maplibregl"]
|
||||
),
|
||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton),
|
||||
@ -643,7 +644,8 @@ legend {
|
||||
.mx_ThemeChoicePanel_CustomTheme button,
|
||||
.mx_UnpinAllDialog button,
|
||||
.mx_ShareDialog button,
|
||||
.mx_EncryptionUserSettingsTab button
|
||||
.mx_EncryptionUserSettingsTab button,
|
||||
.mx_InviteDialog_section button
|
||||
):focus,
|
||||
.mx_Dialog input[type="submit"]:focus,
|
||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):focus,
|
||||
|
||||
@ -68,21 +68,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_InviteDialog_section {
|
||||
padding-bottom: $spacing-4;
|
||||
|
||||
h3 {
|
||||
font-size: $font-12px;
|
||||
color: $muted-fg-color;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> span {
|
||||
color: $primary-content;
|
||||
}
|
||||
|
||||
.mx_InviteDialog_section_showMore {
|
||||
margin: 7px 18px;
|
||||
display: block;
|
||||
@ -194,10 +179,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_InviteDialog_userSections {
|
||||
flex-grow: 1;
|
||||
padding-inline-end: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: var(--cpd-space-3x);
|
||||
gap: var(--cpd-space-3x);
|
||||
|
||||
.mx_InviteDialog_section {
|
||||
padding-bottom: 0;
|
||||
margin-top: $spacing-12;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,7 +237,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
|
||||
.mx_InviteDialog_userSections {
|
||||
margin-top: $spacing-4;
|
||||
overflow-y: auto;
|
||||
padding: 0 45px $spacing-4 0;
|
||||
}
|
||||
@ -325,48 +312,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
gap: $spacing-8 $spacing-12;
|
||||
align-items: center;
|
||||
|
||||
&.mx_InviteDialog_tile--room {
|
||||
/* mx_InviteDialog_tile_avatarStack, mx_InviteDialog_tile_nameStack, time */
|
||||
grid-template-columns: min-content auto auto;
|
||||
padding: $spacing-4 $spacing-8;
|
||||
|
||||
&:hover {
|
||||
background-color: $header-panel-bg-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mx_InviteDialog_tile--room_selected {
|
||||
border-radius: 36px;
|
||||
background-color: var(--cpd-color-bg-success-subtle);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
|
||||
mask-size: 100%;
|
||||
mask-repeat: no-repeat;
|
||||
position: absolute;
|
||||
top: 6px; /* 50% */
|
||||
left: 6px; /* 50% */
|
||||
background-color: $primary-content;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_InviteDialog_tile--room_time {
|
||||
margin-inline-start: auto;
|
||||
width: max-content;
|
||||
font-size: $font-12px;
|
||||
color: $muted-fg-color;
|
||||
}
|
||||
|
||||
.mx_InviteDialog_tile--room_highlight {
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_InviteDialog_tile--inviterError {
|
||||
grid-template-columns: max-content auto; /* max-content = avatar width */
|
||||
margin-bottom: $spacing-24;
|
||||
@ -388,15 +333,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_InviteDialog_tile_avatarStack,
|
||||
.mx_InviteDialog_tile--room_selected {
|
||||
.mx_InviteDialog_tile_avatarStack {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_InviteDialog_tile_avatarStack {
|
||||
grid-row-start: 1;
|
||||
grid-column-start: 1;
|
||||
|
||||
|
||||
@ -12,10 +12,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: unset;
|
||||
padding: unset;
|
||||
list-style-type: none;
|
||||
.mx_RoomListPrimaryFilters_list {
|
||||
/**
|
||||
* The InteractionObserver needs the height to be set to work properly.
|
||||
*/
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Echoes a version based on the git hashes of the element-web, react-sdk & js-sdk checkouts, for the case where
|
||||
# Echoes a version based on the git hashes of the element-web & js-sdk checkouts, for the case where
|
||||
# these dependencies are git checkouts.
|
||||
|
||||
set -e
|
||||
|
||||
# Since the deps are fetched from git, we can rev-parse
|
||||
# Since the deps are fetched from git & linked, we can rev-parse
|
||||
JSSDK_SHA=$(git -C node_modules/matrix-js-sdk rev-parse --short=12 HEAD)
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD) # use the ACTUAL SHA rather than assume develop
|
||||
echo $VECTOR_SHA-js-$JSSDK_SHA
|
||||
echo "$VECTOR_SHA-js-$JSSDK_SHA"
|
||||
|
||||
@ -508,4 +508,19 @@ export default abstract class BasePlatform {
|
||||
* Begin update polling, if applicable
|
||||
*/
|
||||
public startUpdater(): void {}
|
||||
|
||||
/**
|
||||
* Checks if the current session is lock-free, i.e., no other instance is holding the session lock.
|
||||
* Platforms that support session locking should override this method.
|
||||
* @returns {boolean} True if the session is lock-free, false otherwise.
|
||||
*/
|
||||
public abstract checkSessionLockFree(): boolean;
|
||||
/**
|
||||
* Attempts to acquire a session lock for this instance.
|
||||
* If another instance is detected, calls the provided callback.
|
||||
* Platforms that support session locking should override this method.
|
||||
* @param _onNewInstance Callback to invoke if a new instance is detected.
|
||||
* @returns {Promise<boolean>} True if the lock was acquired, false otherwise.
|
||||
*/
|
||||
public abstract getSessionLock(_onNewInstance: () => Promise<void>): Promise<boolean>;
|
||||
}
|
||||
|
||||
@ -390,6 +390,9 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
|
||||
const [urlPrefix, loop] = audioInfo[audioId];
|
||||
const source = await this.backgroundAudio.pickFormatAndPlay(urlPrefix, ["mp3", "ogg"], loop);
|
||||
if (this.playingSources[audioId]) {
|
||||
logger.warn(`${logPrefix} Already playing audio ${audioId}!`);
|
||||
}
|
||||
this.playingSources[audioId] = source;
|
||||
logger.debug(`${logPrefix} playing audio successfully`);
|
||||
}
|
||||
|
||||
@ -129,7 +129,6 @@ import { NotificationLevel } from "../../stores/notifications/NotificationLevel"
|
||||
import { type UserTab } from "../views/dialogs/UserTab";
|
||||
import { shouldSkipSetupEncryption } from "../../utils/crypto/shouldSkipSetupEncryption";
|
||||
import { Filter } from "../views/dialogs/spotlight/Filter";
|
||||
import { checkSessionLockFree, getSessionLock } from "../../utils/SessionLock";
|
||||
import { SessionLockStolenView } from "./auth/SessionLockStolenView";
|
||||
import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView";
|
||||
import { LoginSplashView } from "./auth/LoginSplashView";
|
||||
@ -314,7 +313,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
private async initSession(): Promise<void> {
|
||||
// The Rust Crypto SDK will break if two Element instances try to use the same datastore at once, so
|
||||
// make sure we are the only Element instance in town (on this browser/domain).
|
||||
if (!(await getSessionLock(() => this.onSessionLockStolen()))) {
|
||||
const platform = PlatformPeg.get();
|
||||
if (platform && !(await platform.getSessionLock(() => this.onSessionLockStolen()))) {
|
||||
// we failed to get the lock. onSessionLockStolen should already have been called, so nothing left to do.
|
||||
return;
|
||||
}
|
||||
@ -479,7 +479,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
// mounted.
|
||||
if (!this.sessionLoadStarted) {
|
||||
this.sessionLoadStarted = true;
|
||||
if (!checkSessionLockFree()) {
|
||||
const platform = PlatformPeg.get();
|
||||
if (platform && !platform.checkSessionLockFree()) {
|
||||
// another instance holds the lock; confirm its theft before proceeding
|
||||
setTimeout(() => this.setState({ view: Views.CONFIRM_LOCK_THEFT }), 0);
|
||||
} else {
|
||||
|
||||
@ -2620,7 +2620,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
<CallView
|
||||
room={this.state.room}
|
||||
resizing={this.state.resizing}
|
||||
skipLobby={this.roomViewStore.skipCallLobby() ?? false}
|
||||
role="main"
|
||||
onClose={this.onCallClose}
|
||||
/>
|
||||
|
||||
@ -11,7 +11,6 @@ import { type Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/m
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { humanizeTime } from "../../../utils/humanize";
|
||||
import { preventDefaultWrapper } from "../../../utils/NativeEventUtils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
@ -19,6 +18,7 @@ import BeaconStatus from "./BeaconStatus";
|
||||
import { BeaconDisplayStatus } from "./displayStatus";
|
||||
import StyledLiveBeaconIcon from "./StyledLiveBeaconIcon";
|
||||
import ShareLatestLocation from "./ShareLatestLocation";
|
||||
import { humanizeTime } from "../../../shared-components/utils/humanize";
|
||||
|
||||
interface Props {
|
||||
beacon: Beacon;
|
||||
|
||||
@ -35,7 +35,7 @@ const RoomCallBannerInner: React.FC<RoomCallBannerProps> = ({ roomId, call }) =>
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
view_call: true,
|
||||
skipLobby: "shiftKey" in ev ? ev.shiftKey : false,
|
||||
skipLobby: ("shiftKey" in ev && ev.shiftKey) || undefined,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
},
|
||||
|
||||
@ -17,6 +17,7 @@ import { EncryptionCardButtons } from "../settings/encryption/EncryptionCardButt
|
||||
import { type OpenToTabPayload } from "../../../dispatcher/payloads/OpenToTabPayload";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { UserTab } from "./UserTab";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
|
||||
interface Props {
|
||||
onFinished: (dismissed: boolean) => void;
|
||||
@ -60,7 +61,7 @@ export default class ConfirmKeyStorageOffDialog extends React.Component<Props> {
|
||||
a: (sub) => (
|
||||
<>
|
||||
<br />
|
||||
<a href="https://element.io/help#encryption5" target="_blank" rel="noreferrer noopener">
|
||||
<a href={SdkConfig.get("help_encryption_url")} target="_blank" rel="noreferrer noopener">
|
||||
{sub} <PopOutIcon />
|
||||
</a>
|
||||
</>
|
||||
|
||||
@ -24,7 +24,6 @@ import { getDefaultIdentityServerUrl, setToDefaultIdentityServer } from "../../.
|
||||
import { buildActivityScores, buildMemberScores, compareMembers } from "../../../utils/SortMembers";
|
||||
import { abbreviateUrl } from "../../../utils/UrlUtils";
|
||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||
import { humanizeTime } from "../../../utils/humanize";
|
||||
import { type IInviteResult, inviteMultipleToRoom, showAnyInviteErrors } from "../../../RoomInvite";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||
@ -65,6 +64,8 @@ import AskInviteAnywayDialog, { type UnknownProfiles } from "./AskInviteAnywayDi
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import { type UserProfilesStore } from "../../../stores/UserProfilesStore";
|
||||
import InviteProgressBody from "./InviteProgressBody.tsx";
|
||||
import { RichList } from "../../../shared-components/rich-list/RichList";
|
||||
import { RichItem } from "../../../shared-components/rich-list/RichItem";
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
/* eslint-disable camelcase */
|
||||
@ -163,7 +164,6 @@ interface IDMRoomTileProps {
|
||||
member: Member;
|
||||
lastActiveTs?: number;
|
||||
onToggle(member: Member): void;
|
||||
highlightWord: string;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
@ -176,54 +176,8 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||
this.props.onToggle(this.props.member);
|
||||
};
|
||||
|
||||
private highlightName(str: string): ReactNode {
|
||||
if (!this.props.highlightWord) return str;
|
||||
|
||||
// We convert things to lowercase for index searching, but pull substrings from
|
||||
// the submitted text to preserve case. Note: we don't need to htmlEntities the
|
||||
// string because React will safely encode the text for us.
|
||||
const lowerStr = str.toLowerCase();
|
||||
const filterStr = this.props.highlightWord.toLowerCase();
|
||||
|
||||
const result: JSX.Element[] = [];
|
||||
|
||||
let i = 0;
|
||||
let ii: number;
|
||||
while ((ii = lowerStr.indexOf(filterStr, i)) >= 0) {
|
||||
// Push any text we missed (first bit/middle of text)
|
||||
if (ii > i) {
|
||||
// Push any text we aren't highlighting (middle of text match, or beginning of text)
|
||||
result.push(<span key={i + "begin"}>{str.substring(i, ii)}</span>);
|
||||
}
|
||||
|
||||
i = ii; // copy over ii only if we have a match (to preserve i for end-of-text matching)
|
||||
|
||||
// Highlight the word the user entered
|
||||
const substr = str.substring(i, filterStr.length + i);
|
||||
result.push(
|
||||
<span className="mx_InviteDialog_tile--room_highlight" key={i + "bold"}>
|
||||
{substr}
|
||||
</span>,
|
||||
);
|
||||
i += substr.length;
|
||||
}
|
||||
|
||||
// Push any text we missed (end of text)
|
||||
if (i < str.length) {
|
||||
result.push(<span key={i + "end"}>{str.substring(i)}</span>);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
let timestamp: JSX.Element | undefined;
|
||||
if (this.props.lastActiveTs) {
|
||||
const humanTs = humanizeTime(this.props.lastActiveTs);
|
||||
timestamp = <span className="mx_InviteDialog_tile--room_time">{humanTs}</span>;
|
||||
}
|
||||
|
||||
const avatarSize = "36px";
|
||||
const avatarSize = "32px";
|
||||
const avatar = (this.props.member as ThreepidMember).isEmail ? (
|
||||
<EmailPillAvatarIcon width={avatarSize} height={avatarSize} />
|
||||
) : (
|
||||
@ -241,40 +195,23 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||
/>
|
||||
);
|
||||
|
||||
let checkmark: JSX.Element | undefined;
|
||||
if (this.props.isSelected) {
|
||||
// To reduce flickering we put the 'selected' room tile above the real avatar
|
||||
checkmark = <div className="mx_InviteDialog_tile--room_selected" />;
|
||||
}
|
||||
|
||||
// To reduce flickering we put the checkmark on top of the actual avatar (prevents
|
||||
// the browser from reloading the image source when the avatar remounts).
|
||||
const stackedAvatar = (
|
||||
<span className="mx_InviteDialog_tile_avatarStack">
|
||||
{avatar}
|
||||
{checkmark}
|
||||
</span>
|
||||
);
|
||||
|
||||
const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(this.props.member.userId, {
|
||||
withDisplayName: true,
|
||||
});
|
||||
|
||||
const caption = (this.props.member as ThreepidMember).isEmail
|
||||
? _t("invite|email_caption")
|
||||
: this.highlightName(userIdentifier || this.props.member.userId);
|
||||
: userIdentifier || this.props.member.userId;
|
||||
|
||||
return (
|
||||
<AccessibleButton className="mx_InviteDialog_tile mx_InviteDialog_tile--room" onClick={this.onClick}>
|
||||
{stackedAvatar}
|
||||
<span className="mx_InviteDialog_tile_nameStack">
|
||||
<div className="mx_InviteDialog_tile_nameStack_name">
|
||||
{this.highlightName(this.props.member.name)}
|
||||
</div>
|
||||
<div className="mx_InviteDialog_tile_nameStack_userId">{caption}</div>
|
||||
</span>
|
||||
{timestamp}
|
||||
</AccessibleButton>
|
||||
<RichItem
|
||||
avatar={avatar}
|
||||
title={this.props.member.name}
|
||||
description={caption}
|
||||
timestamp={this.props.lastActiveTs}
|
||||
onClick={this.onClick}
|
||||
selected={this.props.isSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1048,8 +985,13 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
if (sourceMembers.length === 0 && !hasAdditionalMembers) {
|
||||
return (
|
||||
<div className="mx_InviteDialog_section">
|
||||
<h3>{sectionName}</h3>
|
||||
<p>{_t("common|no_results")}</p>
|
||||
<RichList
|
||||
title={sectionName}
|
||||
titleAttributes={{ "role": "heading", "aria-level": 3 }}
|
||||
isEmpty={true}
|
||||
>
|
||||
{_t("common|no_results")}
|
||||
</RichList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1084,14 +1026,15 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
lastActiveTs={lastActive(r)}
|
||||
key={r.user.userId}
|
||||
onToggle={this.toggleMember}
|
||||
highlightWord={this.state.filterText}
|
||||
isSelected={this.state.targets.some((t) => t.userId === r.userId)}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="mx_InviteDialog_section">
|
||||
<h3>{sectionName}</h3>
|
||||
{tiles}
|
||||
<RichList title={sectionName} titleAttributes={{ "role": "heading", "aria-level": 3 }}>
|
||||
{tiles}
|
||||
</RichList>
|
||||
{showMore}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -60,7 +60,7 @@ export default class VerificationRequestDialog extends React.Component<IProps, I
|
||||
>
|
||||
<EncryptionPanel
|
||||
layout="dialog"
|
||||
verificationRequest={this.props.verificationRequest}
|
||||
verificationRequest={this.state.verificationRequest}
|
||||
verificationRequestPromise={this.props.verificationRequestPromise}
|
||||
onClose={this.props.onFinished}
|
||||
member={member}
|
||||
|
||||
@ -320,7 +320,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
||||
const label = this.getLabel();
|
||||
|
||||
let dateHeaderContent: JSX.Element;
|
||||
if (this.state.jumpToDateEnabled) {
|
||||
if (this.state.jumpToDateEnabled && !this.props.forExport) {
|
||||
dateHeaderContent = this.renderJumpToDateMenu();
|
||||
} else {
|
||||
dateHeaderContent = (
|
||||
|
||||
@ -129,6 +129,7 @@ export default function RoomHeader({
|
||||
disabled={!!videoCallDisabledReason}
|
||||
color="primary"
|
||||
aria-label={videoCallDisabledReason ?? _t("action|join")}
|
||||
data-testId="join-call-button"
|
||||
>
|
||||
{_t("action|join")}
|
||||
</Button>
|
||||
|
||||
@ -59,6 +59,7 @@ export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX
|
||||
align="center"
|
||||
gap="var(--cpd-space-2x)"
|
||||
wrap="wrap"
|
||||
className="mx_RoomListPrimaryFilters_list"
|
||||
ref={ref}
|
||||
>
|
||||
{filters.map((filter, i) => (
|
||||
@ -103,7 +104,7 @@ function useCollapseFilters<T extends HTMLElement>(
|
||||
|
||||
// If the previous element is on the left element of the current one, it means that the filter is wrapping
|
||||
const previousSibling = child.previousElementSibling as HTMLElement | null;
|
||||
if (previousSibling && child.offsetLeft < previousSibling.offsetLeft) {
|
||||
if (previousSibling && child.offsetLeft <= previousSibling.offsetLeft) {
|
||||
if (!isWrapping) setWrappingIndex(i);
|
||||
isWrapping = true;
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): J
|
||||
element="button"
|
||||
onClick={onClick as (e: ButtonEvent) => void}
|
||||
aria-label={label}
|
||||
disabled={actionState === "disabled"}
|
||||
className={classNames("mx_FormattingButtons_Button", {
|
||||
mx_FormattingButtons_active: actionState === "reversed",
|
||||
mx_FormattingButtons_Button_hover: actionState === "enabled",
|
||||
@ -64,55 +65,59 @@ function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): J
|
||||
interface FormattingButtonsProps {
|
||||
composer: FormattingFunctions;
|
||||
actionStates: AllActionStates;
|
||||
/**
|
||||
* Whether all buttons should be disabled
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function FormattingButtons({ composer, actionStates }: FormattingButtonsProps): JSX.Element {
|
||||
export function FormattingButtons({ composer, actionStates, disabled }: FormattingButtonsProps): JSX.Element {
|
||||
const composerContext = useComposerContext();
|
||||
const isInList = actionStates.unorderedList === "reversed" || actionStates.orderedList === "reversed";
|
||||
return (
|
||||
<div className="mx_FormattingButtons">
|
||||
<Button
|
||||
actionState={actionStates.bold}
|
||||
actionState={disabled ? "disabled" : actionStates.bold}
|
||||
label={_t("composer|format_bold")}
|
||||
keyCombo={{ ctrlOrCmdKey: true, key: "b" }}
|
||||
onClick={() => composer.bold()}
|
||||
icon={<BoldIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.italic}
|
||||
actionState={disabled ? "disabled" : actionStates.italic}
|
||||
label={_t("composer|format_italic")}
|
||||
keyCombo={{ ctrlOrCmdKey: true, key: "i" }}
|
||||
onClick={() => composer.italic()}
|
||||
icon={<ItalicIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.underline}
|
||||
actionState={disabled ? "disabled" : actionStates.underline}
|
||||
label={_t("composer|format_underline")}
|
||||
keyCombo={{ ctrlOrCmdKey: true, key: "u" }}
|
||||
onClick={() => composer.underline()}
|
||||
icon={<UnderlineIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.strikeThrough}
|
||||
actionState={disabled ? "disabled" : actionStates.strikeThrough}
|
||||
label={_t("composer|format_strikethrough")}
|
||||
onClick={() => composer.strikeThrough()}
|
||||
icon={<StrikeThroughIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.unorderedList}
|
||||
actionState={disabled ? "disabled" : actionStates.unorderedList}
|
||||
label={_t("composer|format_unordered_list")}
|
||||
onClick={() => composer.unorderedList()}
|
||||
icon={<BulletedListIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.orderedList}
|
||||
actionState={disabled ? "disabled" : actionStates.orderedList}
|
||||
label={_t("composer|format_ordered_list")}
|
||||
onClick={() => composer.orderedList()}
|
||||
icon={<NumberedListIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
{isInList && (
|
||||
<Button
|
||||
actionState={actionStates.indent}
|
||||
actionState={disabled ? "disabled" : actionStates.indent}
|
||||
label={_t("composer|format_increase_indent")}
|
||||
onClick={() => composer.indent()}
|
||||
icon={<IndentIcon className="mx_FormattingButtons_Icon" />}
|
||||
@ -120,33 +125,33 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
|
||||
)}
|
||||
{isInList && (
|
||||
<Button
|
||||
actionState={actionStates.unindent}
|
||||
actionState={disabled ? "disabled" : actionStates.unindent}
|
||||
label={_t("composer|format_decrease_indent")}
|
||||
onClick={() => composer.unindent()}
|
||||
icon={<UnIndentIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
actionState={actionStates.quote}
|
||||
actionState={disabled ? "disabled" : actionStates.quote}
|
||||
label={_t("action|quote")}
|
||||
onClick={() => composer.quote()}
|
||||
icon={<QuoteIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.inlineCode}
|
||||
actionState={disabled ? "disabled" : actionStates.inlineCode}
|
||||
label={_t("composer|format_inline_code")}
|
||||
keyCombo={{ ctrlOrCmdKey: true, key: "e" }}
|
||||
onClick={() => composer.inlineCode()}
|
||||
icon={<InlineCodeIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.codeBlock}
|
||||
actionState={disabled ? "disabled" : actionStates.codeBlock}
|
||||
label={_t("composer|format_code_block")}
|
||||
onClick={() => composer.codeBlock()}
|
||||
icon={<CodeBlockIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
actionState={actionStates.link}
|
||||
actionState={disabled ? "disabled" : actionStates.link}
|
||||
label={_t("composer|format_link")}
|
||||
onClick={() => openLinkModal(composer, composerContext, actionStates.link === "reversed")}
|
||||
icon={<LinkIcon className="mx_FormattingButtons_Icon" />}
|
||||
|
||||
@ -60,6 +60,7 @@ export function PlainTextComposer({
|
||||
handleCommand,
|
||||
handleMention,
|
||||
handleAtRoomMention,
|
||||
handleEmoji,
|
||||
} = usePlainTextListeners(initialContent, onChange, onSend, eventRelation, isAutoReplaceEmojiEnabled);
|
||||
const composerFunctions = useComposerFunctions(editorRef, setContent);
|
||||
usePlainTextInitialization(initialContent, editorRef);
|
||||
@ -84,6 +85,7 @@ export function PlainTextComposer({
|
||||
handleMention={handleMention}
|
||||
handleCommand={handleCommand}
|
||||
handleAtRoomMention={handleAtRoomMention}
|
||||
handleEmoji={handleEmoji}
|
||||
/>
|
||||
<Editor
|
||||
ref={editorRef}
|
||||
|
||||
@ -40,6 +40,12 @@ interface WysiwygAutocompleteProps {
|
||||
*/
|
||||
handleAtRoomMention: FormattingFunctions["mentionAtRoom"];
|
||||
|
||||
/**
|
||||
* This handler will be called with the emoji character on clicking
|
||||
* an emoji in the autocomplete list or pressing enter on a selected item
|
||||
*/
|
||||
handleEmoji: FormattingFunctions["emoji"];
|
||||
|
||||
ref?: Ref<Autocomplete>;
|
||||
}
|
||||
|
||||
@ -55,6 +61,7 @@ const WysiwygAutocomplete = ({
|
||||
handleMention,
|
||||
handleCommand,
|
||||
handleAtRoomMention,
|
||||
handleEmoji,
|
||||
ref,
|
||||
}: WysiwygAutocompleteProps): JSX.Element | null => {
|
||||
const { room } = useScopedRoomContext("room");
|
||||
@ -89,7 +96,14 @@ const WysiwygAutocomplete = ({
|
||||
return;
|
||||
}
|
||||
// TODO - handle "community" type
|
||||
case "community": {
|
||||
return; // no-op until we decide how to handle community in the wysiwyg composer
|
||||
}
|
||||
default:
|
||||
{
|
||||
// similar to the cider editor we handle emoji and other plain text replacement in the default case
|
||||
handleEmoji(completion.completion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import { parsePermalink } from "../../../../../utils/permalinks/Permalinks";
|
||||
import { isNotNull } from "../../../../../Typeguards";
|
||||
import { useSettingValue } from "../../../../../hooks/useSettings";
|
||||
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
|
||||
import { useContainsCommand } from "../hooks/useContainsCommand.ts";
|
||||
|
||||
interface WysiwygComposerProps {
|
||||
disabled?: boolean;
|
||||
@ -83,6 +84,9 @@ export const WysiwygComposer = memo(function WysiwygComposer({
|
||||
}
|
||||
}, [onChange, messageContent, disabled]);
|
||||
|
||||
// Disable formatting buttons if the message content contains a slash command
|
||||
const disableFormatting = useContainsCommand(content, room);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClick(e: Event): void {
|
||||
e.preventDefault();
|
||||
@ -123,8 +127,9 @@ export const WysiwygComposer = memo(function WysiwygComposer({
|
||||
handleMention={wysiwyg.mention}
|
||||
handleAtRoomMention={wysiwyg.mentionAtRoom}
|
||||
handleCommand={wysiwyg.command}
|
||||
handleEmoji={wysiwyg.emoji}
|
||||
/>
|
||||
<FormattingButtons composer={wysiwyg} actionStates={actionStates} />
|
||||
<FormattingButtons composer={wysiwyg} actionStates={actionStates} disabled={disableFormatting} />
|
||||
<Editor
|
||||
ref={ref}
|
||||
disabled={!isReady}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
import CommandProvider from "../../../../../autocomplete/CommandProvider";
|
||||
|
||||
/**
|
||||
* A hook which determines if the given content contains a slash command.
|
||||
* @returns true if the content contains a slash command, false otherwise.
|
||||
* @param content The content to check for commands.
|
||||
* @param room The current room.
|
||||
*/
|
||||
export function useContainsCommand(content: string | null, room: Room | undefined): boolean {
|
||||
const [contentContainsCommands, setContentContainsCommands] = useState(false);
|
||||
const providerRef = useRef<CommandProvider | null>(null);
|
||||
const currentRoomIdRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!room || !content) {
|
||||
setContentContainsCommands(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create or reuse CommandProvider for the current room
|
||||
if (!providerRef.current || currentRoomIdRef.current !== room.roomId) {
|
||||
providerRef.current = new CommandProvider(room);
|
||||
currentRoomIdRef.current = room.roomId;
|
||||
}
|
||||
|
||||
const provider = providerRef.current;
|
||||
provider
|
||||
.getCompletions(content, { start: 0, end: 0 })
|
||||
.then((results) => {
|
||||
if (results.length > 0) {
|
||||
setContentContainsCommands(true);
|
||||
} else {
|
||||
setContentContainsCommands(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// If there's an error getting completions, assume no commands
|
||||
setContentContainsCommands(false);
|
||||
});
|
||||
}, [content, room]);
|
||||
|
||||
return contentContainsCommands;
|
||||
}
|
||||
@ -60,6 +60,7 @@ export function usePlainTextListeners(
|
||||
handleMention: (link: string, text: string, attributes: AllowedMentionAttributes) => void;
|
||||
handleAtRoomMention: (attributes: AllowedMentionAttributes) => void;
|
||||
handleCommand: (text: string) => void;
|
||||
handleEmoji: (emoji: string) => void;
|
||||
onSelect: (event: SyntheticEvent<HTMLDivElement>) => void;
|
||||
suggestion: MappedSuggestion | null;
|
||||
} {
|
||||
@ -95,8 +96,15 @@ export function usePlainTextListeners(
|
||||
// For separation of concerns, the suggestion handling is kept in a separate hook but is
|
||||
// nested here because we do need to be able to update the `content` state in this hook
|
||||
// when a user selects a suggestion from the autocomplete menu
|
||||
const { suggestion, onSelect, handleCommand, handleMention, handleAtRoomMention, handleEmojiReplacement } =
|
||||
useSuggestion(ref, setText, isAutoReplaceEmojiEnabled);
|
||||
const {
|
||||
suggestion,
|
||||
onSelect,
|
||||
handleCommand,
|
||||
handleMention,
|
||||
handleAtRoomMention,
|
||||
handleEmojiSuggestion,
|
||||
handleEmojiReplacement,
|
||||
} = useSuggestion(ref, setText, isAutoReplaceEmojiEnabled);
|
||||
|
||||
const onInput = useCallback(
|
||||
(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
|
||||
@ -178,5 +186,6 @@ export function usePlainTextListeners(
|
||||
handleCommand,
|
||||
handleMention,
|
||||
handleAtRoomMention,
|
||||
handleEmoji: handleEmojiSuggestion,
|
||||
};
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ export function useSuggestion(
|
||||
handleMention: (href: string, displayName: string, attributes: AllowedMentionAttributes) => void;
|
||||
handleAtRoomMention: (attributes: AllowedMentionAttributes) => void;
|
||||
handleCommand: (text: string) => void;
|
||||
handleEmojiSuggestion: (text: string) => void;
|
||||
handleEmojiReplacement: () => void;
|
||||
onSelect: (event: SyntheticEvent<HTMLDivElement>) => void;
|
||||
suggestion: MappedSuggestion | null;
|
||||
@ -86,11 +87,15 @@ export function useSuggestion(
|
||||
|
||||
const handleEmojiReplacement = (): void => processEmojiReplacement(suggestionData, setSuggestionData, setText);
|
||||
|
||||
const handleEmojiSuggestion = (emoji: string): void =>
|
||||
processTextReplacement(emoji, suggestionData, setSuggestionData, setText);
|
||||
|
||||
return {
|
||||
suggestion: suggestionData?.mappedSuggestion ?? null,
|
||||
handleCommand,
|
||||
handleMention,
|
||||
handleAtRoomMention,
|
||||
handleEmojiSuggestion,
|
||||
handleEmojiReplacement,
|
||||
onSelect,
|
||||
};
|
||||
@ -260,10 +265,31 @@ export function processEmojiReplacement(
|
||||
setText: (text?: string) => void,
|
||||
): void {
|
||||
// if we do not have a suggestion of the correct type, return early
|
||||
if (suggestionData === null || suggestionData.mappedSuggestion.type !== `custom`) {
|
||||
if (suggestionData?.mappedSuggestion?.type !== `custom`) {
|
||||
return;
|
||||
}
|
||||
const { node, mappedSuggestion } = suggestionData;
|
||||
|
||||
processTextReplacement(suggestionData.mappedSuggestion.text, suggestionData, setSuggestionData, setText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the relevant part of the editor text, replacing the suggestionData selection with the replacement text.
|
||||
* @param replacementText - the text that we will insert into the DOM
|
||||
* @param suggestionData - representation of the part of the DOM that will be replaced
|
||||
* @param setSuggestionData - setter function to set the suggestion state
|
||||
* @param setText - setter function to set the content of the composer
|
||||
*/
|
||||
export function processTextReplacement(
|
||||
replacementText: string,
|
||||
suggestionData: SuggestionState,
|
||||
setSuggestionData: React.Dispatch<React.SetStateAction<SuggestionState>>,
|
||||
setText: (text?: string) => void,
|
||||
): void {
|
||||
// if we do not have suggestion data return early
|
||||
if (suggestionData === null) {
|
||||
return;
|
||||
}
|
||||
const { node } = suggestionData;
|
||||
const existingContent = node.textContent;
|
||||
|
||||
if (existingContent == null) {
|
||||
@ -273,7 +299,7 @@ export function processEmojiReplacement(
|
||||
// replace the emoticon with the suggesed emoji
|
||||
const newContent =
|
||||
existingContent.slice(0, suggestionData.startOffset) +
|
||||
mappedSuggestion.text +
|
||||
replacementText +
|
||||
existingContent.slice(suggestionData.endOffset);
|
||||
|
||||
node.textContent = newContent;
|
||||
@ -405,6 +431,8 @@ export function getMappedSuggestion(text: string, isAutoReplaceEmojiEnabled?: bo
|
||||
case "#":
|
||||
case "@":
|
||||
return { keyChar: firstChar, text: restOfString, type: "mention" };
|
||||
case ":":
|
||||
return { keyChar: firstChar, text: restOfString, type: "emoji" };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Ele
|
||||
<Button
|
||||
size="sm"
|
||||
kind="secondary"
|
||||
Icon={ShareIcon}
|
||||
Icon={DownloadIcon}
|
||||
onClick={() =>
|
||||
Modal.createDialog(
|
||||
lazy(
|
||||
@ -89,7 +89,7 @@ function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Ele
|
||||
<Button
|
||||
size="sm"
|
||||
kind="secondary"
|
||||
Icon={DownloadIcon}
|
||||
Icon={ShareIcon}
|
||||
onClick={() =>
|
||||
Modal.createDialog(
|
||||
lazy(
|
||||
|
||||
@ -13,6 +13,7 @@ import { SettingsSection } from "../shared/SettingsSection";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { SettingsHeader } from "../SettingsHeader";
|
||||
import { useKeyStoragePanelViewModel } from "../../../viewmodels/settings/encryption/KeyStoragePanelViewModel";
|
||||
import SdkConfig from "../../../../SdkConfig";
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
@ -55,7 +56,7 @@ export const KeyStoragePanel: React.FC<Props> = ({ onKeyStorageDisableClick }) =
|
||||
}
|
||||
subHeading={_t("settings|encryption|key_storage|description", undefined, {
|
||||
a: (sub) => (
|
||||
<a href="https://element.io/help#encryption5" target="_blank" rel="noreferrer noopener">
|
||||
<a href={SdkConfig.get("help_encryption_url")} target="_blank" rel="noreferrer noopener">
|
||||
{sub}
|
||||
</a>
|
||||
),
|
||||
|
||||
@ -265,7 +265,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
||||
</SettingsSubsection>
|
||||
|
||||
<SettingsSubsection heading={_t("settings|preferences|room_list_heading")}>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
|
||||
{!newRoomListEnabled && this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
|
||||
{/* The settings is on device level where the other room list settings are on account level */}
|
||||
{newRoomListEnabled && (
|
||||
<SettingsFlag name="RoomList.showMessagePreview" level={SettingLevel.DEVICE} />
|
||||
|
||||
@ -21,12 +21,11 @@ interface JoinCallViewProps {
|
||||
room: Room;
|
||||
resizing: boolean;
|
||||
call: Call;
|
||||
skipLobby?: boolean;
|
||||
role?: AriaRole;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role, onClose }) => {
|
||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, role, onClose }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
useTypedEventEmitter(call, CallEvent.Close, onClose);
|
||||
|
||||
@ -35,12 +34,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
||||
call.clean();
|
||||
}, [call]);
|
||||
|
||||
useEffect(() => {
|
||||
// Always update the widget data so that we don't ignore "skipLobby" accidentally.
|
||||
call.widget.data ??= {};
|
||||
call.widget.data.skipLobby = skipLobby;
|
||||
}, [call.widget, skipLobby]);
|
||||
|
||||
const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
|
||||
// The stickyPromise has to resolve before the widget actually becomes sticky.
|
||||
// We only let the widget become sticky after disconnecting all other active calls.
|
||||
@ -69,7 +62,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
||||
interface CallViewProps {
|
||||
room: Room;
|
||||
resizing: boolean;
|
||||
skipLobby?: boolean;
|
||||
role?: AriaRole;
|
||||
/**
|
||||
* Callback for when the user closes the call.
|
||||
@ -77,19 +69,8 @@ interface CallViewProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const CallView: FC<CallViewProps> = ({ room, resizing, skipLobby, role, onClose }) => {
|
||||
export const CallView: FC<CallViewProps> = ({ room, resizing, role, onClose }) => {
|
||||
const call = useCall(room.roomId);
|
||||
|
||||
return (
|
||||
call && (
|
||||
<JoinCallView
|
||||
room={room}
|
||||
resizing={resizing}
|
||||
call={call}
|
||||
skipLobby={skipLobby}
|
||||
role={role}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)
|
||||
);
|
||||
return call && <JoinCallView room={room} resizing={resizing} call={call} role={role} onClose={onClose} />;
|
||||
};
|
||||
|
||||
@ -229,7 +229,7 @@ export const useRoomCall = (
|
||||
if (widget && promptPinWidget) {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
||||
} else {
|
||||
placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey ?? false);
|
||||
placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined);
|
||||
}
|
||||
},
|
||||
[promptPinWidget, room, widget],
|
||||
@ -240,7 +240,9 @@ export const useRoomCall = (
|
||||
if (widget && promptPinWidget) {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
||||
} else {
|
||||
placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey ?? false);
|
||||
// If we have pressed shift then always skip the lobby, otherwise `undefined` will defer
|
||||
// to the defaults of the call implementation.
|
||||
placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined);
|
||||
}
|
||||
},
|
||||
[widget, promptPinWidget, room],
|
||||
|
||||
@ -656,6 +656,7 @@
|
||||
"poll_button_no_perms_description": "Nemáte oprávnění zahajovat hlasování v této místnosti.",
|
||||
"poll_button_no_perms_title": "Vyžaduje oprávnění",
|
||||
"replying_title": "Odpovídá",
|
||||
"room_unencrypted": "Zprávy v této místnosti nejsou koncově šifrované.",
|
||||
"room_upgraded_link": "Konverzace pokračuje zde.",
|
||||
"room_upgraded_notice": "Tato místnost byla nahrazena a už není používaná.",
|
||||
"send_button_title": "Poslat zprávu",
|
||||
@ -719,6 +720,7 @@
|
||||
"personal_space_description": "Soukromý prostor pro uspořádání vašich místností",
|
||||
"private_description": "Pouze pozvat, nejlepší pro sebe nebo pro týmy",
|
||||
"private_heading": "Váš soukromý prostor",
|
||||
"private_only_heading": "Váš prostor",
|
||||
"private_personal_description": "Zajistěte, aby do %(name)s měli přístup správní lidé",
|
||||
"private_personal_heading": "S kým pracujete?",
|
||||
"private_space": "Já a moji spolupracovníci",
|
||||
@ -969,7 +971,6 @@
|
||||
"title": "Záloha klíčů byla odstraněna",
|
||||
"warning": "Pokud jste způsob obnovy neodstranili vy, mohou se pokoušet k vašemu účtu dostat útočníci. Změňte si raději ihned heslo a nastavte nový způsob obnovy v Nastavení."
|
||||
},
|
||||
"reset_all_button": "Zapomněli nebo ztratili jste všechny metody obnovy? <a>Resetovat vše</a>",
|
||||
"set_up_recovery": "Nastavení obnovení",
|
||||
"set_up_recovery_toast_description": "Vygenerujte klíč pro obnovení, který lze použít k obnovení historie šifrovaných zpráv v případě, že ztratíte přístup k zařízením.",
|
||||
"set_up_toast_title": "Nastavení zabezpečené zálohy",
|
||||
@ -992,16 +993,18 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Zařízení ověřeno",
|
||||
"skip_verification": "Prozatím přeskočit ověřování",
|
||||
"unable_to_verify": "Nelze ověřit toto zařízení",
|
||||
"verify_this_device": "Ověřit toto zařízení"
|
||||
},
|
||||
"cancelled": "Zrušili jste proces ověření.",
|
||||
"cancelled_self": "Ověřování na jiném zařízení jste zrušili.",
|
||||
"cancelled_user": "%(displayName)s zrušil(a) proces ověření.",
|
||||
"cancelling": "Rušení…",
|
||||
"cant_confirm": "Nemůžete potvrdit?",
|
||||
"complete_action": "OK",
|
||||
"complete_description": "Uživatel úspěšně ověřen.",
|
||||
"complete_title": "Ověřeno!",
|
||||
"confirm_identity_description": "Ověřte toto zařízení a nastavte zabezpečené zasílání zpráv.",
|
||||
"confirm_identity_title": "Potvrďte svou totožnost",
|
||||
"error_starting_description": "Nepodařilo se zahájit chat s druhým uživatelem.",
|
||||
"error_starting_title": "Chyba při zahájení ověření",
|
||||
"explainer": "Bezpečné zprávy s tímto uživatelem jsou koncově šifrované a nikdo další je nemůže číst.",
|
||||
@ -1027,7 +1030,6 @@
|
||||
"text": "Zadejte ID a otisk prstu jednoho ze svých vlastních zařízení a ověřte jej. POZNÁMKA to umožňuje druhému zařízení odesílat a přijímat zprávy jako vy. POKUD VÁM NĚKDO ŘEKL, ABYSTE SEM NĚCO VLOŽILI, JE PRAVDĚPODOBNÉ, ŽE JSTE PODVEDENI!",
|
||||
"wrong_fingerprint": "Nelze ověřit zařízení '%(deviceId)s' - zadaný otisk prstu '%(fingerprint)s' neodpovídá otisku prstu zařízení, '%(fprint)s'"
|
||||
},
|
||||
"no_key_or_device": "Vypadá to, že nemáte klíč pro obnovení ani žádné jiné zařízení, které byste mohli ověřit. Toto zařízení nebude mít přístup ke starým zašifrovaným zprávám. Abyste mohli na tomto zařízení ověřit svou identitu, budete muset obnovit ověřovací klíče.",
|
||||
"no_support_qr_emoji": "Zařízení, které se snažíte ověřit, neumožňuje ověření QR kódem ani pomocí emotikonů, které %(brand)s podporuje. Zkuste použít jiného klienta.",
|
||||
"other_party_cancelled": "Druhá strana ověření zrušila.",
|
||||
"prompt_encrypted": "Ověřit všechny uživatele v místnosti, abyste se přesvědčili o bezpečnosti.",
|
||||
@ -1043,7 +1045,6 @@
|
||||
"request_toast_accept_user": "Ověřit uživatele",
|
||||
"request_toast_decline_counter": "Ignorovat (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s z %(ip)s",
|
||||
"reset_proceed_prompt": "Pokračovat v resetování",
|
||||
"sas_caption_self": "Ověřte toto zařízení tak, že potvrdíte, že se na jeho obrazovce zobrazí následující číslo.",
|
||||
"sas_caption_user": "Ověřte uživatele zkontrolováním, že se na obrazovce objevila stejná čísla.",
|
||||
"sas_description": "Pokud na žádném zařízení nemáte kameru, porovnejte jedinečnou kombinaci emoji",
|
||||
@ -1066,7 +1067,8 @@
|
||||
"unverified_sessions_toast_description": "Zkontrolujte, zda je váš účet v bezpečí",
|
||||
"unverified_sessions_toast_reject": "Později",
|
||||
"unverified_sessions_toast_title": "Máte neověřené relace",
|
||||
"verification_description": "Ověřte svou identitu, abyste získali přístup k šifrovaným zprávám a prokázali svou identitu ostatním. Pokud používáte také mobilní zařízení, před pokračováním otevřete aplikaci.",
|
||||
"use_another_device": "Použít jiné zařízení",
|
||||
"use_recovery_key": "Použít klíč pro obnovení",
|
||||
"verification_dialog_title_device": "Ověřit jiné zařízení",
|
||||
"verification_dialog_title_user": "Požadavek na ověření",
|
||||
"verification_skip_warning": "Bez ověření nebudete mít přístup ke všem svým zprávám a můžete se ostatním jevit jako nedůvěryhodní.",
|
||||
@ -1076,9 +1078,6 @@
|
||||
"verify_emoji_prompt": "Ověření porovnáním několika emoji.",
|
||||
"verify_emoji_prompt_qr": "Pokud vám skenování kódů nefunguje, ověřte se porovnáním emoji.",
|
||||
"verify_later": "Ověřím se později",
|
||||
"verify_using_device": "Ověřit pomocí jiného zařízení",
|
||||
"verify_using_key": "Ověřit pomocí klíče pro obnovení",
|
||||
"verify_using_key_or_phrase": "Ověření pomocí klíče pro obnovení nebo fráze",
|
||||
"waiting_for_user_accept": "Čekáme, než %(displayName)s výzvu přijme…",
|
||||
"waiting_other_device": "Čekáme na ověření na jiném zařízení…",
|
||||
"waiting_other_device_details": "Čekáme na ověření na vašem dalším zařízení, %(deviceName)s (%(deviceId)s)…",
|
||||
@ -1128,6 +1127,7 @@
|
||||
"tls": "Nelze se připojit k domovskému serveru – zkontrolujte prosím své připojení, prověřte, zda je <a>SSL certifikát</a> vašeho domovského serveru důvěryhodný, a že některé z rozšíření prohlížeče neblokuje komunikaci.",
|
||||
"unknown": "Neznámá chyba",
|
||||
"unknown_error_code": "neznámý kód chyby",
|
||||
"update_history_visibility": "Nepodařilo se změnit viditelnost historie",
|
||||
"update_power_level": "Nepodařilo se změnit úroveň oprávnění"
|
||||
},
|
||||
"error_app_open_in_another_tab": "Přepněte na jiný panel a připojte se k %(brand)s. Tuto kartu nyní můžete zavřít.",
|
||||
@ -2001,7 +2001,9 @@
|
||||
"inaccessible_subtitle_1": "Zkuste to později nebo požádejte správce místnosti či prostoru, aby zkontroloval, zda máte přístup.",
|
||||
"inaccessible_subtitle_2": "Při pokusu o přístup do místnosti nebo prostoru bylo vráceno %(errcode)s. Pokud si myslíte, že se vám tato zpráva zobrazuje chybně, pošlete prosím <issueLink>hlášení o chybě</issueLink>.",
|
||||
"intro": {
|
||||
"display_topic": "Téma: <topic/>",
|
||||
"dm_caption": "V této konverzaci jste pouze vy dva, dokud někdo z vás nepozve někoho dalšího.",
|
||||
"edit_topic": "Téma: <topic/> (<a>upravit</a>)",
|
||||
"enable_encryption_prompt": "Povolte šifrování v nastavení.",
|
||||
"encrypted_3pid_dm_pending_join": "Jakmile se všichni připojí, budete moci konverzovat",
|
||||
"no_avatar_label": "Přidejte fotografii, aby lidé mohli snadno najít váši místnost.",
|
||||
@ -2065,7 +2067,9 @@
|
||||
"pinned_message_banner": {
|
||||
"button_close_list": "Zavřít seznam",
|
||||
"button_view_all": "Zobrazit vše",
|
||||
"description": "Tato místnost má připnuté zprávy. Kliknutím je zobrazíte.",
|
||||
"description": "Připnuté zprávy",
|
||||
"go_to_newest_message": "Zobrazit připnutou zprávu na časové ose a nejnovější připnutou zprávu zde",
|
||||
"go_to_next_message": "Zobrazit připnutou zprávu na časové ose a další nejstarší připnutou zprávu zde",
|
||||
"title": "<bold>%(index)sz%(length)s</bold> Připnuté zprávy"
|
||||
},
|
||||
"read_topic": "Klikněte pro přečtení tématu",
|
||||
@ -2176,6 +2180,26 @@
|
||||
"one": "Momentálně se odstraňují zprávy v %(count)s místnosti",
|
||||
"other": "Momentálně se odstraňují zprávy v %(count)s místnostech"
|
||||
},
|
||||
"release_announcement": {
|
||||
"done": "Hotovo",
|
||||
"filter": {
|
||||
"description": "Filtrujte své chaty jediným kliknutím. Rozbalením zobrazíte další filtry.",
|
||||
"title": "Nové rychlé filtry"
|
||||
},
|
||||
"intro": {
|
||||
"description": "Seznam chatů byl aktualizován, aby byl přehlednější a jednodušší na používání.",
|
||||
"title": "Chaty mají nový vzhled!"
|
||||
},
|
||||
"next": "Další",
|
||||
"settings": {
|
||||
"description": "Chcete-li zobrazit nebo skrýt náhledy zpráv, přejděte do Všechna nastavení > Předvolby > Seznam místností",
|
||||
"title": "Některá nastavení byla přesunuta"
|
||||
},
|
||||
"sort": {
|
||||
"description": "Změňte pořadí svých chatů z nejnovějších na A-Z",
|
||||
"title": "Seřaďte si chaty"
|
||||
}
|
||||
},
|
||||
"room": {
|
||||
"more_options": "Více možností",
|
||||
"open_room": "Otevřít místnost %(roomName)s"
|
||||
@ -2365,6 +2389,10 @@
|
||||
"users_default": "Výchozí role"
|
||||
},
|
||||
"security": {
|
||||
"cannot_change_to_private_due_to_missing_history_visiblity_permissions": {
|
||||
"description": "Nemáte oprávnění měnit viditelnost historie místnosti. To je nebezpečné, protože by to mohlo umožnit uživatelům, kteří nejsou členy, číst zprávy.",
|
||||
"title": "Nelze nastavit místnost jako soukromou"
|
||||
},
|
||||
"enable_encryption_confirm_description": "Po zapnutí již nelze šifrování v této místnosti vypnout. Zprávy v šifrovaných místnostech mohou číst jen členové místnosti, server se k obsahu nedostane. Šifrování místností nepodporuje většina botů a propojení. <a>Více informací o šifrování.</a>",
|
||||
"enable_encryption_confirm_title": "Povolit šifrování?",
|
||||
"enable_encryption_public_room_confirm_description_1": "<b>Nedoporučuje se šifrovat veřejné místnosti.</b>Veřejné místnosti může najít a připojit se k nim kdokoli, takže si v nich může číst zprávy kdokoli. Nezískáte tak žádnou z výhod šifrování a nebudete ho moci později vypnout. Šifrování zpráv ve veřejné místnosti zpomalí příjem a odesílání zpráv.",
|
||||
@ -2382,7 +2410,7 @@
|
||||
"history_visibility_joined": "Pouze členové (od chvíle jejich vstupu)",
|
||||
"history_visibility_legend": "Kdo může číst historii?",
|
||||
"history_visibility_shared": "Pouze členové (od chvíle vybrání této volby)",
|
||||
"history_visibility_warning": "Změny viditelnosti historie této místnosti ovlivní jenom nové zprávy. Viditelnost starších zpráv zůstane, jaká byla v době jejich odeslání.",
|
||||
"history_visibility_warning": "Viditelnost stávající historie se nezmění.",
|
||||
"history_visibility_world_readable": "Kdokoliv",
|
||||
"join_rule_description": "Rozhodněte, kdo se může připojit k místnosti %(roomName)s.",
|
||||
"join_rule_invite": "Soukromý (pouze pro pozvané)",
|
||||
@ -2425,6 +2453,7 @@
|
||||
"other": "Aktualizace prostorů... (%(progress)s z %(count)s)"
|
||||
},
|
||||
"join_rule_upgrade_upgrading_room": "Aktualizace místnosti",
|
||||
"join_rule_world_readable_description": "Změna toho, kdo může vstoupit do místnosti, změní také viditelnost budoucích zpráv.",
|
||||
"public_without_alias_warning": "Přidejte prosím místnosti adresu aby na ní šlo odkazovat.",
|
||||
"publish_room": "Zviditelněte tuto místnost ve veřejném adresáři místností.",
|
||||
"publish_space": "Zviditelněte tento prostor ve veřejném adresáři místností.",
|
||||
@ -2563,6 +2592,7 @@
|
||||
"breadcrumb_second_description": "Ztratíte veškerou historii zpráv, která je uložena pouze na serveru",
|
||||
"breadcrumb_third_description": "Budete muset znovu ověřit všechna svá stávající zařízení a kontakty",
|
||||
"breadcrumb_title": "Opravdu chcete obnovit svou identitu?",
|
||||
"breadcrumb_title_cant_confirm": "Musíte resetovat svou totožnost",
|
||||
"breadcrumb_title_forgot": "Zapomněli jste klíč pro obnovení? Budete muset obnovit svou identitu.",
|
||||
"breadcrumb_title_sync_failed": "Synchronizace úložiště klíčů se nezdařila. Musíte obnovit svou identitu.",
|
||||
"breadcrumb_warning": "Udělejte to pouze v případě, že se domníváte, že váš účet byl napaden.",
|
||||
@ -3964,6 +3994,7 @@
|
||||
"connection_lost": "Došlo ke ztrátě připojení k serveru",
|
||||
"connection_lost_description": "Bez připojení k serveru nelze uskutečňovat hovory.",
|
||||
"consulting": "Konzultace s %(transferTarget)s. <a>Převod na %(transferee)s</a>",
|
||||
"decline_call": "Odmítnout",
|
||||
"default_device": "Výchozí zařízení",
|
||||
"dial": "Vytočit",
|
||||
"dialpad": "Číselník",
|
||||
@ -4015,6 +4046,7 @@
|
||||
"show_sidebar_button": "Zobrazit postranní panel",
|
||||
"silence": "Ztlumit zvonění",
|
||||
"silenced": "Oznámení ztlumena",
|
||||
"skip_lobby_toggle_option": "Připojte se ihned",
|
||||
"start_screenshare": "Začít sdílet obrazovku",
|
||||
"stop_screenshare": "Ukončit sdílení obrazovky",
|
||||
"too_many_calls": "Přiliš mnoho hovorů",
|
||||
|
||||
@ -644,7 +644,7 @@
|
||||
"mode_plain": "Formatierung ausblenden",
|
||||
"mode_rich_text": "Formatierung anzeigen",
|
||||
"no_perms_notice": "Du darfst in diesem Chat nichts schreiben",
|
||||
"placeholder": "Eine unverschlüsselte Nachricht senden...",
|
||||
"placeholder": "Unverschlüsselte Nachricht senden...",
|
||||
"placeholder_encrypted": "Nachricht senden …",
|
||||
"placeholder_reply": "Eine unverschlüsselte Antwort senden…",
|
||||
"placeholder_reply_encrypted": "Antwort senden…",
|
||||
@ -718,6 +718,7 @@
|
||||
"personal_space_description": "Ein privater Space zum Organisieren deiner Chats",
|
||||
"private_description": "Nur für Eingeladene – optimal für dich selbst oder Teams",
|
||||
"private_heading": "Dein privater Space",
|
||||
"private_only_heading": "Dein Space",
|
||||
"private_personal_description": "Stelle sicher, dass die richtigen Personen Zugriff auf %(name)s haben",
|
||||
"private_personal_heading": "Für wen ist dieser Space gedacht?",
|
||||
"private_space": "Für mich und meine Kollegen",
|
||||
@ -968,7 +969,6 @@
|
||||
"title": "Wiederherstellungsmethode gelöscht",
|
||||
"warning": "Wenn du die Wiederherstellungsmethode nicht gelöscht hast, kann ein Angreifer versuchen, Zugang zu deinem Konto zu bekommen. Ändere dein Passwort und richte sofort eine neue Wiederherstellungsmethode in den Einstellungen ein."
|
||||
},
|
||||
"reset_all_button": "Hast du alle Wiederherstellungsmethoden vergessen? <a>Setze sie hier zurück</a>",
|
||||
"set_up_recovery": "Wiederherstellung einrichten",
|
||||
"set_up_recovery_toast_description": "Erzeuge einen Wiederherstellungsschlüssel. Er wird verwendet, um den verschlüsselten Nachrichtenverlauf wiederherzustellen, falls du den Zugriff auf deine Geräte verlierst.",
|
||||
"set_up_toast_title": "Schlüsselsicherung einrichten",
|
||||
@ -991,7 +991,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Gerät verifiziert",
|
||||
"skip_verification": "Verifizierung vorläufig überspringen",
|
||||
"unable_to_verify": "Gerät konnte nicht verifiziert werden",
|
||||
"verify_this_device": "Dieses Gerät verifizieren"
|
||||
},
|
||||
"cancelled": "Du hast die Verifikation abgebrochen.",
|
||||
@ -1026,7 +1025,6 @@
|
||||
"text": "Gib die Geräte-ID und den Fingerabdruck eines deiner eigenen Geräte zur Verifizierung ein. HINWEIS: Dadurch kann das andere Gerät Nachrichten in deinem Namen senden und empfangen. WENN DICH JEMAND AUFGEFORDERT HAT, HIER ETWAS EINZUFÜGEN, WIRST DU WAHRSCHEINLICH BETROGEN!",
|
||||
"wrong_fingerprint": "Das Gerät „%(deviceId)s” kann nicht verifiziert werden – der angegebene Fingerabdruck „%(fingerprint)s” stimmt nicht mit dem Fingerabdruck des Geräts „%(fprint)s” überein"
|
||||
},
|
||||
"no_key_or_device": "Es sieht so aus, als hättest du weder einen Wiederherstellungsschlüssel noch andere Geräte, mit denen du dich verifizieren kannst. Dieses Gerät kann daher nicht auf den verschlüsselten Nachrichtenverlauf zugreifen. Um deine Identität auf diesem Gerät zu bestätigen, muss deine kryptografische Identität zurückgesetzt werden.",
|
||||
"no_support_qr_emoji": "Das Gerät unterstützt weder Verifizieren mittels QR-Code noch Emoji-Verifizierung. %(brand)s benötigt dies jedoch. Bitte verwende eine andere Anwendung.",
|
||||
"other_party_cancelled": "Die Gegenstelle hat die Überprüfung abgebrochen.",
|
||||
"prompt_encrypted": "Verifiziere alle Nutzer in einem Chat um vollständige Sicherheit zu gewährleisten.",
|
||||
@ -1042,7 +1040,6 @@
|
||||
"request_toast_accept_user": "Benutzer verifizieren",
|
||||
"request_toast_decline_counter": "Ignoriere (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s von %(ip)s",
|
||||
"reset_proceed_prompt": "Mit Zurücksetzen fortfahren",
|
||||
"sas_caption_self": "Verifiziere dieses Gerät, indem du überprüfst, dass die folgende Zahl auf dem Bildschirm erscheint.",
|
||||
"sas_caption_user": "Verifiziere diesen Nutzer, indem du bestätigst, dass die folgende Nummer auf dessen Bildschirm erscheint.",
|
||||
"sas_description": "Vergleiche eine einmalige Reihe von Emojis, sofern du an keinem Gerät eine Kamera hast",
|
||||
@ -1065,7 +1062,6 @@
|
||||
"unverified_sessions_toast_description": "Überprüfe, um dein Konto sicher zu halten",
|
||||
"unverified_sessions_toast_reject": "Später",
|
||||
"unverified_sessions_toast_title": "Du hast nicht verifizierte Sitzungen",
|
||||
"verification_description": "Verifiziere deine Identität, um auf verschlüsselte Nachrichten zuzugreifen und dich gegenüber anderen Nutzern auszuweisen. Solltest du auch ein mobiles Gerät verwenden, öffne dort die App bevor du fortfährst.",
|
||||
"verification_dialog_title_device": "Anderes Gerät verifizieren",
|
||||
"verification_dialog_title_user": "Verifizierungsanfrage",
|
||||
"verification_skip_warning": "Ohne dich zu verifizieren wirst du keinen Zugriff auf alle deine Nachrichten haben und könntest für andere als nicht vertrauenswürdig erscheinen.",
|
||||
@ -1075,9 +1071,6 @@
|
||||
"verify_emoji_prompt": "Durch den Vergleich einzigartiger Emojis verifizieren.",
|
||||
"verify_emoji_prompt_qr": "Wenn du obigen Code nicht erfassen kannst, verifiziere stattdessen durch den Vergleich von Emojis.",
|
||||
"verify_later": "Später verifizieren",
|
||||
"verify_using_device": "Mit anderem Gerät verifizieren",
|
||||
"verify_using_key": "Mit Wiederherstellungsschlüssel verifizieren",
|
||||
"verify_using_key_or_phrase": "Mit Wiederherstellungsschlüssel oder Wiederherstellungsphrase verifizieren",
|
||||
"waiting_for_user_accept": "Warte auf die Annahme von %(displayName)s …",
|
||||
"waiting_other_device": "Warten darauf, dass du das auf deinem anderen Gerät bestätigst…",
|
||||
"waiting_other_device_details": "Warten, dass du auf deinem anderen Gerät %(deviceName)s (%(deviceId)s) verifizierst…",
|
||||
@ -1127,6 +1120,7 @@
|
||||
"tls": "Verbindung zum Heim-Server fehlgeschlagen – bitte überprüfe die Internetverbindung und stelle sicher, dass dem <a>SSL-Zertifikat deines Heimservers</a> vertraut wird und dass Anfragen nicht durch eine Browser-Erweiterung blockiert werden.",
|
||||
"unknown": "Unbekannter Fehler",
|
||||
"unknown_error_code": "Unbekannter Fehlercode",
|
||||
"update_history_visibility": "Die Sichtbarkeit des Nachrichtenverlaufs konnte nicht geändert werden",
|
||||
"update_power_level": "Ändern der Berechtigungsstufe fehlgeschlagen"
|
||||
},
|
||||
"error_app_open_in_another_tab": "Wechsle zu einem anderen Tab um mit %(brand)s zu verbinden. Dieser Tab kann jetzt geschlossen werden.",
|
||||
@ -2383,6 +2377,10 @@
|
||||
"users_default": "Standard-Rolle"
|
||||
},
|
||||
"security": {
|
||||
"cannot_change_to_private_due_to_missing_history_visiblity_permissions": {
|
||||
"description": "Du hast keine Berechtigung, die Sichtbarkeit des Nachrichtenverlaufs des Chats zu ändern. Das ist gefährlich, weil es nicht angemeldeten Nutzern ermöglichen könnte, Nachrichten zu lesen.",
|
||||
"title": "Chat kann nicht \"privat\" gemacht werden"
|
||||
},
|
||||
"enable_encryption_confirm_description": "Die Verschlüsselung für einen Chat kann nicht mehr deaktiviert werden sobald sie aktiv ist. Nachrichten in einem verschlüsselten Chat können nur noch von Mitgliedern, aber nicht mehr vom Server gelesen werden. Einige Bots und Brücken werden vielleicht nicht mehr funktionieren. <a>Erfahre mehr über Verschlüsselung.</a>",
|
||||
"enable_encryption_confirm_title": "Verschlüsselung aktivieren?",
|
||||
"enable_encryption_public_room_confirm_description_1": "<b>Verschlüsselung wird für öffentliche Chats nicht empfohlen.</b> Jeder kann öffentliche Chats finden und ihnen beitreten, also kann auch jeder die Nachrichten lesen. Die Verschlüsselung bringt keine Vorteile und lässt sich später auch nicht mehr deaktivieren. Außerdem verlangsamt die Verschlüsselung das Senden und Empfangen von Nachrichten in öffentlichen Chats.",
|
||||
@ -2400,7 +2398,7 @@
|
||||
"history_visibility_joined": "Mitglieder (ab Betreten)",
|
||||
"history_visibility_legend": "Wer kann den bisherigen Verlauf lesen?",
|
||||
"history_visibility_shared": "Mitglieder",
|
||||
"history_visibility_warning": "Änderungen an der Sichtbarkeit des Verlaufs gelten nur für zukünftige Nachrichten. Die Sichtbarkeit des existierenden Verlaufs bleibt unverändert.",
|
||||
"history_visibility_warning": "Die Sichtbarkeit des existierenden Nachrichtenverlaufs bleibt unverändert.",
|
||||
"history_visibility_world_readable": "Alle",
|
||||
"join_rule_description": "Entscheide, wer %(roomName)s betreten kann.",
|
||||
"join_rule_invite": "Privat (Betreten mit Einladung)",
|
||||
@ -2443,6 +2441,7 @@
|
||||
"other": "Spaces aktualisieren … (%(progress)s von %(count)s)"
|
||||
},
|
||||
"join_rule_upgrade_upgrading_room": "Chat-Version wird aktualisiert",
|
||||
"join_rule_world_readable_description": "Wenn du änderst, wer dem Chat beitreten darf, ändert sich auch, wer zukünftige Nachrichten sehen kann.",
|
||||
"public_without_alias_warning": "Um den Chat zu verlinken, füge bitte eine Adresse hinzu.",
|
||||
"publish_room": "Veröffentliche diesen Chat im Chat Verzeichnis.",
|
||||
"publish_space": "Veröffentliche diesen Space im Chat Verzeichnis.",
|
||||
|
||||
@ -811,7 +811,6 @@
|
||||
"title": "Η Μέθοδος Ανάκτησης Καταργήθηκε",
|
||||
"warning": "Εάν δεν καταργήσατε τη μέθοδο ανάκτησης, ένας εισβολέας μπορεί να προσπαθεί να αποκτήσει πρόσβαση στον λογαριασμό σας. Αλλάξτε τον κωδικό πρόσβασης του λογαριασμού σας και ορίστε μια νέα μέθοδο ανάκτησης αμέσως στις Ρυθμίσεις."
|
||||
},
|
||||
"reset_all_button": "Ξεχάσατε ή χάσατε όλες τις μεθόδους ανάκτησης; <a>Επαναφορά όλων</a>",
|
||||
"set_up_toast_title": "Ρυθμίστε το αντίγραφο ασφαλείας",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Δημιουργήστε αντίγραφα ασφαλείας των κλειδιών σας πριν αποσυνδεθείτε για να μην τα χάσετε."
|
||||
@ -830,7 +829,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Η συσκευή επαληθεύτηκε",
|
||||
"skip_verification": "Παράβλεψη επαλήθευσης προς το παρόν",
|
||||
"unable_to_verify": "Αδυναμία επαλήθευσης αυτής της συσκευής",
|
||||
"verify_this_device": "Επαληθεύστε αυτήν τη συσκευή"
|
||||
},
|
||||
"cancelled": "Ακυρώσατε την επαλήθευση.",
|
||||
@ -850,7 +848,6 @@
|
||||
"incoming_sas_dialog_waiting": "Αναμονή επιβεβαίωσης από τον συνεργάτη…",
|
||||
"incoming_sas_user_dialog_text_1": "Επαληθεύστε αυτόν τον χρήστη για να τον επισημάνετε ως αξιόπιστο. Η εμπιστοσύνη των χρηστών σάς προσφέρει επιπλέον ηρεμία όταν χρησιμοποιείτε μηνύματα με κρυπτογράφηση από άκρο σε άκρο.",
|
||||
"incoming_sas_user_dialog_text_2": "Η επαλήθευση αυτού του χρήστη θα επισημάνει τη συνεδρία του ως αξιόπιστη και θα επισημάνει επίσης τη συνεδρία σας ως αξιόπιστη σε αυτόν.",
|
||||
"no_key_or_device": "Φαίνεται ότι δεν έχετε Κλειδί Ανάκτησης ή άλλες συσκευές με τις οποίες μπορείτε να κάνετε επαλήθευση. Αυτή η συσκευή δεν θα έχει πρόσβαση σε παλιά κρυπτογραφημένα μηνύματα. Για να επαληθεύσετε την ταυτότητά σας σε αυτήν τη συσκευή, θα πρέπει να επαναφέρετε τα κλειδιά επαλήθευσης.",
|
||||
"no_support_qr_emoji": "Η συσκευή που προσπαθείτε να επαληθεύσετε δεν υποστηρίζει τη σάρωση κωδικού QR ή επαλήθευσης emoji, κάτι που υποστηρίζει το %(brand)s. Δοκιμάστε με διαφορετικό πρόγραμμα-πελάτη.",
|
||||
"other_party_cancelled": "Το άλλο μέρος ακύρωσε την επαλήθευση.",
|
||||
"prompt_encrypted": "Επαληθεύστε όλους τους χρήστες σε ένα δωμάτιο για να βεβαιωθείτε ότι είναι ασφαλές.",
|
||||
@ -865,7 +862,6 @@
|
||||
"request_toast_accept": "Επαλήθευση Συνεδρίας",
|
||||
"request_toast_decline_counter": "Παράβλεψη (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s από %(ip)s",
|
||||
"reset_proceed_prompt": "Προχωρήστε με την επαναφορά",
|
||||
"sas_caption_self": "Επαληθεύστε αυτήν τη συσκευή επιβεβαιώνοντας ότι ο ακόλουθος αριθμός εμφανίζεται στην οθόνη της.",
|
||||
"sas_caption_user": "Επαληθεύστε αυτόν τον χρήστη επιβεβαιώνοντας ότι ο ακόλουθος αριθμός εμφανίζεται στην οθόνη του.",
|
||||
"sas_description": "Συγκρίνετε ένα μοναδικό σύνολο emoji εάν δεν έχετε κάμερα σε καμία από τις δύο συσκευές",
|
||||
@ -888,7 +884,6 @@
|
||||
"unverified_sessions_toast_description": "Ελέγξτε για να βεβαιωθείτε ότι ο λογαριασμός σας είναι ασφαλής",
|
||||
"unverified_sessions_toast_reject": "Αργότερα",
|
||||
"unverified_sessions_toast_title": "Έχετε μη επαληθευμένες συνεδρίες",
|
||||
"verification_description": "Επαληθεύστε την ταυτότητά σας για να αποκτήσετε πρόσβαση σε κρυπτογραφημένα μηνύματα και να αποδείξετε την ταυτότητά σας σε άλλους. Εάν χρησιμοποιείτε επίσης κινητή συσκευή, ανοίξτε την εφαρμογή εκεί πριν προχωρήσετε.",
|
||||
"verification_dialog_title_device": "Επαλήθευση άλλης συσκευής",
|
||||
"verification_dialog_title_user": "Αίτημα επαλήθευσης",
|
||||
"verification_skip_warning": "Χωρίς επαλήθευση, δε θα έχετε πρόσβαση σε όλα τα μηνύματά σας και ενδέχεται να φαίνεστε ως αναξιόπιστος στους άλλους.",
|
||||
@ -898,9 +893,6 @@
|
||||
"verify_emoji_prompt": "Επαληθεύστε συγκρίνοντας μοναδικά emoji.",
|
||||
"verify_emoji_prompt_qr": "Εάν δεν μπορείτε να σαρώσετε τον παραπάνω κώδικα, επαληθεύστε το συγκρίνοντας μοναδικά emoji.",
|
||||
"verify_later": "Θα επαληθεύσω αργότερα",
|
||||
"verify_using_device": "Επαλήθευση με άλλη συσκευή",
|
||||
"verify_using_key": "Επαλήθευση με Κλειδί Ανάκτησης",
|
||||
"verify_using_key_or_phrase": "Επαλήθευση με Κλειδί ή Φράση Ανάκτησης",
|
||||
"waiting_for_user_accept": "Αναμονή αποδοχής από %(displayName)s…",
|
||||
"waiting_other_device": "Αναμονή για επαλήθευση στην άλλη συσκευή σας…",
|
||||
"waiting_other_device_details": "Αναμονή για επαλήθευση στην άλλη συσκευή σας, %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -633,7 +633,6 @@
|
||||
"title": "Rehava metodo foriĝis",
|
||||
"warning": "Se vi ne forigis la rehavan metodon, eble atakanto provas aliri vian konton. Vi tuj ŝanĝu la pasvorton de via konto, kaj agordu novan rehavan metodon en la agordoj."
|
||||
},
|
||||
"reset_all_button": "Ĉu vi forgesis aŭ perdis ĉiujn manierojn de rehavo? <a>Restarigu ĉion</a>",
|
||||
"set_up_toast_title": "Agordi Sekuran savkopiadon",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Savkopiu viajn ŝlosilojn antaŭ adiaŭo, por ilin ne perdi."
|
||||
@ -668,7 +667,6 @@
|
||||
"qr_prompt": "Skanu ĉi tiun unikan kodon",
|
||||
"qr_reciprocate_same_shield_user": "Preskaŭ finite! Ĉu %(displayName)s montras la saman ŝildon?",
|
||||
"request_toast_detail": "%(deviceId)s de %(ip)s",
|
||||
"reset_proceed_prompt": "Procedu por restarigi",
|
||||
"sas_caption_user": "Kontrolu ĉu tiun uzanton per konfirmo, ke la jena numero aperis sur ĝia ekrano.",
|
||||
"sas_description": "Komparu unikan aron de bildsignoj se vi ne havas kameraon sur la alia aparato",
|
||||
"sas_emoji_caption_user": "Kontrolu ĉi tiun uzanton per konfirmo, ke la jenaj bildsignoj aperis sur ĝia ekrano.",
|
||||
@ -688,7 +686,6 @@
|
||||
"unverified_sessions_toast_description": "Kontrolu por certigi sekurecon de via konto",
|
||||
"unverified_sessions_toast_reject": "Pli poste",
|
||||
"unverified_sessions_toast_title": "Vi havas nekontrolitajn salutaĵojn",
|
||||
"verification_description": "Kontrolu vian identecon por aliri ĉifritajn mesaĝojn kaj pruvi vian identecon al aliuloj.",
|
||||
"verification_dialog_title_user": "Kontrolpeto",
|
||||
"verification_skip_warning": "Sen kontrolado, vi ne havos aliron al ĉiuj viaj mesaĝoj kaj povas aperi kiel nefidinda al aliaj.",
|
||||
"verification_success_with_backup": "Via nova aparato nun estas kontrolita. Ĝi havas aliron al viaj ĉifritaj mesaĝoj, kaj aliaj vidos ĝin kiel fidinda.",
|
||||
@ -697,9 +694,6 @@
|
||||
"verify_emoji_prompt": "Kontrolu per komparo de unikaj bildsignoj.",
|
||||
"verify_emoji_prompt_qr": "Se vi ne povas skani la supran kodon, kontrolu per komparo de unikaj bildsignoj.",
|
||||
"verify_later": "Kontrolu poste",
|
||||
"verify_using_device": "Kontrolu per alia aparato",
|
||||
"verify_using_key": "Kontrolu per Sekureca ŝlosilo",
|
||||
"verify_using_key_or_phrase": "Kontrolu per Sekureca ŝlosilo aŭ frazo",
|
||||
"waiting_for_user_accept": "Atendante akcepton de %(displayName)s…",
|
||||
"waiting_other_user": "Atendas kontrolon de %(displayName)s…"
|
||||
},
|
||||
|
||||
@ -780,7 +780,6 @@
|
||||
"title": "Método de recuperación eliminado",
|
||||
"warning": "Si no eliminó el método de recuperación, es posible que un atacante esté intentando acceder a su cuenta. Cambie la contraseña de su cuenta y configure un nuevo método de recuperación inmediatamente en Configuración."
|
||||
},
|
||||
"reset_all_button": "¿Has olvidado o perdido todos los métodos de recuperación? <a>Restablecer todo</a>",
|
||||
"set_up_toast_title": "Configurar copia de seguridad segura",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Haz copia de seguridad de tus claves antes de cerrar sesión para evitar perderlas."
|
||||
@ -799,7 +798,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Dispositivo verificado",
|
||||
"skip_verification": "Saltar la verificación por ahora",
|
||||
"unable_to_verify": "No se ha podido verificar el dispositivo",
|
||||
"verify_this_device": "Verificar este dispositivo"
|
||||
},
|
||||
"cancelled": "Has cancelado la verificación.",
|
||||
@ -819,7 +817,6 @@
|
||||
"incoming_sas_dialog_waiting": "Esperando a que la otra persona confirme…",
|
||||
"incoming_sas_user_dialog_text_1": "Verifica a este usuario para marcarlo como de confianza. Confiar en usuarios aporta tranquilidad en los mensajes cifrados de extremo a extremo.",
|
||||
"incoming_sas_user_dialog_text_2": "Verificar este usuario marcará su sesión como de confianza, y también marcará tu sesión como de confianza para él.",
|
||||
"no_key_or_device": "Parece que no tienes una clave de seguridad u otros dispositivos para la verificación. Este dispositivo no podrá acceder los mensajes cifrados antiguos. Para verificar tu identidad en este dispositivo, tendrás que restablecer tus claves de verificación.",
|
||||
"no_support_qr_emoji": "El dispositivo que estás intentando verificar no es compatible con el escaneo de códigos QR o la verificación con emojis, que son las opciones que %(brand)s ofrece. Prueba con otra aplicación distinta.",
|
||||
"other_party_cancelled": "El otro lado canceló la verificación.",
|
||||
"prompt_encrypted": "Verifica a todos los usuarios de una sala para asegurar que es segura.",
|
||||
@ -834,7 +831,6 @@
|
||||
"request_toast_accept": "Verificar sesión",
|
||||
"request_toast_decline_counter": "Ignorar (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s desde %(ip)s",
|
||||
"reset_proceed_prompt": "Continuar y restablecer",
|
||||
"sas_caption_self": "Verifica este dispositivo confirmando que el siguiente número aparece en pantalla.",
|
||||
"sas_caption_user": "Verifica a este usuario confirmando que este número aparece en su pantalla.",
|
||||
"sas_description": "Compara un conjunto de emojis si no tienes cámara en ninguno de los dispositivos",
|
||||
@ -857,7 +853,6 @@
|
||||
"unverified_sessions_toast_description": "Revisa que tu cuenta esté segura",
|
||||
"unverified_sessions_toast_reject": "Más tarde",
|
||||
"unverified_sessions_toast_title": "Tienes sesiones sin verificar",
|
||||
"verification_description": "Verifica tu identidad para leer tus mensajes cifrados y probar a las demás personas que realmente eres tú.",
|
||||
"verification_dialog_title_device": "Verificar otro dispositivo",
|
||||
"verification_dialog_title_user": "Solicitud de verificación",
|
||||
"verification_skip_warning": "Si decides no verificar, no tendrás acceso a todos tus mensajes y puede que le aparezcas a los demás como «no confiado».",
|
||||
@ -867,9 +862,6 @@
|
||||
"verify_emoji_prompt": "Verifica comparando emoji únicos.",
|
||||
"verify_emoji_prompt_qr": "Si no puedes escanear el código de arriba, verifica comparando emoji únicos.",
|
||||
"verify_later": "La verificaré en otro momento",
|
||||
"verify_using_device": "Verificar con otro dispositivo",
|
||||
"verify_using_key": "Verificar con una clave de seguridad",
|
||||
"verify_using_key_or_phrase": "Verificar con una clave o frase de seguridad",
|
||||
"waiting_for_user_accept": "Esperando a que %(displayName)s acepte…",
|
||||
"waiting_other_device": "Esperando a que verifiques en tu otro dispositivo…",
|
||||
"waiting_other_device_details": "Esperando a que verifiques en tu otro dispositivo, %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -718,6 +718,7 @@
|
||||
"personal_space_description": "Privaatne kogukonnakeskus jututubade koondamiseks",
|
||||
"private_description": "Liitumine vaid kutse alusel, sobib sulle ja sinu lähematele kaaslastele",
|
||||
"private_heading": "Sinu privaatne kogukonnakeskus",
|
||||
"private_only_heading": "Sinu kogukond",
|
||||
"private_personal_description": "Palun kontrolli, et vajalikel inimestel oleks ligipääs siia - %(name)s",
|
||||
"private_personal_heading": "Kellega sa koos töötad?",
|
||||
"private_space": "Mina ja minu kaasteelised",
|
||||
@ -968,7 +969,6 @@
|
||||
"title": "Taastemeetod on eemaldatud",
|
||||
"warning": "Kui sa ei ole ise taastamise meetodeid eemaldanud, siis võib olla tegemist ründega sinu konto vastu. Palun vaheta koheselt oma kasutajakonto salasõna ning määra seadistustes uus taastemeetod."
|
||||
},
|
||||
"reset_all_button": "Unustasid või oled kaotanud kõik võimalused ligipääsu taastamiseks? <a>Lähtesta kõik ühe korraga</a>",
|
||||
"set_up_recovery": "Seadista krüptovõtmete taastamine",
|
||||
"set_up_recovery_toast_description": "Kui peaksid kaotama ligipääsu oma seadmetele, siis siinloodava taastevõtmega saad taastada ligipääsu oma krüptitud sõnumitele.",
|
||||
"set_up_toast_title": "Võta kasutusele turvaline varundus",
|
||||
@ -991,7 +991,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Seade on verifitseeritud",
|
||||
"skip_verification": "Jäta verifitseerimine praegu vahele",
|
||||
"unable_to_verify": "Selle seadme verifitseerimine ei õnnestunud",
|
||||
"verify_this_device": "Verifitseeri see seade"
|
||||
},
|
||||
"cancelled": "Sina tühistasid verifitseerimise.",
|
||||
@ -1026,7 +1025,6 @@
|
||||
"text": "Verifitseerimiseks sisesta ühe oma seadme tunnus ja sõrmejälg. Palun arvesta, et see võimaldab muul seadmel saata ja vastu võtta sõnumeid esinedes sinuna. KUI KEEGI PALUS SUL SIIA MIDAGI KOPEERIDA, SIIS ON SEE KAHTLANE JA ILMSELT PROOVITAKSE SIND PETTA!",
|
||||
"wrong_fingerprint": "„%(deviceId)s“ seadme verifitseerimine ei õnnestunud - lisatud sõrmejälg „%(fingerprint)s“ ja seadme sõrmejälg „%(fprint)s“ pole samad"
|
||||
},
|
||||
"no_key_or_device": "Tundub, et sul ei ole ei taastevõtit ega muid seadmeid, mida saaksid verifitseerimiseks kasutada. Siin seadmes ei saa lugeda vanu krüptitud sõnumeid. Enda tuvastamiseks selles seadmes pead oma vanad verifitseerimisvõtmed kustutama.",
|
||||
"no_support_qr_emoji": "See seade, mida sa tahad verifitseerida ei toeta QR-koodi ega emoji-põhist verifitseerimist, aga just neid %(brand)s oskab kasutada. Proovi mõne muu Matrix'i kliendiga.",
|
||||
"other_party_cancelled": "Teine osapool tühistas verifitseerimise.",
|
||||
"prompt_encrypted": "Tagamaks, et jututuba on turvaline, verifitseeri kõik selle kasutajad.",
|
||||
@ -1042,7 +1040,6 @@
|
||||
"request_toast_accept_user": "Verifitseeri kasutaja",
|
||||
"request_toast_decline_counter": "Eira (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s ip-aadressil %(ip)s",
|
||||
"reset_proceed_prompt": "Jätka kustutamisega",
|
||||
"sas_caption_self": "Verifitseeri see seade tehes kindlaks, et järgnev number kuvatakse tema ekraanil.",
|
||||
"sas_caption_user": "Verifitseeri see kasutaja tehes kindlaks, et järgnev number kuvatakse tema ekraanil.",
|
||||
"sas_description": "Kui sul mõlemas seadmes pole kaamerat, siis võrdle unikaalset emoji'de komplekti",
|
||||
@ -1065,7 +1062,6 @@
|
||||
"unverified_sessions_toast_description": "Tagamaks, et su konto on sinu kontrolli all, vaata andmed üle",
|
||||
"unverified_sessions_toast_reject": "Hiljem",
|
||||
"unverified_sessions_toast_title": "Sul on verifitseerimata sessioone",
|
||||
"verification_description": "Tagamaks ligipääsu oma krüptitud sõnumitele ja tõestamaks oma isikut teistele kasutajatale, verifitseeri end. Kui kasutad mobiilirakendust, siis palun ava see enne jätkamist.",
|
||||
"verification_dialog_title_device": "Verifitseeri oma teine seade",
|
||||
"verification_dialog_title_user": "Verifitseerimispäring",
|
||||
"verification_skip_warning": "Ilma verifitseerimiseta sul puudub ligipääs kõikidele oma sõnumitele ning teised ei näe sinu kasutajakontot usaldusväärsena.",
|
||||
@ -1075,9 +1071,6 @@
|
||||
"verify_emoji_prompt": "Verifitseeri unikaalsete emoji'de võrdlemise teel.",
|
||||
"verify_emoji_prompt_qr": "Kui sa ei saa skaneerida eespool kuvatud koodi, siis verifitseeri unikaalsete emoji'de võrdlemise teel.",
|
||||
"verify_later": "Ma verifitseerin hiljem",
|
||||
"verify_using_device": "Verifitseeri teise seadmega",
|
||||
"verify_using_key": "Verifitseeri taastevõtmega",
|
||||
"verify_using_key_or_phrase": "Verifitseeri taastevõtme või -fraasiga",
|
||||
"waiting_for_user_accept": "Ootan, et %(displayName)s nõustuks…",
|
||||
"waiting_other_device": "Ootan, et sa verifitseeriksid oma teises seadmes…",
|
||||
"waiting_other_device_details": "Ootan, et sa verifitseerid oma teises seadmes: %(deviceName)s (%(deviceId)s)…",
|
||||
@ -1127,6 +1120,7 @@
|
||||
"tls": "Ei sa ühendust koduserveriga. Palun kontrolli, et sinu <a>koduserveri SSL sertifikaat</a> oleks usaldusväärne ning mõni brauseri lisamoodul ei blokeeri päringuid.",
|
||||
"unknown": "Teadmata viga",
|
||||
"unknown_error_code": "tundmatu veakood",
|
||||
"update_history_visibility": "Ei õnnestunud muuta ajaloo nähtavust",
|
||||
"update_power_level": "Õiguste muutmine ei õnnestunud"
|
||||
},
|
||||
"error_app_open_in_another_tab": "%(brand)s'i kasutamiseks ava teine vahekaart. Selle vahekaardi võid kinni panna.",
|
||||
@ -2384,6 +2378,10 @@
|
||||
"users_default": "Vaikimisi roll"
|
||||
},
|
||||
"security": {
|
||||
"cannot_change_to_private_due_to_missing_history_visiblity_permissions": {
|
||||
"description": "Sul pole õigusi selle jututoa ajaloo nähtavuse muutmiseks. Kuna võib tekkida olukord, kus ka mitteliitunud kasutajad saavad lugeda sõnumeid, siis on selline tegevus ka ohtlik.",
|
||||
"title": "Ei õnnestu muuta jututuba privaatseks"
|
||||
},
|
||||
"enable_encryption_confirm_description": "Kui kord juba kasutusele võetud, siis krüptimist enam hiljem ära lõpetada ei saa. Krüptitud sõnumeid ei saa lugeda ei vaheapealses veebiliikluses ega serveris ja vaid jututoa liikmed saavad neid lugeda. Krüptimise kasutusele võtmine võib takistada nii robotite kui sõnumisildade tööd. <a>Lisateave krüptimise kohta.</a>",
|
||||
"enable_encryption_confirm_title": "Kas võtame krüptimise kasutusele?",
|
||||
"enable_encryption_public_room_confirm_description_1": "<b>Me ei soovita avalikes jututubades krüptimise kasutamist.</b> Kuna kõik huvilised saavad vabalt leida avalikke jututube ning nendega liituda, siis saavad nad niikuinii ka neis leiduvaid sõnumeid lugeda. Olemuselt puuduvad sellises olukorras krüptimise eelised ning sa ei saa hiljem krüptimist välja lülitada. Avalike jututubade sõnumite krüptimine teeb ka sõnumite saatmise ja vastuvõtmise aeglasemaks.",
|
||||
@ -2401,7 +2399,7 @@
|
||||
"history_visibility_joined": "Ainult liikmetele (alates liitumisest)",
|
||||
"history_visibility_legend": "Kes võivad lugeda ajalugu?",
|
||||
"history_visibility_shared": "Ainult liikmetele (alates selle seadistuse kasutuselevõtmisest)",
|
||||
"history_visibility_warning": "Kui muudad seda, kes saavad selle jututoa ajalugu lugeda, siis kehtib see vaid tulevaste sõnumite kohta. Senise ajaloo nähtavus sellega ei muutu.",
|
||||
"history_visibility_warning": "Senise ajaloo nähtavus ei muutu.",
|
||||
"history_visibility_world_readable": "Kõik kasutajad",
|
||||
"join_rule_description": "Vali, kes saavad liituda %(roomName)s jututoaga.",
|
||||
"join_rule_invite": "Privaatne jututuba (eeldab kutset)",
|
||||
@ -2444,6 +2442,7 @@
|
||||
"other": "Uuendan kogukonnakeskuseid... (%(progress)s / %(count)s)"
|
||||
},
|
||||
"join_rule_upgrade_upgrading_room": "Uuendan jututoa versiooni",
|
||||
"join_rule_world_readable_description": "Kui muudad seda, kes võib jututoaga liituda, siis muutub ka tulevaste sõnumite nähtavus.",
|
||||
"public_without_alias_warning": "Sellele jututoale viitamiseks palun lisa talle aadress.",
|
||||
"publish_room": "Tee see jututuba nähtavaks avalikus jututubade kataloogis.",
|
||||
"publish_space": "Tee see kogukond nähtavaks avalikus jututubade kataloogis.",
|
||||
|
||||
@ -593,7 +593,6 @@
|
||||
"title": "روش بازیابی حذف شد",
|
||||
"warning": "اگر متد بازیابی را حذف نکردهاید، ممکن است حملهکنندهای سعی در دسترسی به حسابکاربری شما داشته باشد. گذرواژه حساب کاربری خود را تغییر داده و فورا یک روش بازیابی را از بخش تنظیمات خود تنظیم کنید."
|
||||
},
|
||||
"reset_all_button": "همه روشهای بازیابی را فراموش کرده یا از دست دادهاید؟ <a>بازراهاندازی (reset) همه</a>",
|
||||
"set_up_toast_title": "پشتیبانگیری امن را انجام دهید",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "پیش از خروج از حساب کاربری، از کلیدهای خود پشتیبان بگیرید تا آنها را از دست ندهید."
|
||||
@ -645,7 +644,6 @@
|
||||
"unverified_session_toast_title": "ورود جدید. آیا شما بودید؟",
|
||||
"unverified_sessions_toast_description": "برای کسب اطمینان از امنبودن حساب کاربری خود، لطفا بررسی فرمائید",
|
||||
"unverified_sessions_toast_reject": "بعداً",
|
||||
"verification_description": "با تائید هویت خود به پیامهای رمزشده دسترسی یافته و هویت خود را به دیگران ثابت میکنید.",
|
||||
"verification_dialog_title_user": "درخواست تأیید",
|
||||
"verify_emoji": "تأیید توسط شکلک",
|
||||
"verify_emoji_prompt": "با مقایسه شکلک تأیید کنید.",
|
||||
|
||||
@ -826,7 +826,6 @@
|
||||
"title": "Palautustapa poistettu",
|
||||
"warning": "Jos et poistanut palautustapaa, hyökkääjä saattaa yrittää käyttää tiliäsi. Vaihda tilisi salasana ja aseta uusi palautustapa asetuksissa välittömästi."
|
||||
},
|
||||
"reset_all_button": "Unohtanut tai kadottanut kaikki palautustavat? <a>Nollaa kaikki</a>",
|
||||
"set_up_recovery": "Määritä palautus",
|
||||
"set_up_recovery_toast_description": "Luo palautusavain, jota voit käyttää salatun viestihistorian palauttamiseen, jos menetät pääsyn laitteisiisi.",
|
||||
"set_up_toast_title": "Määritä turvallinen varmuuskopio",
|
||||
@ -846,7 +845,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Laite vahvistettu",
|
||||
"skip_verification": "Ohita vahvistus toistaiseksi",
|
||||
"unable_to_verify": "Tätä laitetta ei voitu vahvistaa",
|
||||
"verify_this_device": "Vahvista tämä laite"
|
||||
},
|
||||
"cancelled": "Peruutit varmennuksen.",
|
||||
@ -905,9 +903,6 @@
|
||||
"verify_emoji_prompt": "Varmenna vertaamalla uniikkia emojia.",
|
||||
"verify_emoji_prompt_qr": "Jos et pysty skannaamaan yläpuolella olevaa koodia, varmenna vertaamalla emojia.",
|
||||
"verify_later": "Vahvistan myöhemmin",
|
||||
"verify_using_device": "Vahvista toisella laitteella",
|
||||
"verify_using_key": "Vahvista palautusavaimella",
|
||||
"verify_using_key_or_phrase": "Vahvista turva-avaimella tai turvalauseella",
|
||||
"waiting_for_user_accept": "Odotetaan, että %(displayName)s hyväksyy…",
|
||||
"waiting_other_device": "Odotetaan vahvistustasi toiselta laitteelta…",
|
||||
"waiting_other_device_details": "Odotetaan vahvistustasi toiselta laitteelta, %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -718,6 +718,7 @@
|
||||
"personal_space_description": "Un espace privé pour organiser vos salons",
|
||||
"private_description": "Sur invitation, idéal pour vous-même ou les équipes",
|
||||
"private_heading": "Votre espace privé",
|
||||
"private_only_heading": "Votre Espace",
|
||||
"private_personal_description": "Assurez-vous que les bonnes personnes aient accès à %(name)s",
|
||||
"private_personal_heading": "Avec qui travaillez-vous ?",
|
||||
"private_space": "Moi et mon équipe",
|
||||
@ -968,7 +969,6 @@
|
||||
"title": "Méthode de récupération supprimée",
|
||||
"warning": "Si vous n’avez pas supprimé la méthode de récupération, un attaquant peut être en train d’essayer d’accéder à votre compte. Modifiez le mot de passe de votre compte et configurez une nouvelle méthode de récupération dans les réglages."
|
||||
},
|
||||
"reset_all_button": "Vous avez perdu ou oublié tous vos moyens de récupération ? <a>Tout réinitialiser</a>",
|
||||
"set_up_recovery": "Configurer la récupération",
|
||||
"set_up_recovery_toast_description": "Générez une clé de récupération qui peut être utilisée pour restaurer l'historique de vos messages chiffrés au cas où vous perdriez l'accès à vos appareils.",
|
||||
"set_up_toast_title": "Configurer la sauvegarde sécurisée",
|
||||
@ -991,16 +991,18 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Appareil vérifié",
|
||||
"skip_verification": "Ignorer la vérification pour l’instant",
|
||||
"unable_to_verify": "Impossible de vérifier cet appareil",
|
||||
"verify_this_device": "Vérifier cet appareil"
|
||||
},
|
||||
"cancelled": "Vous avez annulé la vérification.",
|
||||
"cancelled_self": "Vous avez annulé la vérification dans votre autre appareil.",
|
||||
"cancelled_user": "%(displayName)s a annulé la vérification.",
|
||||
"cancelling": "Annulation…",
|
||||
"cant_confirm": "Confirmation impossible ?",
|
||||
"complete_action": "Compris",
|
||||
"complete_description": "Vous avez vérifié cet utilisateur avec succès.",
|
||||
"complete_title": "Vérifié !",
|
||||
"confirm_identity_description": "Vérifier cet appareil pour configurer votre messagerie sécurisée",
|
||||
"confirm_identity_title": "Confirmez votre identité",
|
||||
"error_starting_description": "Nous n’avons pas pu démarrer une conversation avec l’autre utilisateur.",
|
||||
"error_starting_title": "Erreur en démarrant la vérification",
|
||||
"explainer": "Les messages sécurisés avec cet utilisateur sont chiffrés de bout en bout et ne peuvent être lus par d’autres personnes.",
|
||||
@ -1026,7 +1028,6 @@
|
||||
"text": "Fournissez l'identifiant et l'empreinte numétrique de l'un de vos appareils pour le vérifier. REMARQUE : cela permet à l'autre appareil d'envoyer et de recevoir des messages comme vous. SI QUELQU'UN VOUS A DIT DE COLLER QUELQUE CHOSE ICI, IL EST PROBABLE QUE VOUS SOYEZ VICTIME D'UNE ARNAQUE !",
|
||||
"wrong_fingerprint": "Impossible de vérifier l'appareil %(deviceId)s - l'empreinte numérique %(fingerprint)s fournie ne correspond pas à celle de l'appareil %(fprint)s"
|
||||
},
|
||||
"no_key_or_device": "Il semblerait que vous ne disposiez pas de clé de récupération ou d’autres appareils pour réalisation la vérification. Cet appareil ne pourra pas accéder aux anciens messages chiffrés. Afin de vérifier votre identité sur cet appareil, vous devrez réinitialiser vos clés de vérifications.",
|
||||
"no_support_qr_emoji": "L’appareil que vous essayez de vérifier ne prend pas en charge les QR codes ou la vérification d’émojis, qui sont les méthodes prises en charge par %(brand)s. Essayez avec un autre client.",
|
||||
"other_party_cancelled": "L’autre personne a annulé la vérification.",
|
||||
"prompt_encrypted": "Vérifiez tous les utilisateurs d’un salon pour vous assurer qu’il est sécurisé.",
|
||||
@ -1042,7 +1043,6 @@
|
||||
"request_toast_accept_user": "Vérifier l'utilisateur",
|
||||
"request_toast_decline_counter": "Ignorer (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s depuis %(ip)s",
|
||||
"reset_proceed_prompt": "Faire la réinitialisation",
|
||||
"sas_caption_self": "Vérifiez cet appareil en confirmant que le nombre suivant s’affiche sur son écran.",
|
||||
"sas_caption_user": "Vérifier cet utilisateur en confirmant que le nombre suivant apparaît sur leur écran.",
|
||||
"sas_description": "Comparez une liste unique d’émojis si vous n’avez d’appareil photo sur aucun des deux appareils",
|
||||
@ -1065,7 +1065,8 @@
|
||||
"unverified_sessions_toast_description": "Vérifiez pour assurer la sécurité de votre compte",
|
||||
"unverified_sessions_toast_reject": "Plus tard",
|
||||
"unverified_sessions_toast_title": "Vous avez des sessions non vérifiées",
|
||||
"verification_description": "Vérifiez votre identité pour accéder aux messages chiffrés et prouver votre identité aux autres. Si vous utilisez également un appareil mobile, veuillez ouvrir l’application avant de continuer.",
|
||||
"use_another_device": "Utiliser un autre appareil",
|
||||
"use_recovery_key": "Utiliser la clé de récupération",
|
||||
"verification_dialog_title_device": "Vérifier un autre appareil",
|
||||
"verification_dialog_title_user": "Demande de vérification",
|
||||
"verification_skip_warning": "Sans vérification, vous n’aurez pas accès à tous vos messages et vous n’apparaîtrez pas comme de confiance aux autres.",
|
||||
@ -1075,9 +1076,6 @@
|
||||
"verify_emoji_prompt": "Vérifier en comparant des émojis uniques.",
|
||||
"verify_emoji_prompt_qr": "Si vous ne pouvez pas scanner le code ci-dessus, vérifiez en comparant des émojis uniques.",
|
||||
"verify_later": "Je ferai la vérification plus tard",
|
||||
"verify_using_device": "Vérifier avec un autre appareil",
|
||||
"verify_using_key": "Vérifier avec la clé de récupération",
|
||||
"verify_using_key_or_phrase": "Vérifier avec une clé de récupération ou une phrase",
|
||||
"waiting_for_user_accept": "En attente d’acceptation par %(displayName)s…",
|
||||
"waiting_other_device": "En attente de votre vérification sur votre autre appareil…",
|
||||
"waiting_other_device_details": "En attente de votre vérification sur votre autre appareil, %(deviceName)s (%(deviceId)s)…",
|
||||
@ -1127,6 +1125,7 @@
|
||||
"tls": "Impossible de se connecter au serveur d’accueil - veuillez vérifier votre connexion, assurez-vous que le <a>certificat SSL de votre serveur d’accueil</a> est un certificat de confiance, et qu’aucune extension du navigateur ne bloque les requêtes.",
|
||||
"unknown": "Erreur inconnue",
|
||||
"unknown_error_code": "code d’erreur inconnu",
|
||||
"update_history_visibility": "Echec lors de la modification de la visibilité de l'historique",
|
||||
"update_power_level": "Échec du changement de rang"
|
||||
},
|
||||
"error_app_open_in_another_tab": "Vous pouvez fermer cet onglet déconnecté, et aller à l'autre onglet %(brand)s.",
|
||||
@ -2383,6 +2382,10 @@
|
||||
"users_default": "Rôle par défaut"
|
||||
},
|
||||
"security": {
|
||||
"cannot_change_to_private_due_to_missing_history_visiblity_permissions": {
|
||||
"description": "Vous n\"avez pas les autorisations nécessaires pour modifier l\"historique du salon. Ceci est dangereux, car cela pourrait permettre aux utilisateurs non présents de lire les messages.",
|
||||
"title": "Impossible de rendre le salon privé"
|
||||
},
|
||||
"enable_encryption_confirm_description": "Le chiffrement du salon ne peut pas être désactivé après son activation. Les messages d’un salon chiffré ne peuvent pas être vus par le serveur, seulement par les membres du salon. Activer le chiffrement peut empêcher certains robots et certaines passerelles de fonctionner correctement. <a>En savoir plus sur le chiffrement.</a>",
|
||||
"enable_encryption_confirm_title": "Activer le chiffrement ?",
|
||||
"enable_encryption_public_room_confirm_description_1": "<b>Il n'est pas recommandé d’ajouter le chiffrement aux salons publics.</b> Tout le monde peut trouver et rejoindre les salons publics, donc tout le monde peut lire les messages qui s’y trouvent. Vous n’aurez aucun des avantages du chiffrement, et vous ne pourrez pas le désactiver plus tard. Chiffrer les messages dans un salon public ralentira la réception et l’envoi de messages.",
|
||||
@ -2400,7 +2403,7 @@
|
||||
"history_visibility_joined": "Seulement les membres (depuis leur arrivée)",
|
||||
"history_visibility_legend": "Qui peut lire l’historique ?",
|
||||
"history_visibility_shared": "Seulement les membres (depuis la sélection de cette option)",
|
||||
"history_visibility_warning": "Les modifications concernant l'accès à l’historique ne s'appliqueront qu’aux futurs messages de ce salon. La visibilité de l’historique existant ne sera pas modifiée.",
|
||||
"history_visibility_warning": "La visibilité de l’historique existant ne sera pas modifiée.",
|
||||
"history_visibility_world_readable": "N’importe qui",
|
||||
"join_rule_description": "Choisir qui peut rejoindre %(roomName)s.",
|
||||
"join_rule_invite": "Privé (sur invitation)",
|
||||
@ -2443,6 +2446,7 @@
|
||||
"other": "Mise-à-jour des espaces… (%(progress)s sur %(count)s)"
|
||||
},
|
||||
"join_rule_upgrade_upgrading_room": "Mise-à-jour du salon",
|
||||
"join_rule_world_readable_description": "Changer qui peut rejoindre la salon modifiera également la visibilité des futurs messages.",
|
||||
"public_without_alias_warning": "Pour créer un lien vers ce salon, ajoutez une adresse.",
|
||||
"publish_room": "Rendez ce salon visible dans l’annuaire des salons publics.",
|
||||
"publish_space": "Rendez cet espace visible dans le répertoires des salons publics.",
|
||||
@ -2581,6 +2585,7 @@
|
||||
"breadcrumb_second_description": "Vous perdrez l’historique de vos messages",
|
||||
"breadcrumb_third_description": "Vous devrez vérifier à nouveau tous vos appareils et tous vos contacts",
|
||||
"breadcrumb_title": "Êtes-vous sûr de vouloir réinitialiser votre identité ?",
|
||||
"breadcrumb_title_cant_confirm": "Vous devez réinitialiser votre identité",
|
||||
"breadcrumb_title_forgot": "Vous avez oublié votre clé de récupération ? Vous devez réinitialiser votre identité.",
|
||||
"breadcrumb_title_sync_failed": "Impossible de synchroniser le stockage des clés. Vous devez réinitialiser votre identité.",
|
||||
"breadcrumb_warning": "Ne faites cela que si vous pensez que votre compte a été compromis.",
|
||||
@ -3981,6 +3986,7 @@
|
||||
"connection_lost": "La connexion au serveur a été perdue",
|
||||
"connection_lost_description": "Vous ne pouvez pas passer d’appels sans connexion au serveur.",
|
||||
"consulting": "Consultation avec %(transferTarget)s. <a>Transfert à %(transferee)s</a>",
|
||||
"decline_call": "Refuser",
|
||||
"default_device": "Appareil par défaut",
|
||||
"dial": "Composer",
|
||||
"dialpad": "Pavé numérique",
|
||||
@ -4032,6 +4038,7 @@
|
||||
"show_sidebar_button": "Afficher la barre latérale",
|
||||
"silence": "Mettre l’appel en sourdine",
|
||||
"silenced": "Notifications silencieuses",
|
||||
"skip_lobby_toggle_option": "Rejoignez immédiatement",
|
||||
"start_screenshare": "Commencer à partager mon écran",
|
||||
"stop_screenshare": "Arrêter de partager mon écran",
|
||||
"too_many_calls": "Trop d’appels",
|
||||
|
||||
@ -708,7 +708,6 @@
|
||||
"title": "Método de Recuperación eliminado",
|
||||
"warning": "Se non eliminaches o método de recuperación, un atacante podería estar a intentar acceder á túa conta. Cambia inmediatamente o contrasinal da conta e establece un novo método de recuperación nos Axustes."
|
||||
},
|
||||
"reset_all_button": "Perdidos ou esquecidos tódolos métodos de recuperación? <a>Restabléceos</a>",
|
||||
"set_up_toast_title": "Configurar Copia de apoio Segura",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Fai unha copia de apoio das chaves antes de saír para evitar perdelas."
|
||||
@ -727,7 +726,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Dispositivo verificado",
|
||||
"skip_verification": "Omitir a verificación por agora",
|
||||
"unable_to_verify": "Non se puido verificar este dispositivo",
|
||||
"verify_this_device": "Verifica este dispositivo"
|
||||
},
|
||||
"cancelled": "Cancelaches a verificación.",
|
||||
@ -744,7 +742,6 @@
|
||||
"incoming_sas_dialog_title": "Solicitude entrante de verificación",
|
||||
"incoming_sas_user_dialog_text_1": "Verifica esta usuaria para marcala como confiable. Ao confiar nas usuarias proporcionache tranquilidade extra cando usas cifrado de extremo-a-extremo.",
|
||||
"incoming_sas_user_dialog_text_2": "Ao verificar esta usuaria marcarás a súa sesión como confiable, e tamén marcará a túa sesión como confiable para elas.",
|
||||
"no_key_or_device": "Semella que non tes unha Chave de Seguridade ou outros dispositivos cos que verificar. Este dispositivo non poderá acceder a mensaxes antigas cifradas. Para poder verificar a túa identidade neste dispositivo tes que restablecer as chaves de verificación.",
|
||||
"no_support_qr_emoji": "Este dispositivo que intentas verificar non ten soporte para código QR nin verificación por emoji, que é o que %(brand)s soporta. Inténtao cun cliente diferente.",
|
||||
"other_party_cancelled": "A outra parte cancelou a verificación.",
|
||||
"prompt_encrypted": "Verificar todas as usuarias da sala para asegurar que é segura.",
|
||||
@ -757,7 +754,6 @@
|
||||
"qr_reciprocate_same_shield_device": "Xa case está! Mostra o teu outro dispositivo o mesmo escudo?",
|
||||
"qr_reciprocate_same_shield_user": "Case feito! ¿está %(displayName)s mostrando as mesmas emoticonas?",
|
||||
"request_toast_detail": "%(deviceId)s desde %(ip)s",
|
||||
"reset_proceed_prompt": "Procede co restablecemento",
|
||||
"sas_caption_self": "Verifica este dispositivo confirmando que o seguinte número aparece na pantalla.",
|
||||
"sas_caption_user": "Verifica esta usuaria confirmando que o seguinte número aparece na súa pantalla.",
|
||||
"sas_description": "Compara o conxunto único de emoticonas se non tes cámara no outro dispositivo",
|
||||
@ -778,7 +774,6 @@
|
||||
"unverified_session_toast_title": "Nova sesión. Foches ti?",
|
||||
"unverified_sessions_toast_description": "Revisa para asegurarte de que a túa conta está protexida",
|
||||
"unverified_sessions_toast_reject": "Máis tarde",
|
||||
"verification_description": "Verifica a túa identidade para acceder a mensaxes cifradas e acreditar a túa identidade ante outras.",
|
||||
"verification_dialog_title_device": "Verificar outro dispositivo",
|
||||
"verification_dialog_title_user": "Solicitude de Verificación",
|
||||
"verification_skip_warning": "Sen verificación non poderás acceder a tódalas túas mensaxes e poderían aparecer como non confiables ante outras persoas.",
|
||||
@ -788,9 +783,6 @@
|
||||
"verify_emoji_prompt": "Verficación por comparación de emoticonas.",
|
||||
"verify_emoji_prompt_qr": "Se non podes escanear o código superior, verifica comparando as emoticonas.",
|
||||
"verify_later": "Verificarei máis tarde",
|
||||
"verify_using_device": "Verifica usando outro dispositivo",
|
||||
"verify_using_key": "Verificar coa Chave de Seguridade",
|
||||
"verify_using_key_or_phrase": "Verificar coa Chave ou Frase de Seguridade",
|
||||
"waiting_for_user_accept": "Agardando a que %(displayName)s acepte…",
|
||||
"waiting_other_device": "Agardando a que verifiques no teu outro dispositivo…",
|
||||
"waiting_other_device_details": "Agardando a que verifiques o teu outro dispositivo, %(deviceName)s %(deviceId)s …",
|
||||
|
||||
@ -617,7 +617,6 @@
|
||||
"accepting": "מקבל…",
|
||||
"after_new_login": {
|
||||
"device_verified": "המכשיר אומת",
|
||||
"unable_to_verify": "לא ניתן לאמת את מכשיר זה",
|
||||
"verify_this_device": "אמתו את מכשיר זה"
|
||||
},
|
||||
"cancelled": "בטלתם את האימות.",
|
||||
|
||||
@ -965,7 +965,6 @@
|
||||
"title": "Helyreállítási mód eltávolítva",
|
||||
"warning": "Ha nem Ön távolított el a helyreállítási módot, akkor lehet, hogy egy támadó hozzá akar férni a fiókjához. Azonnal változtassa meg a jelszavát, és állítson be egy helyreállítási módot a Beállításokban."
|
||||
},
|
||||
"reset_all_button": "Elfelejtette vagy elveszett minden helyreállítási lehetőség? <a>Minden alaphelyzetbe állítása</a>",
|
||||
"set_up_recovery": "Helyreállítás beállítása",
|
||||
"set_up_recovery_toast_description": "Létrehozhat egy helyreállítási kulcsot, amellyel helyreállíthatja a titkosított üzenetelőzményeit, ha elveszíti a hozzáférést az eszközeihez.",
|
||||
"set_up_toast_title": "Biztonsági mentés beállítása",
|
||||
@ -988,7 +987,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Eszköz ellenőrizve",
|
||||
"skip_verification": "Ellenőrzés kihagyása most",
|
||||
"unable_to_verify": "Ennek az eszköznek az ellenőrzése nem lehetséges",
|
||||
"verify_this_device": "Az eszköz ellenőrzése"
|
||||
},
|
||||
"cancelled": "Megszakította az ellenőrzést.",
|
||||
@ -1023,7 +1021,6 @@
|
||||
"text": "Adja meg valamelyik saját eszköze azonosítóját és ujjlenyomatát annak ellenőrzéséhez. MEGJEGYZÉS: ez lehetővé teszi a másik eszköz számára, hogy az Ön nevében küldjön és fogadjon üzeneteket. HA VALAKI AZT MONDTA, HOGY ILLESSZEN BE IDE VALAMIT, VALÓSZÍNŰLEG ÁTVERIK!",
|
||||
"wrong_fingerprint": "Nem sikerült ellenőrizni a(z) „%(deviceId)s” eszközt – a mellékelt „%(fingerprint)s” ujjlenyomat nem egyezik az eszköz ujjlenyomatával: „%(fprint)s”"
|
||||
},
|
||||
"no_key_or_device": "Úgy tűnik, hogy nem rendelkezik helyreállítási kulccsal, vagy másik eszközzel, amellyel ellenőrizhetné. Ezzel az eszközzel nem fér majd hozzá a régi titkosított üzenetekhez. Ahhoz, hogy a személyazonosságát ezen az eszközön ellenőrizni lehessen, az ellenőrzési kulcsokat alaphelyzetbe kell állítania.",
|
||||
"no_support_qr_emoji": "Az ellenőrizni kívánt eszköz nem támogatja sem a QR-kód leolvasását, sem az emodzsis ellenőrzést, amelyeket az %(brand)s támogat. Próbálja meg egy másik klienssel.",
|
||||
"other_party_cancelled": "A másik fél megszakította az ellenőrzést.",
|
||||
"prompt_encrypted": "Ellenőrizze a szoba összes tagját, hogy meggyőződjön a biztonságáról.",
|
||||
@ -1039,7 +1036,6 @@
|
||||
"request_toast_accept_user": "Felhasználó ellenőrzése",
|
||||
"request_toast_decline_counter": "Mellőzés (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s innen: %(ip)s",
|
||||
"reset_proceed_prompt": "Lecserélés folytatása",
|
||||
"sas_caption_self": "Ellenőrizze ezt az eszközt azzal, hogy megerősíti, hogy a következő szám jelenik meg a képernyőjén.",
|
||||
"sas_caption_user": "Ellenőrizze ezt a felhasználót azzal, hogy megerősíti, hogy a következő szám jelenik meg a képernyőjén.",
|
||||
"sas_description": "Hasonlítsd össze az egyedi emodzsikat ha valamelyik eszközön nincs kamera",
|
||||
@ -1062,7 +1058,6 @@
|
||||
"unverified_sessions_toast_description": "Tekintse át, hogy meggyőződjön arról, hogy a fiókja biztonságban van",
|
||||
"unverified_sessions_toast_reject": "Később",
|
||||
"unverified_sessions_toast_title": "Ellenőrizetlen bejelentkezései vannak",
|
||||
"verification_description": "Ellenőrizze a személyazonosságát, hogy hozzáférjen a titkosított üzeneteihez és másoknak is bizonyítani tudja személyazonosságát.",
|
||||
"verification_dialog_title_device": "Másik eszköz ellenőrzése",
|
||||
"verification_dialog_title_user": "Ellenőrzési kérés",
|
||||
"verification_skip_warning": "Az ellenőrzés nélkül nem fér hozzá az összes üzenetéhez és mások számára megbízhatatlannak fog látszani.",
|
||||
@ -1072,9 +1067,6 @@
|
||||
"verify_emoji_prompt": "Ellenőrzés egyedi emodzsik összehasonlításával.",
|
||||
"verify_emoji_prompt_qr": "Ha nem tudod beolvasni az alábbi kódot, ellenőrizd az egyedi emodzsik összehasonlításával.",
|
||||
"verify_later": "Később ellenőrzöm",
|
||||
"verify_using_device": "Ellenőrizze egy másik eszközzel",
|
||||
"verify_using_key": "Ellenőrzés helyreállítási kulccsal",
|
||||
"verify_using_key_or_phrase": "Ellenőrzés helyreállítási kulccsal vagy jelmondattal",
|
||||
"waiting_for_user_accept": "%(displayName)s felhasználóra várakozás az elfogadáshoz…",
|
||||
"waiting_other_device": "Várakozás a másik eszköztől való ellenőrzésre…",
|
||||
"waiting_other_device_details": "Várakozás a másik eszközről való ellenőrzésre: %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -964,7 +964,6 @@
|
||||
"title": "Metode Pemulihan Dihapus",
|
||||
"warning": "Jika Anda tidak menghapus metode pemulihan, sebuah penyerang mungkin mencoba mengakses akun Anda. Ubah kata sandi akun Anda dan segera tetapkan metode pemulihan baru di Pengaturan."
|
||||
},
|
||||
"reset_all_button": "Lupa atau kehilangan semua metode pemulihan? <a>Atur ulang semuanya</a>",
|
||||
"set_up_recovery": "Siapkan pemulihan",
|
||||
"set_up_recovery_toast_description": "Buat kunci pemulihan yang dapat digunakan untuk memulihkan riwayat pesan terenkripsi jika Anda kehilangan akses ke perangkat Anda.",
|
||||
"set_up_toast_title": "Siapkan Cadangan Aman",
|
||||
@ -987,7 +986,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Perangkat telah diverifikasi",
|
||||
"skip_verification": "Lewatkan verifikasi untuk sementara",
|
||||
"unable_to_verify": "Tidak dapat memverifikasi perangkat ini",
|
||||
"verify_this_device": "Verifikasi perangkat ini"
|
||||
},
|
||||
"cancelled": "Anda membatalkan verifikasi.",
|
||||
@ -1022,7 +1020,6 @@
|
||||
"text": "Berikan ID dan sidik jari salah satu perangkat Anda untuk memverifikasinya. PERHATIKAN bahwa ini memungkinkan perangkat lain untuk mengirim dan menerima pesan seperti Anda. JIKA SESEORANG MEMINTA ANDA UNTUK MENEMPELKAN SESUATU DI SINI, KEMUNGKINAN ANDA SEDANG DITIPU!",
|
||||
"wrong_fingerprint": "Tidak dapat memverifikasi perangkat '%(deviceId)s' - sidik jari yang disediakan '%(fingerprint)s' tidak cocok dengan sidik jari perangkat, '%(fprint)s'"
|
||||
},
|
||||
"no_key_or_device": "Sepertinya Anda tidak memiliki Kunci Pemulihan atau perangkat lain yang dapat Anda verifikasi. Perangkat ini tidak akan dapat mengakses pesan terenkripsi lama. Untuk memverifikasi identitas Anda di perangkat ini, Anda harus mengatur ulang kunci verifikasi Anda.",
|
||||
"no_support_qr_emoji": "Perangkat yang Anda sedang verifikasi tidak mendukung pemindaian kode QR atau verifikasi emoji, yang didukung oleh %(brand)s. Coba menggunakan klien yang lain.",
|
||||
"other_party_cancelled": "Pengguna yang lain membatalkan proses verifikasi ini.",
|
||||
"prompt_encrypted": "Verifikasi semua pengguna di sebuah ruangan untuk memastikan keamanannya.",
|
||||
@ -1038,7 +1035,6 @@
|
||||
"request_toast_accept_user": "Verifikasi Pengguna",
|
||||
"request_toast_decline_counter": "Abaikan (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s dari %(ip)s",
|
||||
"reset_proceed_prompt": "Lanjutkan dengan mengatur ulang",
|
||||
"sas_caption_self": "Verifikasi perangkat ini dengan mengkonfirmasi nomor berikut ini yang ditampilkan di layarnya.",
|
||||
"sas_caption_user": "Verifikasi pengguna ini dengan mengkonfirmasi nomor berikut yang ditampilkan.",
|
||||
"sas_description": "Bandingkan emoji jika Anda tidak memiliki sebuah kamera di kedua perangkat",
|
||||
@ -1061,7 +1057,6 @@
|
||||
"unverified_sessions_toast_description": "Periksa untuk memastikan akun Anda aman",
|
||||
"unverified_sessions_toast_reject": "Nanti",
|
||||
"unverified_sessions_toast_title": "Anda memiliki sesi yang belum diverifikasi",
|
||||
"verification_description": "Verifikasi identitas Anda untuk mengakses pesan terenkripsi dan membuktikan identitas Anda kepada orang lain. Jika Anda juga menggunakan ponsel, harap buka aplikasi di sana sebelum melanjutkan.",
|
||||
"verification_dialog_title_device": "Verifikasi perangkat lain",
|
||||
"verification_dialog_title_user": "Permintaan Verifikasi",
|
||||
"verification_skip_warning": "Tanpa memverifikasi, Anda tidak akan memiliki akses ke semua pesan Anda dan tampak tidak dipercayai kepada lainnya.",
|
||||
@ -1071,9 +1066,6 @@
|
||||
"verify_emoji_prompt": "Verifikasi dengan membandingkan emoji unik.",
|
||||
"verify_emoji_prompt_qr": "Jika Anda tidak dapat memindai kode di atas, verifikasi dengan membandingkan emoji yang unik.",
|
||||
"verify_later": "Saya verifikasi nanti",
|
||||
"verify_using_device": "Verifikasi dengan perangkat lain",
|
||||
"verify_using_key": "Verifikasi dengan Kunci Keamanan",
|
||||
"verify_using_key_or_phrase": "Verifikasi dengan Kunci atau Frasa Keamanan",
|
||||
"waiting_for_user_accept": "Menunggu untuk %(displayName)s untuk menerima…",
|
||||
"waiting_other_device": "Menunggu Anda untuk verifikasi di perangkat Anda yang lain…",
|
||||
"waiting_other_device_details": "Menunggu Anda untuk memverifikasi perangkat Anda yang lain, %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -701,7 +701,6 @@
|
||||
"recovery_method_removed": {
|
||||
"title": "Endurheimtuaðferð fjarlægð"
|
||||
},
|
||||
"reset_all_button": "Gleymdirðu eða týndir öllum aðferðum til endurheimtu? <a>Endurstilla allt</a>",
|
||||
"set_up_toast_title": "Setja upp varið öryggisafrit",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Taktu öryggisafrit af dulritunarlyklunum áður en þú skráir þig út svo þeir tapist ekki."
|
||||
@ -719,7 +718,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Tæki er sannreynt",
|
||||
"skip_verification": "Sleppa sannvottun í bili",
|
||||
"unable_to_verify": "Tókst ekki að sannreyna þetta tæki",
|
||||
"verify_this_device": "Sannreyna þetta tæki"
|
||||
},
|
||||
"cancelled": "Þú hættir við sannvottun.",
|
||||
@ -743,7 +741,6 @@
|
||||
"qr_reciprocate_same_shield_device": "Næstum því búið! Sýnir hitt tækið þitt sama skjöldinn?",
|
||||
"qr_reciprocate_same_shield_user": "Næstum því búið! Sýnir %(displayName)s sama skjöldinn?",
|
||||
"request_toast_detail": "%(deviceId)s frá %(ip)s",
|
||||
"reset_proceed_prompt": "Halda áfram með endurstillingu",
|
||||
"sas_caption_self": "Sannreyndu þetta tæki með því að staðfesta eftirfarandi númer sem birtist á skjá þess.",
|
||||
"sas_caption_user": "Sannreyndu þennan notanda með því að staðfesta eftirfarandi númer sem birtist á skjánum hans.",
|
||||
"sas_description": "Berðu saman einstakar táknmyndir ef ekki er myndavél á tækjunum",
|
||||
@ -770,9 +767,6 @@
|
||||
"verify_emoji_prompt": "Sannprófaðu með því að bera saman einstakar táknmyndir.",
|
||||
"verify_emoji_prompt_qr": "Ef þú getur ekki skannað kóðann hér fyrir ofan, skaltu sannprófa með því að bera saman einstakar táknmyndir.",
|
||||
"verify_later": "Ég mun sannreyna síðar",
|
||||
"verify_using_device": "Sannreyna með öðru tæki",
|
||||
"verify_using_key": "Sannreyna með öryggislykli",
|
||||
"verify_using_key_or_phrase": "Sannreyna með öryggisfrasa",
|
||||
"waiting_for_user_accept": "Bíð eftir að %(displayName)s samþykki…",
|
||||
"waiting_other_device": "Bíð eftir að þú staðfestir á hinu tækinu…",
|
||||
"waiting_other_device_details": "Bíð eftir að þú staðfestir á hinu tækinu, %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -836,7 +836,6 @@
|
||||
"title": "Metodo di ripristino rimosso",
|
||||
"warning": "Se non hai rimosso il metodo di ripristino, è possibile che un aggressore stia cercando di accedere al tuo account. Cambia la password del tuo account e imposta immediatamente un nuovo metodo di recupero nelle impostazioni."
|
||||
},
|
||||
"reset_all_button": "Hai dimenticato o perso tutti i metodi di recupero? <a>Reimposta tutto</a>",
|
||||
"set_up_toast_title": "Imposta il Backup Sicuro",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Fai una copia delle tue chiavi prima di disconnetterti per evitare di perderle."
|
||||
@ -855,7 +854,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Dispositivo verificato",
|
||||
"skip_verification": "Salta la verifica per adesso",
|
||||
"unable_to_verify": "Impossibile verificare questo dispositivo",
|
||||
"verify_this_device": "Verifica questo dispositivo"
|
||||
},
|
||||
"cancelled": "Hai annullato la verifica.",
|
||||
@ -875,7 +873,6 @@
|
||||
"incoming_sas_dialog_waiting": "In attesa che il partner confermi…",
|
||||
"incoming_sas_user_dialog_text_1": "Verifica questo utente per contrassegnarlo come affidabile. La fiducia degli utenti offre una maggiore tranquillità quando si utilizzano messaggi cifrati end-to-end.",
|
||||
"incoming_sas_user_dialog_text_2": "La verifica di questo utente contrassegnerà come fidata la sua sessione a te e viceversa.",
|
||||
"no_key_or_device": "Pare che tu non abbia una chiave di sicurezza o altri dispositivi con cui poterti verificare. Questo dispositivo non potrà accedere ai vecchi messaggi cifrati. Per potere verificare la tua ideintità su questo dispositivo, dovrai reimpostare le chiavi di verifica.",
|
||||
"no_support_qr_emoji": "Il dispositivo che stai cercando di verificare non supporta la scansione di un codice QR o la verifica emoji, che sono supportate da %(brand)s. Prova con un client diverso.",
|
||||
"other_party_cancelled": "L'altra parte ha annullato la verifica.",
|
||||
"prompt_encrypted": "Verifica tutti gli utenti in una stanza per confermare che sia sicura.",
|
||||
@ -890,7 +887,6 @@
|
||||
"request_toast_accept": "Verifica sessione",
|
||||
"request_toast_decline_counter": "Ignora (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s da %(ip)s",
|
||||
"reset_proceed_prompt": "Procedi con la reimpostazione",
|
||||
"sas_caption_self": "Verifica questo dispositivo confermando che il seguente numero appare sul suo schermo.",
|
||||
"sas_caption_user": "Verifica questo utente confermando che il seguente numero appare sul suo schermo.",
|
||||
"sas_description": "Confrontate un set di emoji univoci se non avete una fotocamera sui dispositivi",
|
||||
@ -913,7 +909,6 @@
|
||||
"unverified_sessions_toast_description": "Controlla per assicurarti che l'account sia sicuro",
|
||||
"unverified_sessions_toast_reject": "Più tardi",
|
||||
"unverified_sessions_toast_title": "Hai sessioni non verificate",
|
||||
"verification_description": "Verifica la tua identità per accedere ai messaggi cifrati e provare agli altri che sei tu.",
|
||||
"verification_dialog_title_device": "Verifica altro dispositivo",
|
||||
"verification_dialog_title_user": "Richiesta verifica",
|
||||
"verification_skip_warning": "Senza la verifica, non avrai accesso a tutti i tuoi messaggi e potresti apparire agli altri come non fidato.",
|
||||
@ -923,9 +918,6 @@
|
||||
"verify_emoji_prompt": "Verifica confrontando emoji specifici.",
|
||||
"verify_emoji_prompt_qr": "Se non riesci a scansionare il codice sopra, verifica confrontando emoji specifiche.",
|
||||
"verify_later": "Verificherò dopo",
|
||||
"verify_using_device": "Verifica con un altro dispositivo",
|
||||
"verify_using_key": "Verifica con chiave di sicurezza",
|
||||
"verify_using_key_or_phrase": "Verifica con chiave di sicurezza o frase",
|
||||
"waiting_for_user_accept": "In attesa che %(displayName)s accetti…",
|
||||
"waiting_other_device": "In attesa della verifica nel tuo altro dispositivo…",
|
||||
"waiting_other_device_details": "In attesa della verifica nel tuo altro dispositivo, %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -792,7 +792,6 @@
|
||||
"title": "復元方法を削除しました",
|
||||
"warning": "復元方法を削除しなかった場合、攻撃者があなたのアカウントにアクセスしようとしている可能性があります。設定画面でアカウントのパスワードを至急変更し、新しい復元方法を設定してください。"
|
||||
},
|
||||
"reset_all_button": "復元方法を全て失ってしまいましたか?<a>リセットできます</a>",
|
||||
"set_up_toast_title": "セキュアバックアップを設定",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "鍵を失くさないよう、サインアウトする前にバックアップしてください。"
|
||||
@ -811,7 +810,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "端末が認証されました",
|
||||
"skip_verification": "認証をスキップ",
|
||||
"unable_to_verify": "この端末を認証できません",
|
||||
"verify_this_device": "この端末を認証"
|
||||
},
|
||||
"cancelled": "認証をキャンセルしました。",
|
||||
@ -831,7 +829,6 @@
|
||||
"incoming_sas_dialog_waiting": "相手の承認を待機しています…",
|
||||
"incoming_sas_user_dialog_text_1": "このユーザーを認証すると、信頼済として表示します。ユーザーを信頼すると、より一層安心してエンドツーエンド暗号化を使用することができます。",
|
||||
"incoming_sas_user_dialog_text_2": "このユーザーを認証すると、相手のセッションと自分のセッションを信頼済として表示します。",
|
||||
"no_key_or_device": "セキュリティーキーもしくは認証可能な端末が設定されていません。この端末では、以前暗号化されたメッセージにアクセスすることができません。この端末で本人確認を行うには、認証用の鍵を再設定する必要があります。",
|
||||
"no_support_qr_emoji": "認証しようとしている端末は、QRコードのスキャンや絵文字による認証をサポートしていませんが、%(brand)sではサポートされています。異なるクライアントで試してください。",
|
||||
"other_party_cancelled": "相手が認証をキャンセルしました。",
|
||||
"prompt_encrypted": "ルームの全てのユーザーを認証すると、ルームが安全であることを確認できます。",
|
||||
@ -844,7 +841,6 @@
|
||||
"qr_reciprocate_same_shield_device": "あと少しです! あなたの他の端末は同じ盾マークを表示していますか?",
|
||||
"qr_reciprocate_same_shield_user": "あと少しです!%(displayName)sは同じ盾マークを表示していますか?",
|
||||
"request_toast_detail": "%(ip)sの%(deviceId)s",
|
||||
"reset_proceed_prompt": "リセットする",
|
||||
"sas_caption_self": "この端末を認証するには、画面に以下の数字が表示されていることを確認してください。",
|
||||
"sas_caption_user": "このユーザーを認証するには、相手の画面に以下の数字が表示されていることを確認してください。",
|
||||
"sas_description": "両方の端末でQRコードをキャプチャできない場合、絵文字の比較を選んでください",
|
||||
@ -866,7 +862,6 @@
|
||||
"unverified_sessions_toast_description": "アカウントが安全かどうか確認してください",
|
||||
"unverified_sessions_toast_reject": "後で",
|
||||
"unverified_sessions_toast_title": "未認証のセッションがあります",
|
||||
"verification_description": "暗号化されたメッセージにアクセスするには、本人確認が必要です。",
|
||||
"verification_dialog_title_device": "他の端末を認証",
|
||||
"verification_dialog_title_user": "認証の要求",
|
||||
"verification_skip_warning": "認証を行わないと、あなたの全てのメッセージにアクセスできず、他のユーザーに信頼済として表示されない可能性があります。",
|
||||
@ -876,9 +871,6 @@
|
||||
"verify_emoji_prompt": "絵文字の並びを比較して認証。",
|
||||
"verify_emoji_prompt_qr": "上記のコードをスキャンできない場合は、絵文字による確認を行ってください。",
|
||||
"verify_later": "後で認証",
|
||||
"verify_using_device": "別の端末で認証",
|
||||
"verify_using_key": "セキュリティーキーで認証",
|
||||
"verify_using_key_or_phrase": "セキュリティーキーあるいはセキュリティーフレーズで認証",
|
||||
"waiting_for_user_accept": "%(displayName)sによる承認を待機しています…",
|
||||
"waiting_other_device": "他の端末での認証を待機しています…",
|
||||
"waiting_other_device_details": "端末 %(deviceName)s(%(deviceId)s)での認証を待機しています…",
|
||||
|
||||
@ -700,7 +700,6 @@
|
||||
"title": "აღდგენის მეთოდი ამოღებულია",
|
||||
"warning": "თუ თქვენ არ წაშალეთ აღდგენის მეთოდი, შესაძლოა თავდამსხმელი ცდილობდეს თქვენს ანგარიშზე წვდომას. შეცვალეთ თქვენი ანგარიშის პაროლი და დააყენეთ აღდგენის ახალი მეთოდი დაუყოვნებლივ პარამეტრებში."
|
||||
},
|
||||
"reset_all_button": "დაგავიწყდათ თუ დაკარგეთ აღდგენის ყველა მეთოდი?<a> გადატვირთეთ ყველა</a>",
|
||||
"set_up_toast_title": "დააყენეთ უსაფრთხო სარეზერვო ასლი",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "შექმენით თქვენი გასაღებების სარეზერვო ასლები გასვლამდე, რათა არ დაკარგოთ ისინი."
|
||||
@ -719,7 +718,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "მოწყობილობა დადასტურებულია",
|
||||
"skip_verification": "ამ დროისთვის გამოტოვეთ დადასტურება",
|
||||
"unable_to_verify": "ამ მოწყობილობის დადასტურება შეუძლებელია",
|
||||
"verify_this_device": "დაადასტურეთ ეს მოწყობილობა"
|
||||
},
|
||||
"cancelled": "თქვენ გააუქმეთ დადასტურება.",
|
||||
@ -739,7 +737,6 @@
|
||||
"incoming_sas_dialog_waiting": "ელოდება პარტნიორის დადასტურებას…",
|
||||
"incoming_sas_user_dialog_text_1": "დაადასტურეთ ეს მომხმარებელი, რათა მონიშნოთ ისინი სანდოდ. მომხმარებელთა ნდობა გაძლევთ დამატებით სიმშვიდეს, როდესაც იყენებთ ბოლომდე დაშიფრული შეტყობინებების გამოყენებას.",
|
||||
"incoming_sas_user_dialog_text_2": "ამ მომხმარებლის დადასტურება მონიშნავს მის სესიას სანდოდ და ასევე მონიშნავს თქვენს სესიას, როგორც მისთვის სანდო.",
|
||||
"no_key_or_device": "როგორც ჩანს, არ გაქვთ უსაფრთხოების გასაღები ან სხვა მოწყობილობები, რომლებზეც შეგიძლიათ დაადასტუროთ. ეს მოწყობილობა ვერ შეძლებს ძველ დაშიფრულ შეტყობინებებზე წვდომას. იმისათვის, რომ დაადასტუროთ თქვენი ვინაობა ამ მოწყობილობაზე, დაგჭირდებათ თქვენი დამადასტურებელი გასაღებების გადატვირთვა.",
|
||||
"no_support_qr_emoji": "მოწყობილობა, რომლის გადამოწმებას ცდილობთ, არ უჭერს მხარს QR კოდის ან emoji-ის დადასტურებას, რაც არის%(brand)s მხარს უჭერს. სცადეთ სხვა კლიენტთან.",
|
||||
"other_party_cancelled": "მეორე მხარემ გააუქმა გადამოწმება.",
|
||||
"prompt_encrypted": "დაადასტურეთ ოთახის ყველა მომხმარებელი, რომ დარწმუნდეთ, რომ ის უსაფრთხოა.",
|
||||
@ -754,7 +751,6 @@
|
||||
"request_toast_accept": "სესიის გადამოწმება",
|
||||
"request_toast_decline_counter": "იგნორირება (%(counter)s )",
|
||||
"request_toast_detail": "%(deviceId)sსაწყისი%(ip)s",
|
||||
"reset_proceed_prompt": "გააგრძელეთ გადატვირთვა",
|
||||
"sas_caption_self": "დაადასტურეთ ეს მოწყობილობა და დაადასტურეთ, რომ შემდეგი ნომერი გამოჩნდება მის ეკრანზე.",
|
||||
"sas_caption_user": "დაადასტურეთ ეს მომხმარებელი და დაადასტურეთ, რომ შემდეგი ნომერი გამოჩნდება მის ეკრანზე.",
|
||||
"sas_description": "შეადარეთ emoji-ების უნიკალური ნაკრები, თუ არცერთ მოწყობილობაზე არ გაქვთ კამერა",
|
||||
@ -777,7 +773,6 @@
|
||||
"unverified_sessions_toast_description": "გადახედეთ, რათა დარწმუნდეთ, რომ თქვენი ანგარიში უსაფრთხოა",
|
||||
"unverified_sessions_toast_reject": "მოგვიანებით",
|
||||
"unverified_sessions_toast_title": "თქვენ გაქვთ დაუდასტურებელი სესიები",
|
||||
"verification_description": "დაადასტურეთ თქვენი ვინაობა დაშიფრულ შეტყობინებებზე წვდომისთვის და სხვებისთვის თქვენი ვინაობის დასამტკიცებლად. თუ თქვენ ასევე იყენებთ მობილურ მოწყობილობას, გთხოვთ, გახსენით აპი იქ, სანამ გააგრძელებთ.",
|
||||
"verification_dialog_title_device": "გადაამოწმეთ სხვა მოწყობილობა",
|
||||
"verification_dialog_title_user": "გადამოწმების მოთხოვნა",
|
||||
"verification_skip_warning": "დადასტურების გარეშე, თქვენ არ გექნებათ წვდომა თქვენს ყველა შეტყობინებაზე და შეიძლება სხვებისთვის არასანდო გამოჩნდეთ.",
|
||||
@ -787,9 +782,6 @@
|
||||
"verify_emoji_prompt": "გადაამოწმეთ უნიკალური emoji-ების შედარებით.",
|
||||
"verify_emoji_prompt_qr": "თუ ზემოთ მოცემულ კოდს ვერ სკანირებთ, გადაამოწმეთ უნიკალური emoji-ების შედარებით.",
|
||||
"verify_later": "მოგვიანებით გადავამოწმებ",
|
||||
"verify_using_device": "გადაამოწმეთ სხვა მოწყობილობით",
|
||||
"verify_using_key": "გადაამოწმეთ უსაფრთხოების გასაღებით",
|
||||
"verify_using_key_or_phrase": "გადაამოწმეთ უსაფრთხოების გასაღებით ან ფრაზით",
|
||||
"waiting_for_user_accept": "ელოდება%(displayName)s მიღება…",
|
||||
"waiting_other_device": "ელოდება თქვენ გადამოწმებას თქვენს სხვა მოწყობილობაზე…",
|
||||
"waiting_other_device_details": "გელოდებათ გადამოწმებას თქვენს სხვა მოწყობილობაზე,%(deviceName)s (%(deviceId)s )…",
|
||||
|
||||
@ -700,7 +700,6 @@
|
||||
"title": "ວິທີລົບຂະບວນການກູ້ຄືນ",
|
||||
"warning": "ຖ້າທ່ານບໍ່ໄດ້ລືບຂະບວນການກູ້ຄືນ, ຜູ້ໂຈມຕີອາດຈະພະຍາຍາມເຂົ້າເຖິງບັນຊີຂອງທ່ານ. ປ່ຽນລະຫັດຜ່ານບັນຊີຂອງທ່ານ ແລະ ກຳນົດຂະບວນການກູ້ຄືນໃໝ່ທັນທີໃນການຕັ້ງຄ່າ."
|
||||
},
|
||||
"reset_all_button": "ລືມ ຫຼື ສູນເສຍວິທີການກູ້ຄືນທັງຫມົດ? <a>ຕັ້ງຄ່າຄືນໃໝ່ທັງໝົດ</a>",
|
||||
"set_up_toast_title": "ຕັ້ງຄ່າການສໍາຮອງຂໍ້ມູນທີ່ປອດໄພ",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "ສຳຮອງຂໍ້ມູນກະແຈຂອງທ່ານກ່ອນທີ່ຈະອອກຈາກລະບົບເພື່ອຫຼີກເວັ້ນການສູນເສຍຂໍ້ມູນ."
|
||||
@ -718,7 +717,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "ຢັ້ງຢືນອຸປະກອນແລ້ວ",
|
||||
"skip_verification": "ຂ້າມການຢັ້ງຢືນດຽວນີ້",
|
||||
"unable_to_verify": "ບໍ່ສາມາດຢັ້ງຢືນອຸປະກອນນີ້ໄດ້",
|
||||
"verify_this_device": "ຢັ້ງຢືນອຸປະກອນນີ້"
|
||||
},
|
||||
"cancelled": "ທ່ານໄດ້ຍົກເລີກການຢັ້ງຢືນແລ້ວ.",
|
||||
@ -735,7 +733,6 @@
|
||||
"incoming_sas_dialog_title": "ການຮ້ອງຂໍການຢັ້ງຢືນຂາເຂົ້າ",
|
||||
"incoming_sas_user_dialog_text_1": "ຢັ້ງຢືນຜູ້ໃຊ້ນີ້ເພື່ອສ້າງເຄື່ອງທີ່ເຊື່ອຖືໄດ້. ຜູ້ໃຊ້ທີ່ເຊື່ອຖືໄດ້ ເຮັດໃຫ້ທ່ານອຸ່ນໃຈຂື້ນເມື່ຶຶອເຂົ້າລະຫັດຂໍ້ຄວາມແຕ່ຕົ້ນທາງເຖິງປາຍທາງ.",
|
||||
"incoming_sas_user_dialog_text_2": "ການຢືນຢັນຜູ້ໃຊ້ນີ້ຈະເປັນເຄື່ອງໝາຍໃນລະບົບຂອງເຂົາເຈົ້າໜ້າເຊື່ອຖືໄດ້ ແລະ ເປັນເຄື່ອງໝາຍເຖິງລະບົບຂອງທ່ານ ເປັນທີ່ເຊື່ອຖືໄດ້ຕໍ່ກັບເຂົາເຈົ້າ.",
|
||||
"no_key_or_device": "ເບິ່ງຄືວ່າທ່ານບໍ່ມີກະແຈຄວາມປອດໄພ ຫຼື ອຸປະກອນອື່ນໆທີ່ທ່ານສາມາດຢືນຢັນໄດ້. ອຸປະກອນນີ້ຈະບໍ່ສາມາດເຂົ້າເຖິງຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດເກົ່າໄດ້. ເພື່ອຢືນຢັນຕົວຕົນຂອງທ່ານໃນອຸປະກອນນີ້, ທ່ານຈຳເປັນຕ້ອງຕັ້ງລະຫັດຢືນຢັນຂອງທ່ານ.",
|
||||
"no_support_qr_emoji": "ອຸປະກອນທີ່ທ່ານພະຍາຍາມກວດສອບບໍ່ຮອງຮັບການສະແກນລະຫັດ QR ຫຼື ການຢັ້ງຢືນ emoji, ຊຶ່ງເປັນສິ່ງທີ່%(brand)sສະຫນັບສະຫນູນ. ລອງໃຊ້ກັບລູກຄ້າອື່ນ.",
|
||||
"other_party_cancelled": "ອີກຝ່າຍໄດ້ຍົກເລີກການຢັ້ງຢືນ.",
|
||||
"prompt_encrypted": "ຢັ້ງຢືນຜູ້ໃຊ້ທັງໝົດຢູ່ໃນຫ້ອງເພື່ອຮັບປະກັນວ່າມີຄວາມປອດໄພ.",
|
||||
@ -747,7 +744,6 @@
|
||||
"qr_reciprocate_same_shield_device": "ເກືອບສຳເລັດແລ້ວ! ອຸປະກອນອື່ນຂອງທ່ານສະແດງການປ້ອງກັນຄືກັນບໍ?",
|
||||
"qr_reciprocate_same_shield_user": "ໃກ້ສຳເລັດແລ້ວ! %(displayName)s ສະແດງການປ້ອງກັນແບບດຽວກັນບໍ?",
|
||||
"request_toast_detail": "%(deviceId)s ຈາກ %(ip)s",
|
||||
"reset_proceed_prompt": "ດຳເນີນການຕັ້ງຄ່າໃໝ່",
|
||||
"sas_caption_self": "ຢືນຢັນອຸປະກອນນີ້ໂດຍການຢືນຢັນຕົວເລກຕໍ່ໄປນີ້ໃຫ້ປາກົດຢູ່ໃນຫນ້າຈໍຂອງມັນ.",
|
||||
"sas_caption_user": "ຢືນຢັນຜູ້ໃຊ້ນີ້ໂດຍການຢືນຢັນຕົວເລກຕໍ່ໄປນີ້ໃຫ້ປາກົດຢູ່ໃນຫນ້າຈໍຂອງເຂົາເຈົ້າ.",
|
||||
"sas_description": "ປຽບທຽບຊຸດ emoji ທີ່ເປັນເອກະລັກຖ້າຫາກທ່ານບໍ່ມີກ້ອງຖ່າຍຮູບຢູ່ໃນອຸປະກອນໃດໜຶ່ງ",
|
||||
@ -768,7 +764,6 @@
|
||||
"unverified_session_toast_title": "ເຂົ້າສູ່ລະບົບໃໝ່. ນີ້ແມ່ນທ່ານບໍ?",
|
||||
"unverified_sessions_toast_description": "ກວດສອບໃຫ້ແນ່ໃຈວ່າບັນຊີຂອງທ່ານປອດໄພ",
|
||||
"unverified_sessions_toast_reject": "ຕໍ່ມາ",
|
||||
"verification_description": "ຢືນຢັນຕົວຕົນຂອງທ່ານເພື່ອເຂົ້າເຖິງຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດໄວ້ ແລະ ພິສູດຕົວຕົນຂອງທ່ານໃຫ້ກັບຜູ້ອື່ນ.",
|
||||
"verification_dialog_title_device": "ຢືນຢັນອຸປະກອນອື່ນ",
|
||||
"verification_dialog_title_user": "ການຮ້ອງຂໍການຢັ້ງຢືນ",
|
||||
"verification_skip_warning": "ໂດຍບໍ່ມີການຢັ້ງຢືນ, ທ່ານຈະບໍ່ສາມາດເຂົ້າເຖິງຂໍ້ຄວາມທັງຫມົດຂອງທ່ານ ແລະ ອາດຈະປາກົດວ່າບໍ່ຫນ້າເຊື່ອຖື.",
|
||||
@ -778,9 +773,6 @@
|
||||
"verify_emoji_prompt": "ຢັ້ງຢືນໂດຍການປຽບທຽບ emoji ທີ່ເປັນເອກະລັກ.",
|
||||
"verify_emoji_prompt_qr": "ຖ້າທ່ານບໍ່ສາມາດສະແກນລະຫັດຂ້າງເທິງໄດ້, ໃຫ້ກວດສອບໂດຍການປຽບທຽບອີໂມຈິທີ່ເປັນເອກະລັກ.",
|
||||
"verify_later": "ຂ້ອຍຈະກວດສອບພາຍຫຼັງ",
|
||||
"verify_using_device": "ຢັ້ງຢືນດ້ວຍອຸປະກອນອື່ນ",
|
||||
"verify_using_key": "ຢືນຢັນດ້ວຍກະແຈຄວາມປອດໄພ",
|
||||
"verify_using_key_or_phrase": "ຢືນຢັນດ້ວຍກະແຈຄວາມປອດໄພ ຫຼືປະໂຫຍກ",
|
||||
"waiting_for_user_accept": "ກຳລັງລໍຖ້າ %(displayName)s ຍອມຮັບ…",
|
||||
"waiting_other_device": "ກຳລັງລໍຖ້າໃຫ້ທ່ານຢັ້ງຢືນໃນອຸປະກອນອື່ນຂອງທ່ານ…",
|
||||
"waiting_other_device_details": "ກຳລັງລໍຖ້າໃຫ້ທ່ານກວດສອບໃນອຸປະກອນອື່ນຂອງທ່ານ, %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -535,7 +535,6 @@
|
||||
"title": "Atgavimo Metodas Pašalintas",
|
||||
"warning": "Jei jūs nepašalinote paskyros atgavimo metodo, gali būti, kad užpuolikas bando patekti į jūsų paskyrą. Nedelsiant nustatymuose pakeiskite savo paskyros slaptažodį ir nustatykite naują atgavimo metodą."
|
||||
},
|
||||
"reset_all_button": "Pamiršote arba praradote visus atkūrimo metodus? <a>Iš naujo nustatyti viską</a>",
|
||||
"set_up_toast_title": "Nustatyti Saugią Atsarginę Kopiją",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Prieš atsijungdami sukurkite atsarginę savo raktų kopiją, kad išvengtumėte jų praradimo."
|
||||
|
||||
@ -875,7 +875,6 @@
|
||||
"title": "Atkopšanas metode noņemta",
|
||||
"warning": "Ja nenoņēmi atkopšanas veidu, var būt, ka uzbrucējs mēģina piekļūt Tavam kontam. Nekavējoties jānomaina sava konta parole un jāiestata jauns atkopšanas veids."
|
||||
},
|
||||
"reset_all_button": "Aizmirsāt vai pazaudējāt visas atkopšanās iespējas? <a>Atiestatiet visu</a>",
|
||||
"set_up_toast_title": "Iestatīt drošu rezerves dublēšanu",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Pirms izrakstīšanās iestatiet atslēgu dublēšanu, lai izvairītos no tās pazaudēšanas."
|
||||
@ -894,7 +893,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Ierīce ir verificēta",
|
||||
"skip_verification": "Pagaidām izlaist verifikāciju",
|
||||
"unable_to_verify": "Neizdevās verificēt šo ierīci",
|
||||
"verify_this_device": "Verificēt šo ierīci"
|
||||
},
|
||||
"cancelled": "Tu atcēli apliecināšanu.",
|
||||
@ -914,7 +912,6 @@
|
||||
"incoming_sas_dialog_waiting": "Gaida partnera apstiprinājumu…",
|
||||
"incoming_sas_user_dialog_text_1": "Apliecināt šo lietotāju, lai atzīmētu to kā uzticamu. Uzticēšanās lietotājiem sniedz papildu mieru, kad tiek izmantotas pilnīgi šifrētas ziņas.",
|
||||
"incoming_sas_user_dialog_text_2": "Šī lietotāja apliecināšana atzīmēs viņa sesiju kā uzticamu, kā arī atzīmēs šo sesiju kā viņam uzticamu.",
|
||||
"no_key_or_device": "Izskatās, ka Tev nav drošības atslēgas vai citu ierīču, kurās varētu veikt apliecināšanu. Šajā ierīcē nebūs iespējams piekļūt vecām šifrētajām ziņām. Lai šajā ierīcē apliecinātu savu identitāti, būs nepieciešams atiestatīt savas apliecināšanas atslēgas.",
|
||||
"no_support_qr_emoji": "Ierīce, kuru mēģini apliecināt, nenodrošina kvadrātkoda nolasīšanu vai emocijzīmju apliecināšanu, ko nodrošina %(brand)s. Jāmēģina ar citu klientu.",
|
||||
"other_party_cancelled": "Otra puse atcēla apliecināšanu.",
|
||||
"prompt_encrypted": "Apliecināt visus istabā esošos lietotājus, lai nodrošinātu tās drošību.",
|
||||
@ -930,7 +927,6 @@
|
||||
"request_toast_accept_user": "Apliecināt lietotāju",
|
||||
"request_toast_decline_counter": "Neņemt vērā (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s no %(ip)s",
|
||||
"reset_proceed_prompt": "Turpināt atiestatīšanu",
|
||||
"sas_caption_self": "Apliecināt šo ierīci, apstiprinot, ka ekrānā parādās šis skaitlis.",
|
||||
"sas_caption_user": "Apliecināt šo lietotāju, apstiprinot, ka zemāk esošais numurs pārādās lietotāja ekrānā.",
|
||||
"sas_description": "Salīdziniet unikālu emocijzīmju kopu, ja nevienai ierīcei nav kameras",
|
||||
@ -953,7 +949,6 @@
|
||||
"unverified_sessions_toast_description": "Pārskatīt, lai nodrošinātu, ka konts ir drosībā.",
|
||||
"unverified_sessions_toast_reject": "Vēlāk",
|
||||
"unverified_sessions_toast_title": "Tev ir neapliecinātas sesijas",
|
||||
"verification_description": "Verificējiet savu identitāti, lai piekļūtu šifrētām ziņām un pierādītu savu identitāti citiem.",
|
||||
"verification_dialog_title_device": "Apliecināt otru ierīci",
|
||||
"verification_dialog_title_user": "Apliecināšanas pieprasījums",
|
||||
"verification_skip_warning": "Neveicot verifikāciju, jums nebūs piekļuves visām savām ziņām, kā arī ierīce var tikt parādīta citiem kā neuzticama.",
|
||||
@ -963,9 +958,6 @@
|
||||
"verify_emoji_prompt": "Apliecināt ar vienreizēju emocijzīmu salīdzināšanu.",
|
||||
"verify_emoji_prompt_qr": "Ja nevar nolasīt augstāk esošo kodu, var apliecināt ar vienreizēju emocijzīmju salīdzināšanu.",
|
||||
"verify_later": "Es verificēšu vēlāk",
|
||||
"verify_using_device": "Verificēt ar citu ierīci",
|
||||
"verify_using_key": "Verificēt ar drošības atslēgu",
|
||||
"verify_using_key_or_phrase": "Verificēt, izmantojot drošības atslēgu vai frāzi",
|
||||
"waiting_for_user_accept": "Gaida, kamēr %(displayName)s apstiprinās…",
|
||||
"waiting_other_device": "Gaida, kamēr jūs verificēsiet citā savā ierīcē...",
|
||||
"waiting_other_device_details": "Gaida, kamēr jūs verificēsiet citā ierīcē - %(deviceName)s (%(deviceId)s)…",
|
||||
|
||||
@ -832,7 +832,6 @@
|
||||
"title": "Nesorina ny fomba fanarenana",
|
||||
"warning": "Raha tsy nesorinao ny fomba fanarenana, dia mety hanandrana hiditra aminy kaontinao ny mpanafika. Hanova ny tenimiafiny kaontinao ary mametraha fomba fanarenana vaovao avy hatrany ao amin'ny fanitsiana."
|
||||
},
|
||||
"reset_all_button": "Adino na very ny fomba fanarenana rehetra?<a> Avereno daholo</a>",
|
||||
"set_up_toast_title": "Hanitsy angona voaharo",
|
||||
"setup_secure_backup": {
|
||||
"explainer": "Amboary ny fanalahidinao alohany hivoahana mba tsy ho very."
|
||||
@ -851,7 +850,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Voamarina ny fitaovana",
|
||||
"skip_verification": "Alefaso ny fanamarinana amin'izao fotoana izao",
|
||||
"unable_to_verify": "Tsy afaka manamarina ity fitaovana ity",
|
||||
"verify_this_device": "Hamarino ity fitaovana ity"
|
||||
},
|
||||
"cancelled": "Nofoananao ny fanamarinana.",
|
||||
@ -871,7 +869,6 @@
|
||||
"incoming_sas_dialog_waiting": "Miandry ny mpiara-miasa hanamarina…",
|
||||
"incoming_sas_user_dialog_text_1": "Hamarino ity mpampiasa ity mba hanamarihana azy ireo ho azo itokisana. Ny fahatokisana ny mpampiasa dia manome anao fiadanan-tsaina fanampiny rehefa mampiasa hafatra voarakotra amin'ny farany.",
|
||||
"incoming_sas_user_dialog_text_2": "Ny fanamarinana ity mpampiasa ity dia hanamarika ny fotoam-pivorian'izy ireo ho atokisana, ary hanamarika ihany koa ny fotoam-pivorianao ho atokisana azy ireo.",
|
||||
"no_key_or_device": "Toa tsy manana fanalahidiny ianao na fitaovana hafa azonao hamarinina. Ity fitaovana ity dia tsy ho afaka miditra aminy hafatra efa misy miafina. Mba hanamarinana ny mombamomba anao amin'ity fitaovana ity dia mila averinao ny fanalahidiny fanamarinanao.",
|
||||
"no_support_qr_emoji": "Ny fitaovana ezahinao hohamarinina dia tsy mahazaka ny scan kaody QR na fanamarinana emoji, izany hoe%(brand)s ny manohana. Andramo miaraka aminy mpanjifa hafa.",
|
||||
"other_party_cancelled": "Nanafoana ny fanamarinana ny ankilany.",
|
||||
"prompt_encrypted": "Hamarino ny mpampiasa rehetra ao amin'ny efitrano iray mba hahazoana antoka fa azo antoka izany.",
|
||||
@ -886,7 +883,6 @@
|
||||
"request_toast_accept": "Hamarino ny fizorana",
|
||||
"request_toast_decline_counter": "Tsy miraharaha (%(counter)s )",
|
||||
"request_toast_detail": "<x id =\"0\"/> avy amin'ny <x id =\"18\"/>",
|
||||
"reset_proceed_prompt": "Ankany amin'ny famerenana ny rehetra aminy laoniny",
|
||||
"sas_caption_self": "Hamarino ity fitaovana ity amin'ny fanamafisana ity isa manaraka ity dia miseho eo aminy efijery.",
|
||||
"sas_caption_user": "Hamarino ity mpampiasa ity aminy fanamafisana ity isa manaraka ity dia miseho eo amin'ny efijery.",
|
||||
"sas_description": "Ampitahao ny andiana emoji tokana raha tsy manana fakan-tsary amin'ny fitaovana roa ianao",
|
||||
@ -909,7 +905,6 @@
|
||||
"unverified_sessions_toast_description": "Avereno jerena mba hahazoana antoka fa azo antoka ny kaontinao",
|
||||
"unverified_sessions_toast_reject": "Taty aoriana",
|
||||
"unverified_sessions_toast_title": "Manana fivoriana tsy voamarina ianao",
|
||||
"verification_description": "Hamarino ny maha-izy anao mba hidirana aminy hafatra miafina ary porofoy amin'ny hafa ny mombamomba anao.",
|
||||
"verification_dialog_title_device": "Hamarino ny fitaovana hafa",
|
||||
"verification_dialog_title_user": "Fangatahana fanamarinana",
|
||||
"verification_skip_warning": "Raha tsy misy fanamarinana dia tsy afaka miditra aminy hafatrao rehetra ianao ary mety hiseho ho tsy atokisana amin'ny hafa.",
|
||||
@ -919,9 +914,6 @@
|
||||
"verify_emoji_prompt": "Hamarino amin'ny fampitahana emoji tokana.",
|
||||
"verify_emoji_prompt_qr": "Raha tsy azonao atao ny mijery ny kaody etsy ambony dia hamarino amin'ny fampitahana emoji tokana.",
|
||||
"verify_later": "Hamariniko avy eo",
|
||||
"verify_using_device": "Hamarino amin'ny fitaovana hafa",
|
||||
"verify_using_key": "Hamarino aminy fanalahidiny voaharo",
|
||||
"verify_using_key_or_phrase": "Hamarino amin'ny fanalahidy na rakin-tsoratra voaharo",
|
||||
"waiting_for_user_accept": "Miandry%(displayName)s manaiky…",
|
||||
"waiting_other_device": "Miandry anao hanamarina amin'ny fitaovanao hafa…",
|
||||
"waiting_other_device_details": "Miandry anao hanamarina amin'ny fitaovanao hafa,%(deviceName)s (%(deviceId)s )…",
|
||||
|
||||
@ -968,7 +968,6 @@
|
||||
"title": "Gjenopprettingsmetode fjernet",
|
||||
"warning": "Hvis det ikke var deg som fjernet gjenopprettingsmetoden, kan det være en hacker som prøver å få tilgang til kontoen din. Bytt passordet for kontoen din og angi en ny gjenopprettingsmetode øyeblikkelig i Innstillinger."
|
||||
},
|
||||
"reset_all_button": "Glemt eller mistet alle gjenopprettingsmetoder?<a> Tilbakestill alt</a>",
|
||||
"set_up_recovery": "Sett opp gjenoppretting",
|
||||
"set_up_recovery_toast_description": "Generer en gjenopprettingsnøkkel som kan brukes til å gjenopprette den krypterte meldingshistorikken i tilfelle du mister tilgangen til enhetene dine.",
|
||||
"set_up_toast_title": "Sett opp sikker sikkerhetskopiering",
|
||||
@ -991,7 +990,6 @@
|
||||
"after_new_login": {
|
||||
"device_verified": "Enhet verifisert",
|
||||
"skip_verification": "Hopp over verifisering for nå",
|
||||
"unable_to_verify": "Kan ikke verifisere denne enheten",
|
||||
"verify_this_device": "Verifiser denne enheten"
|
||||
},
|
||||
"cancelled": "Du avbrøt verifiseringen.",
|
||||
@ -1026,7 +1024,6 @@
|
||||
"text": "Oppgi ID-en og fingeravtrykket til en av dine egne enheter for å verifisere det. MERK at dette lar den andre enheten sende og motta meldinger som deg. HVIS NOEN HAR FORTALT DEG BARE Å LIME INN NOE HER, ER DET SANNSYNLIGVIS AT DU BLIR SVINDLET!",
|
||||
"wrong_fingerprint": "Kan ikke verifisere enheten %(deviceId)s '- det medfølgende fingeravtrykket'%(fingerprint)s «samsvarer ikke med enhetens fingeravtrykk»%(fprint)s '"
|
||||
},
|
||||
"no_key_or_device": "Det ser ut til at du ikke har en gjenopprettingsnøkkel eller andre enheter du kan verifisere mot. Denne enheten vil ikke kunne få tilgang til gamle krypterte meldinger. For å bekrefte identiteten din på denne enheten, må du tilbakestille verifiseringsnøklene dine.",
|
||||
"no_support_qr_emoji": "Enheten du prøver å bekrefte støtter ikke skanning av en QR-kode eller emoji-verifikasjon, som er det som %(brand)s støtter. Prøv med en annen klient.",
|
||||
"other_party_cancelled": "Den andre parten kansellerte verifiseringen.",
|
||||
"prompt_encrypted": "Bekreft alle brukere i et rom for å sikre at det er sikkert.",
|
||||
@ -1042,7 +1039,6 @@
|
||||
"request_toast_accept_user": "Verifiser bruker",
|
||||
"request_toast_decline_counter": "Ignorer (%(counter)s)",
|
||||
"request_toast_detail": "%(deviceId)s fra %(ip)s",
|
||||
"reset_proceed_prompt": "Fortsett med tilbakestilling",
|
||||
"sas_caption_self": "Bekreft denne enheten ved å bekrefte at følgende nummer vises på skjermen.",
|
||||
"sas_caption_user": "Bekreft denne brukeren ved å bekrefte at følgende nummer vises på skjermen.",
|
||||
"sas_description": "Sammenlign et unikt sett med emojier hvis du ikke har et kamera på noen av enhetene",
|
||||
@ -1065,7 +1061,6 @@
|
||||
"unverified_sessions_toast_description": "Se gjennom for å sikre at kontoen din er trygg",
|
||||
"unverified_sessions_toast_reject": "Senere",
|
||||
"unverified_sessions_toast_title": "Du har ubekreftede økter",
|
||||
"verification_description": "Bekreft identiteten din for å få tilgang til krypterte meldinger og bevise identiteten din for andre.",
|
||||
"verification_dialog_title_device": "Bekreft annen enhet",
|
||||
"verification_dialog_title_user": "Verifiseringsforespørsel",
|
||||
"verification_skip_warning": "Uten verifisering vil du ikke ha tilgang til alle meldingene dine, og du kan fremstå som upålitelig for andre.",
|
||||
@ -1075,9 +1070,6 @@
|
||||
"verify_emoji_prompt": "Bekreft ved å sammenligne unike emoji.",
|
||||
"verify_emoji_prompt_qr": "Hvis du ikke kan skanne koden ovenfor, bekreft ved å sammenligne unike emoji.",
|
||||
"verify_later": "Jeg bekrefter senere",
|
||||
"verify_using_device": "Bekreft med en annen enhet",
|
||||
"verify_using_key": "Bekreft med gjenopprettingsnøkkel",
|
||||
"verify_using_key_or_phrase": "Bekreft med gjenopprettingsnøkkel eller -frase",
|
||||
"waiting_for_user_accept": "Venter på at %(displayName)s skal akseptere …",
|
||||
"waiting_other_device": "Venter på at du skal bekrefte på den andre enheten din...",
|
||||
"waiting_other_device_details": "Venter på at du skal bekrefte på den andre enheten din, %(deviceName)s (%(deviceId)s)...",
|
||||
@ -2173,6 +2165,18 @@
|
||||
"one": "Fjerner for øyeblikket meldinger i %(count)s rom",
|
||||
"other": "Fjerner for øyeblikket meldinger i %(count)s rom"
|
||||
},
|
||||
"release_announcement": {
|
||||
"done": "Ferdig",
|
||||
"filter": {
|
||||
"description": "Filtrer chattene dine med ett enkelt klikk. Utvid for å se flere filtre.",
|
||||
"title": "Nye hurtigfiltre"
|
||||
},
|
||||
"intro": {
|
||||
"description": "Chat-listen er oppdatert for å være mer oversiktlig og enkel å bruke.",
|
||||
"title": "Chatten har fått et nytt utseende!"
|
||||
},
|
||||
"next": "Neste"
|
||||
},
|
||||
"room": {
|
||||
"more_options": "Flere alternativer",
|
||||
"open_room": "Åpne rom %(roomName)s"
|
||||
|
||||