diff --git a/.github/renovate.json b/.github/renovate.json index ded20f15d7..9bc8cd62b3 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -5,8 +5,8 @@ "packageRules": [ { "groupName": "testcontainers docker digests", - "groupSlug": "{{manager}}-docker-digests", - "matchManagers": ["custom.regex"], + "groupSlug": "testcontainers-docker", + "matchDepTypes": ["testcontainers-docker"], "matchPackageNames": ["*"] } ], @@ -17,7 +17,16 @@ "versioningTemplate": "loose", "description": "Update testcontainers docker digests", "managerFilePatterns": ["**/testcontainers/*.ts"], - "matchStrings": ["\\s+\"(?[^@]+):(?[^@]+)@(?sha256:[a-f0-9]+)\""] + "matchStrings": ["\\s+\"(?[^@]+):(?[^@]+)@(?sha256:[a-f0-9]+)\""], + "depTypeTemplate": "testcontainers-docker" + }, + { + "customType": "jsonata", + "managerFilePatterns": ["/(^|/)package\\.json$/"], + "fileFormat": "json", + "matchStrings": ["hakDependencies.$each(function($v, $k) { { 'packageName': $k, 'currentValue': $v } })"], + "datasourceTemplate": "npm", + "depTypeTemplate": "hak" } ] } diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index c5cf85727d..eb469502d9 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -54,6 +54,8 @@ jobs: outputs: num-runners: ${{ env.NUM_RUNNERS }} runners-matrix: ${{ steps.runner-vars.outputs.matrix }} + # Skip pull_request runs on renovate PRs to speed up CI time, delegating to the full run in merge queue + skip: ${{ inputs.skip || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -102,7 +104,7 @@ jobs: playwright_ew: name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build_ew.outputs.num-runners }}" needs: build_ew - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' runs-on: ubuntu-24.04 permissions: actions: read @@ -182,7 +184,7 @@ jobs: downstream-modules: name: Downstream Playwright tests [element-modules] needs: build_ew - if: inputs.skip != true && github.event_name == 'merge_group' + if: needs.build_ew.outputs.skip == 'false' && github.event_name == 'merge_group' uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main # zizmor: ignore[unpinned-uses] with: webapp-artifact: webapp @@ -192,7 +194,7 @@ jobs: name: "Prepare Element Desktop" uses: ./.github/workflows/build_desktop_prepare.yaml needs: build_ew - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' permissions: contents: read with: @@ -204,7 +206,6 @@ jobs: needs: prepare_ed name: "Desktop Windows" uses: ./.github/workflows/build_desktop_windows.yaml - if: inputs.skip != true strategy: matrix: arch: [x64, ia32, arm64] @@ -216,7 +217,6 @@ jobs: needs: prepare_ed name: "Desktop Linux" uses: ./.github/workflows/build_desktop_linux.yaml - if: inputs.skip != true strategy: matrix: sqlcipher: [system, static] @@ -236,13 +236,13 @@ jobs: needs: prepare_ed name: "Desktop macOS" uses: ./.github/workflows/build_desktop_macos.yaml - if: inputs.skip != true with: blob_report: true complete: name: end-to-end-tests needs: + - build_ew - playwright_ew - downstream-modules - prepare_ed @@ -253,25 +253,25 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' with: persist-credentials: false repository: element-hq/element-web - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' with: cache: "pnpm" node-version: "lts/*" - name: Install dependencies - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' run: pnpm install --frozen-lockfile - name: Download blob reports from GitHub Actions Artifacts - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: blob-report-* @@ -279,7 +279,7 @@ jobs: merge-multiple: true - name: Merge into HTML Report - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' run: | pnpm playwright merge-reports \ --config=playwright-merge.config.ts \ @@ -291,7 +291,7 @@ jobs: # Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected - name: Upload HTML report - if: always() && inputs.skip != true + if: always() && needs.build_ew.outputs.skip == 'false' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: html-report diff --git a/.github/workflows/build_desktop_linux.yaml b/.github/workflows/build_desktop_linux.yaml index 4ddd2624ad..767fa06c10 100644 --- a/.github/workflows/build_desktop_linux.yaml +++ b/.github/workflows/build_desktop_linux.yaml @@ -133,7 +133,7 @@ jobs: # This allows contributors to test changes to the dockerbuild image within a pull request - name: Build docker image - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 if: steps.changed_files.outputs.any_modified == 'true' with: file: apps/desktop/dockerbuild/Dockerfile diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml index 1d0c78c7ab..a4c9873a27 100644 --- a/.github/workflows/build_develop.yml +++ b/.github/workflows/build_develop.yml @@ -111,7 +111,7 @@ jobs: running-workflow-name: "Build & Deploy develop.element.io" repo-token: ${{ secrets.GITHUB_TOKEN }} wait-interval: 10 - check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify).)*$ + check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify|Report).)*$ # We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier # as the expires after 24h and requires auth to download. diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index e00296abf1..137ab69e17 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -50,7 +50,7 @@ jobs: run: "pnpm install --frozen-lockfile" - name: Login to GitHub Container Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index e276754640..c33629c2cd 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -39,7 +39,7 @@ jobs: - name: Build and load id: test-build - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: . file: apps/web/Dockerfile @@ -97,14 +97,14 @@ jobs: latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }} - name: Login to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 if: github.event_name != 'pull_request' with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 if: github.event_name != 'pull_request' with: registry: ghcr.io @@ -140,7 +140,7 @@ jobs: services/web-repositories/secret/data/oci.element.io password | OCI_PASSWORD ; - name: Login to oci.element.io Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 if: github.event_name != 'pull_request' with: registry: oci-push.vpn.infra.element.io @@ -149,7 +149,7 @@ jobs: - name: Build and push id: build-and-push - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 if: github.event_name != 'pull_request' with: context: . diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bf24169461..0092b61f2c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: run: pnpm run docs:build - name: Upload artifact - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: ./docs/.vitepress/dist diff --git a/.github/workflows/shared-component-storybook-publish.yaml b/.github/workflows/shared-component-storybook-publish.yaml index 6193c5af74..1a365c6b6b 100644 --- a/.github/workflows/shared-component-storybook-publish.yaml +++ b/.github/workflows/shared-component-storybook-publish.yaml @@ -26,7 +26,7 @@ jobs: path: storybook-static - name: 🚀 Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3 + uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3 with: apiToken: ${{ secrets.CF_PAGES_TOKEN }} accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }} diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index 3dd7da0e39..51d7926b83 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -87,7 +87,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 i18n: strategy: @@ -125,7 +125,9 @@ jobs: # Dummy job to simplify branch protections ci: name: Static Analysis - needs: [lint, i18n] + needs: [lint, i18n, zizmor] + if: always() runs-on: ubuntu-24.04 steps: - - run: echo "Ok" + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index eb63cd55ee..9b58661185 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -105,7 +105,7 @@ "typescript": "5.9.3" }, "hakDependencies": { - "matrix-seshat": "4.0.1" + "matrix-seshat": "4.2.0" }, "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" } diff --git a/apps/desktop/src/i18n/strings/zh_Hans.json b/apps/desktop/src/i18n/strings/zh_Hans.json index 448412456d..077a5d036d 100644 --- a/apps/desktop/src/i18n/strings/zh_Hans.json +++ b/apps/desktop/src/i18n/strings/zh_Hans.json @@ -22,7 +22,9 @@ "about": "关于", "brand_help": "%(brand)s帮助", "help": "帮助", - "preferences": "偏好" + "no": "不", + "preferences": "偏好", + "yes": "是" }, "confirm_quit": "你确定要退出吗?", "edit_menu": { @@ -30,9 +32,20 @@ "speech_start_speaking": "开始讲话", "speech_stop_speaking": "停止讲话" }, + "eol": { + "no_more_updates": "您正在使用不受支持的macOS版本。请升级以获取%(brand)s 更新。", + "title": "系统不支持", + "warning": "您正在使用不受支持的macOS版本。请升级系统以确保%(brand)s 能持续正常运行。" + }, "file_menu": { "label": "文件" }, + "icon_overlay": { + "description_error": "错误", + "description_notifications": { + "other": "您有%(count)s 条未读通知。" + } + }, "menu": { "hide": "隐藏", "hide_others": "隐藏其他", @@ -49,6 +62,21 @@ "save_image_as_error_description": "图片保存失败", "save_image_as_error_title": "图片保存失败" }, + "store": { + "error": { + "backend_changed": "清除数据并重新加载?", + "backend_changed_detail": "无法从系统密钥环访问密钥,该密钥似乎已被更改。", + "backend_changed_title": "数据库加载失败", + "backend_no_encryption": "您的系统支持密钥环,但加密功能不可用。", + "backend_no_encryption_detail": "Electron检测到您的密钥环%(backend)s 不支持加密功能。请确保已安装该密钥环。若已安装,请重启设备后重试。您也可选择允许%(brand)s 使用较弱的加密方式。", + "backend_no_encryption_title": "不支持加密", + "unsupported_keyring": "您的系统存在未受支持的密钥环,这意味着无法打开数据库。", + "unsupported_keyring_detail": "Electron的密钥环检测未找到受支持的后端。您可以尝试通过命令行参数启动%(brand)s 来手动配置后端,此操作仅需执行一次。详情请参阅:%(link)s 。", + "unsupported_keyring_title": "系统不支持", + "unsupported_keyring_use_basic_text": "使用较弱的加密", + "unsupported_keyring_use_plaintext": "不使用加密" + } + }, "view_menu": { "actual_size": "实际大小", "toggle_developer_tools": "切换开发者工具", diff --git a/apps/web/package.json b/apps/web/package.json index d724f50fd4..63cbb7d742 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -101,7 +101,7 @@ "react-transition-group": "^4.4.1", "rfc4648": "^1.4.0", "sanitize-filename": "^1.6.3", - "sanitize-html": "2.17.2", + "sanitize-html": "2.17.3", "tar-js": "^0.3.0", "ua-parser-js": "1.0.40", "uuid": "^13.0.0", diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index f6d15f4e79..6120557148 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -91,7 +91,7 @@ export default defineConfig<{}, WorkerOptions>({ trace: "on-first-retry", }, webServer: { - command: process.env.CI ? "npx serve -p 8080 -L ./webapp" : "pnpm start", + command: process.env.CI ? "npx serve -p 8080 -L ./webapp" : "nx --outputStyle stream start", url: `${baseURL}/config.json`, reuseExistingServer: true, timeout: (process.env.CI ? 30 : 120) * 1000, diff --git a/apps/web/playwright/e2e/crypto/device-verification.spec.ts b/apps/web/playwright/e2e/crypto/device-verification.spec.ts index 1a1731e6ae..07fa4ed9d8 100644 --- a/apps/web/playwright/e2e/crypto/device-verification.spec.ts +++ b/apps/web/playwright/e2e/crypto/device-verification.spec.ts @@ -21,7 +21,6 @@ import { waitForVerificationRequest, } from "./utils"; import { type Bot } from "../../pages/bot"; -import { Toasts } from "../../pages/toasts.ts"; import type { ElementAppPage } from "../../pages/ElementAppPage.ts"; test.describe("Device verification", { tag: "@no-webkit" }, () => { @@ -82,7 +81,11 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { ); // Regression test for https://github.com/element-hq/element-web/issues/29110 - test("No toast after verification, even if the secrets take a while to arrive", async ({ page, credentials }) => { + test("No toast after verification, even if the secrets take a while to arrive", async ({ + page, + credentials, + toasts, + }) => { // Before we log in, the bot creates an encrypted room, so that we can test the toast behaviour that only happens // when we are in an encrypted room. await aliceBotClient.createRoom({ @@ -121,7 +124,6 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { await infoDialog.getByRole("button", { name: "Got it" }).click(); // There should be no toast (other than the notifications one) - const toasts = new Toasts(page); await toasts.rejectToast("Notifications"); await toasts.assertNoToasts(); diff --git a/apps/web/playwright/element-web-test.ts b/apps/web/playwright/element-web-test.ts index f7c6f5b8e2..cae16cce8d 100644 --- a/apps/web/playwright/element-web-test.ts +++ b/apps/web/playwright/element-web-test.ts @@ -24,7 +24,6 @@ import type { IConfigOptions } from "../src/IConfigOptions"; import { type Credentials } from "./plugins/homeserver"; import { ElementAppPage } from "./pages/ElementAppPage"; import { Crypto } from "./pages/crypto"; -import { Toasts } from "./pages/toasts"; import { Bot, type CreateBotOpts } from "./pages/bot"; import { Webserver } from "./plugins/webserver"; import { type WorkerOptions, type Services, test as base } from "./services"; @@ -52,7 +51,6 @@ export interface TestFixtures extends BaseTestFixtures { crypto: Crypto; room?: { roomId: string }; - toasts: Toasts; uut?: Locator; // Unit Under Test, useful place to refer a prepared locator botCreateOpts: CreateBotOpts; bot: Bot; @@ -92,9 +90,6 @@ export const test = base.extend({ crypto: async ({ page, homeserver, request }, use) => { await use(new Crypto(page, homeserver, request)); }, - toasts: async ({ page }, use) => { - await use(new Toasts(page)); - }, botCreateOpts: {}, bot: async ({ page, homeserver, botCreateOpts, user }, use, testInfo) => { diff --git a/apps/web/playwright/pages/toasts.ts b/apps/web/playwright/pages/toasts.ts deleted file mode 100644 index 80ee3c9f26..0000000000 --- a/apps/web/playwright/pages/toasts.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -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 Page, expect, type Locator } from "@playwright/test"; - -export class Toasts { - public constructor(private readonly page: Page) {} - - /** - * Assert that a toast with the given title exists, and return it - * - * @param expectedTitle - Expected title of the toast - * @param timeout Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. - * @returns the Locator for the matching toast - */ - public async getToast(expectedTitle: string, timeout?: number): Promise { - const toast = this.page.locator(".mx_Toast_toast", { hasText: expectedTitle }).first(); - await expect(toast).toBeVisible({ timeout }); - return toast; - } - - /** - * Assert that no toasts exist - */ - public async assertNoToasts(): Promise { - await expect(this.page.locator(".mx_Toast_toast")).not.toBeVisible(); - } - - /** - * Accept a toast with the given title, only works for the first toast in the stack - * - * @param expectedTitle - Expected title of the toast - */ - public async acceptToast(expectedTitle: string): Promise { - const toast = await this.getToast(expectedTitle); - await toast.locator('.mx_Toast_buttons button[data-kind="primary"]').click(); - } - - /** - * Reject a toast with the given title, only works for the first toast in the stack - * - * @param expectedTitle - Expected title of the toast - */ - public async rejectToast(expectedTitle: string): Promise { - const toast = await this.getToast(expectedTitle); - await toast.locator('.mx_Toast_buttons button[data-kind="secondary"]').click(); - } -} diff --git a/apps/web/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png b/apps/web/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png index f764311250..771157a11a 100644 Binary files a/apps/web/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png and b/apps/web/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index d651340ace..77345fe6fe 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png index 98de99bf98..15da4c7c02 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png b/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png index 48625def3a..e5b81c1973 100644 Binary files a/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png and b/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png index 8487b5a381..79a9efe5cf 100644 Binary files a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png and b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png index 21198098a4..efec129b55 100644 Binary files a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png and b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png index ee058b786a..2519cee3ef 100644 Binary files a/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png and b/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png b/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png index 5248968436..c12d1a8808 100644 Binary files a/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png and b/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png index a1d376d62f..1551195fd5 100644 Binary files a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png and b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png differ diff --git a/apps/web/res/css/_common.pcss b/apps/web/res/css/_common.pcss index 4cadbe71c6..f3a9fdcb94 100644 --- a/apps/web/res/css/_common.pcss +++ b/apps/web/res/css/_common.pcss @@ -834,18 +834,6 @@ legend { } } -@define-mixin ButtonResetDefault { - appearance: none; - background: none; - border: none; - padding: 0; - margin: 0; - font-size: inherit; - font-family: inherit; - line-height: inherit; - cursor: pointer; -} - @define-mixin LegacyCallButton { box-sizing: border-box; font-weight: var(--cpd-font-weight-semibold); diff --git a/apps/web/res/css/_components.pcss b/apps/web/res/css/_components.pcss index bdca70276d..fdf774a754 100644 --- a/apps/web/res/css/_components.pcss +++ b/apps/web/res/css/_components.pcss @@ -68,7 +68,6 @@ @import "./structures/_LeftPanel.pcss"; @import "./structures/_MainSplit.pcss"; @import "./structures/_MatrixChat.pcss"; -@import "./structures/_MessagePanel.pcss"; @import "./structures/_NonUrgentToastContainer.pcss"; @import "./structures/_PictureInPictureDragger.pcss"; @import "./structures/_QuickSettingsButton.pcss"; diff --git a/apps/web/res/css/components/views/location/_ShareDialogButtons.pcss b/apps/web/res/css/components/views/location/_ShareDialogButtons.pcss index d0c419accb..1c1d379f89 100644 --- a/apps/web/res/css/components/views/location/_ShareDialogButtons.pcss +++ b/apps/web/res/css/components/views/location/_ShareDialogButtons.pcss @@ -13,8 +13,7 @@ Please see LICENSE files in the repository root for full details. top: 0; } -.mx_ShareDialogButtons_button { - @mixin ButtonResetDefault; +button.mx_ShareDialogButtons_button { height: 24px; width: 24px; border-radius: 50%; diff --git a/apps/web/res/css/structures/_MessagePanel.pcss b/apps/web/res/css/structures/_MessagePanel.pcss deleted file mode 100644 index fb2830bce7..0000000000 --- a/apps/web/res/css/structures/_MessagePanel.pcss +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -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. -*/ - -.mx_MessagePanel_myReadMarker { - height: 0; - margin: 0; - padding: 0; - border: 0; - - hr { - border-top: solid 1px $accent; - border-bottom: solid 1px $accent; - margin-top: 0; - position: relative; - top: -1px; - z-index: 1; - will-change: width; - transition: - width 400ms easeinsine 1s, - opacity 400ms easeinsine 1s; - width: 99%; - opacity: 1; - } -} diff --git a/apps/web/res/css/views/elements/_AccessibleButton.pcss b/apps/web/res/css/views/elements/_AccessibleButton.pcss index fafe75c642..e79a858d49 100644 --- a/apps/web/res/css/views/elements/_AccessibleButton.pcss +++ b/apps/web/res/css/views/elements/_AccessibleButton.pcss @@ -9,6 +9,19 @@ Please see LICENSE files in the repository root for full details. .mx_AccessibleButton { cursor: pointer; + &:where(button) { + /* Clear default button styling */ + appearance: none; + background: none; + border: none; + padding: 0; + margin: 0; + font-size: inherit; + font-family: inherit; + line-height: inherit; + box-sizing: content-box; + } + &.mx_AccessibleButton_disabled { cursor: not-allowed; diff --git a/apps/web/res/css/views/elements/_CopyableText.pcss b/apps/web/res/css/views/elements/_CopyableText.pcss index 6219055085..e5cf1c51da 100644 --- a/apps/web/res/css/views/elements/_CopyableText.pcss +++ b/apps/web/res/css/views/elements/_CopyableText.pcss @@ -28,7 +28,6 @@ Please see LICENSE files in the repository root for full details. /* using em here to adapt to the local font size */ width: 1em; height: 1em; - cursor: pointer; padding-left: 12px; padding-right: 10px; display: block; diff --git a/apps/web/res/css/views/settings/_NotificationSettings2.pcss b/apps/web/res/css/views/settings/_NotificationSettings2.pcss index db439f0706..8a41322615 100644 --- a/apps/web/res/css/views/settings/_NotificationSettings2.pcss +++ b/apps/web/res/css/views/settings/_NotificationSettings2.pcss @@ -12,12 +12,6 @@ Please see LICENSE files in the repository root for full details. gap: 32px; display: flex; flex-direction: column; - - > form { - gap: 32px; - display: flex; - flex-direction: column; - } } .mx_SettingsSubsection_description { diff --git a/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss b/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss index 92a392950f..c92e454e05 100644 --- a/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss +++ b/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss @@ -14,12 +14,13 @@ Please see LICENSE files in the repository root for full details. color: $links; } - form:not(.mx_EncryptionUserSettingsTab form) { + form { display: flex; flex-direction: column; - gap: $spacing-8; + gap: var(--cpd-space-3x); flex-grow: 1; } + // never want full width buttons // event when other content is 100% width .mx_AccessibleButton { diff --git a/apps/web/src/Lifecycle.ts b/apps/web/src/Lifecycle.ts index 78c6cc2de6..32d113adc8 100644 --- a/apps/web/src/Lifecycle.ts +++ b/apps/web/src/Lifecycle.ts @@ -18,7 +18,6 @@ import { decodeBase64, } from "matrix-js-sdk/src/matrix"; import { type AESEncryptedSecretStoragePayload } from "matrix-js-sdk/src/types"; -import { type QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; import { type IMatrixClientCreds, MatrixClientPeg, type MatrixClientPegAssignOpts } from "./MatrixClientPeg"; @@ -81,6 +80,7 @@ import { } from "./utils/tokens/tokens"; import { TokenRefresher } from "./utils/oidc/TokenRefresher"; import { checkBrowserSupport } from "./SupportedBrowser"; +import { type URLParams } from "./vector/url_utils.ts"; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; @@ -148,7 +148,7 @@ interface ILoadSessionOpts { guestIsUrl?: string; ignoreGuest?: boolean; defaultDeviceDisplayName?: string; - fragmentQueryParams?: QueryDict; + urlParams?: URLParams; abortSignal?: AbortSignal; } @@ -187,7 +187,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise let enableGuest = opts.enableGuest || false; const guestHsUrl = opts.guestHsUrl; const guestIsUrl = opts.guestIsUrl; - const fragmentQueryParams = opts.fragmentQueryParams || {}; + const urlParams = opts.urlParams; const defaultDeviceDisplayName = opts.defaultDeviceDisplayName; if (enableGuest && !guestHsUrl) { @@ -195,12 +195,12 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise enableGuest = false; } - if (enableGuest && guestHsUrl && fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_access_token) { + if (enableGuest && guestHsUrl && urlParams?.guest?.guest_user_id && urlParams?.guest?.guest_access_token) { logger.log("Using guest access credentials"); await doSetLoggedIn( { - userId: fragmentQueryParams.guest_user_id as string, - accessToken: fragmentQueryParams.guest_access_token as string, + userId: urlParams.guest.guest_user_id, + accessToken: urlParams.guest.guest_access_token, homeserverUrl: guestHsUrl, identityServerUrl: guestIsUrl, guest: true, @@ -264,38 +264,43 @@ export async function getStoredSessionOwner(): Promise<[string, boolean] | [null * If query string includes OIDC authorization code flow parameters attempt to login using oidc flow * Else, we may be returning from SSO - attempt token login * - * @param {Object} queryParams string->string map of the - * query-parameters extracted from the real query-string of the starting - * URI. + * @param urlParams the parameters read in at app load time from the url * - * @param {string} defaultDeviceDisplayName - * @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again" + * @param defaultDeviceDisplayName + * @param fragmentAfterLogin path to go to after a successful login, only used for "Try again" * - * @returns {Promise} promise which resolves to true if we completed the delegated auth login + * @returns promise which resolves to true if we completed the delegated auth login * else false */ export async function attemptDelegatedAuthLogin( - queryParams: QueryDict, + urlParams: URLParams, defaultDeviceDisplayName?: string, fragmentAfterLogin?: string, ): Promise { - if (queryParams.code && queryParams.state) { - console.log("We have OIDC params - attempting OIDC login"); - return attemptOidcNativeLogin(queryParams); + if (urlParams.oidc_fragment) { + return attemptOidcNativeLogin(urlParams.oidc_fragment, "fragment"); + } else if (urlParams.oidc_query) { + return attemptOidcNativeLogin(urlParams.oidc_query, "query"); } - return attemptTokenLogin(queryParams, defaultDeviceDisplayName, fragmentAfterLogin); + return attemptTokenLogin(urlParams["legacy_sso"], defaultDeviceDisplayName, fragmentAfterLogin); } /** * Attempt to login by completing OIDC authorization code flow - * @param queryParams string->string map of the query-parameters extracted from the real query-string of the starting URI. - * @returns Promise that resolves to true when login succceeded, else false + * @param urlParams subset of app-load url parameters relating to oidc auth + * @param responseMode - the response_mode used in the auth request + * @returns Promise that resolves to true when login succeeded, else false */ -async function attemptOidcNativeLogin(queryParams: QueryDict): Promise { +async function attemptOidcNativeLogin( + urlParams: NonNullable, + responseMode: "fragment" | "query", +): Promise { + console.log("We have OIDC params - attempting OIDC login"); + try { const { accessToken, refreshToken, homeserverUrl, identityServerUrl, idToken, clientId, issuer } = - await completeOidcLogin(queryParams); + await completeOidcLogin(urlParams, responseMode); const { user_id: userId, @@ -354,22 +359,20 @@ async function getUserIdFromAccessToken( } /** - * @param {QueryDict} queryParams string->string map of the - * query-parameters extracted from the real query-string of the starting - * URI. + @param urlParams subset of app-load url parameters relating to legacy sso auth * - * @param {string} defaultDeviceDisplayName - * @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again" + * @param defaultDeviceDisplayName + * @param fragmentAfterLogin path to go to after a successful login, only used for "Try again" * - * @returns {Promise} promise which resolves to true if we completed the token + * @returns promise which resolves to true if we completed the token * login, else false */ export function attemptTokenLogin( - queryParams: QueryDict, + urlParams: URLParams["legacy_sso"], defaultDeviceDisplayName?: string, fragmentAfterLogin?: string, ): Promise { - if (!queryParams.loginToken) { + if (!urlParams?.loginToken) { return Promise.resolve(false); } @@ -384,7 +387,7 @@ export function attemptTokenLogin( } return sendLoginRequest(homeserver, identityServer, "m.login.token", { - token: queryParams.loginToken as string, + token: urlParams.loginToken, initial_device_display_name: defaultDeviceDisplayName, }) .then(async function (creds) { @@ -1040,7 +1043,7 @@ export function isLoggingOut(): boolean { * By the time this method is called, we have successfully logged in if necessary, and the client has been set up with * the access token. * - * Emits {@link Acction.WillStartClient} before starting the client, and {@link Action.ClientStarted} when the client has + * Emits {@link Action.WillStartClient} before starting the client, and {@link Action.ClientStarted} when the client has * been started. * * @param client the matrix client to start diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index ce5bfd4598..14e726e532 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -20,7 +20,6 @@ import { type SyncStateData, type TimelineEvents, } from "matrix-js-sdk/src/matrix"; -import { type QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; import { throttle } from "lodash"; import { CryptoEvent, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; @@ -141,6 +140,8 @@ import Markdown from "../../Markdown"; import { LinkedTextConfiguration, sanitizeHtmlParams } from "../../Linkify"; import { isOnlyAdmin } from "../../utils/membership"; import { ModuleApi } from "../../modules/Api.ts"; +import { type IScreen } from "../../vector/routing.ts"; +import { type URLParams } from "../../vector/url_utils.ts"; // legacy export export { default as Views } from "../../Views"; @@ -152,21 +153,14 @@ const AUTH_SCREENS = ["register", "mobile_register", "login", "forgot_password", // re-factoring to be included in this list in future. const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, Action.CreateChat, Action.CreateRoom]; -interface IScreen { - screen: string; - params?: QueryDict; -} - interface IProps { config: ConfigOptions; onNewScreen: (screen: string, replaceLast: boolean) => void; enableGuest?: boolean; - // the queryParams extracted from the [real] query-string of the URI - realQueryParams: QueryDict; - // the initial queryParams extracted from the hash-fragment of the URI - startingFragmentQueryParams?: QueryDict; + // the params extracted from the [real] query-string & fragment of the URI + urlParams: URLParams; // called when we have completed a token login - onTokenLoginCompleted: () => void; + onTokenLoginCompleted: (urlParams: URLParams, fragmentAfterLogin: string) => void; // Represents the screen to display as a result of parsing the initial window.location initialScreenAfterLogin?: IScreen; // displayname, if any, to set on the device when logging in/registering. @@ -227,11 +221,8 @@ interface IState { export default class MatrixChat extends React.PureComponent { public static displayName = "MatrixChat"; - public static defaultProps = { - realQueryParams: {}, - startingFragmentQueryParams: {}, + public static defaultProps: Partial = { config: {}, - onTokenLoginCompleted: (): void => {}, }; private firstSyncComplete = false; @@ -353,18 +344,18 @@ export default class MatrixChat extends React.PureComponent { // Otherwise, the first thing to do is to try the token params in the query-string const delegatedAuthSucceeded = await Lifecycle.attemptDelegatedAuthLogin( - this.props.realQueryParams, + this.props.urlParams, this.props.defaultDeviceDisplayName, this.getFragmentAfterLogin(), ); // remove the loginToken or auth code from the URL regardless if ( - this.props.realQueryParams?.loginToken || - this.props.realQueryParams?.code || - this.props.realQueryParams?.state + !!this.props.urlParams.legacy_sso || + !!this.props.urlParams.oidc_fragment || + !!this.props.urlParams.oidc_query ) { - this.props.onTokenLoginCompleted(); + this.props.onTokenLoginCompleted(this.props.urlParams, this.getFragmentAfterLogin()); } if (delegatedAuthSucceeded) { @@ -421,7 +412,7 @@ export default class MatrixChat extends React.PureComponent { * {@link onWillStartClient} and {@link onClientStarted} will already have been called (but not necessarily * completed). * - * This method either calls {@link onLiggedIn} directly, or switches to {@link Views.E2E_SETUP} or + * This method either calls {@link onLoggedIn} directly, or switches to {@link Views.E2E_SETUP} or * {@link Views.COMPLETE_SECURITY}, which will later call {@link onCompleteSecurityE2eSetupFinished}. */ private async postLoginSetup(): Promise { @@ -592,7 +583,7 @@ export default class MatrixChat extends React.PureComponent { return Promise.resolve() .then(() => { return Lifecycle.loadSession({ - fragmentQueryParams: this.props.startingFragmentQueryParams, + urlParams: this.props.urlParams, enableGuest: this.props.enableGuest, guestHsUrl: this.getServerProperties().serverConfig.hsUrl, guestIsUrl: this.getServerProperties().serverConfig.isUrl, @@ -1835,7 +1826,7 @@ export default class MatrixChat extends React.PureComponent { } } - public showScreen(screen: string, params?: { [key: string]: any }): void { + public showScreen(screen: string, params?: Record): void { logger.debug(`showScreen ${screen}`); const cli = MatrixClientPeg.get(); @@ -2267,14 +2258,14 @@ export default class MatrixChat extends React.PureComponent { onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined} onServerConfigChange={this.onServerConfigChange} fragmentAfterLogin={fragmentAfterLogin} - defaultUsername={this.props.startingFragmentQueryParams?.defaultUsername as string | undefined} + defaultUsername={this.props.urlParams?.defaults?.defaultUsername} {...this.getServerProperties()} /> ); } else if (this.state.view === Views.SOFT_LOGOUT) { view = ( diff --git a/apps/web/src/components/structures/MessagePanel.tsx b/apps/web/src/components/structures/MessagePanel.tsx index 629b9d3c7a..a38f264aab 100644 --- a/apps/web/src/components/structures/MessagePanel.tsx +++ b/apps/web/src/components/structures/MessagePanel.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, createRef, type ReactNode, type TransitionEvent } from "react"; +import React, { type JSX, createRef, type ReactNode, type TransitionEventHandler } from "react"; import classNames from "classnames"; import { type Room, @@ -20,6 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { isSupportedReceiptType } from "matrix-js-sdk/src/utils"; import { DateSeparatorView, + ReadMarker, TimelineSeparator, useCreateAutoDisposedViewModel, } from "@element-hq/web-shared-components"; @@ -271,7 +272,7 @@ export default class MessagePanel extends React.Component { private readonly _showHiddenEvents: boolean; private unmounted = false; - private readMarkerNode = createRef(); + private readMarkerNode: HTMLLIElement | null = null; private whoIsTyping = createRef(); public scrollPanel = createRef(); @@ -403,7 +404,7 @@ export default class MessagePanel extends React.Component { // 0: read marker is within the window // +1: read marker is below the window public getReadMarkerPosition(): number | null { - const readMarker = this.readMarkerNode.current; + const readMarker = this.readMarkerNode; const messageWrapper = this.scrollPanel.current?.divScroll; if (!readMarker || !messageWrapper) { @@ -507,29 +508,18 @@ export default class MessagePanel extends React.Component { public readMarkerForEvent(eventId: string, isLastEvent: boolean): ReactNode { if (this.context.timelineRenderingType === TimelineRenderingType.File) return null; - const visible = !isLastEvent && this.props.readMarkerVisible; + const showLine = !isLastEvent && !!this.props.readMarkerVisible; if (this.props.readMarkerEventId === eventId) { - let hr; - // if the read marker comes at the end of the timeline (except - // for local echoes, which are excluded from RMs, because they - // don't have useful event ids), we don't want to show it, but - // we still want to create the
  • for it so that the - // algorithms which depend on its position on the screen aren't - // confused. - if (visible) { - hr =
    ; - } - return ( -
  • - {hr} -
  • + /> ); } else if (this.state.ghostReadMarkers.includes(eventId)) { // We render 'ghost' read markers in the DOM while they @@ -542,28 +532,30 @@ export default class MessagePanel extends React.Component { // case is a little more complex because only some of the items // transition (ie. the read markers do but the event tiles do not) // and TransitionGroup requires that all its children are Transitions. - const hr = ( -
    - ); - // give it a key which depends on the event id. That will ensure that // we get a new DOM node (restarting the animation) when the ghost // moves to a different event. return ( -
  • - {hr} -
  • + ); } return null; } - private collectGhostReadMarker = (node: HTMLElement | null): void => { + private collectReadMarker = (node: HTMLLIElement | null): void => { + this.readMarkerNode = node; + }; + + private collectGhostReadMarker = (node: HTMLHRElement | null): void => { if (node) { // now the element has appeared, change the style which will trigger the CSS transition requestAnimationFrame(() => { @@ -573,7 +565,7 @@ export default class MessagePanel extends React.Component { } }; - private onGhostTransitionEnd = (ev: TransitionEvent): void => { + private onGhostTransitionEnd: TransitionEventHandler = (ev): void => { // we can now clean up the ghost element const finishedEventId = (ev.target as HTMLElement).dataset.eventid; this.setState({ diff --git a/apps/web/src/components/structures/SpaceHierarchy.tsx b/apps/web/src/components/structures/SpaceHierarchy.tsx index 779ac9f5c2..c1d5976869 100644 --- a/apps/web/src/components/structures/SpaceHierarchy.tsx +++ b/apps/web/src/components/structures/SpaceHierarchy.tsx @@ -184,6 +184,10 @@ const Tile: React.FC = ({ aria-labelledby={checkboxLabelId} checked={!!selected} tabIndex={-1} + onChange={(e) => { + e.stopPropagation(); + onToggleClick(); + }} /> ); } else { @@ -311,9 +315,9 @@ const Tile: React.FC = ({ }; childSection = ( -
    +
      {children} -
    + ); } diff --git a/apps/web/src/components/structures/auth/ForgotPassword.tsx b/apps/web/src/components/structures/auth/ForgotPassword.tsx index c9d5466801..8f246e17cb 100644 --- a/apps/web/src/components/structures/auth/ForgotPassword.tsx +++ b/apps/web/src/components/structures/auth/ForgotPassword.tsx @@ -12,7 +12,7 @@ import React, { type JSX, type ReactNode } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { sleep } from "matrix-js-sdk/src/utils"; import { LockSolidIcon, CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; -import { Button } from "@vector-im/compound-web"; +import { Button, Form } from "@vector-im/compound-web"; import { _t, _td } from "../../../languageHandler"; import Modal from "../../../Modal"; @@ -380,7 +380,7 @@ export default class ForgotPassword extends React.Component { <>

    {_t("auth|reset_password_title")}

    -
    +
    { this.setState({ logoutDevices: !this.state.logoutDevices })} checked={this.state.logoutDevices} + formWrap={false} > {_t("auth|reset_password|sign_out_other_devices")} @@ -422,7 +423,7 @@ export default class ForgotPassword extends React.Component { {submitButtonChild}
    - +
    ); } diff --git a/apps/web/src/components/structures/auth/SoftLogout.tsx b/apps/web/src/components/structures/auth/SoftLogout.tsx index a307078658..95d83e9d2b 100644 --- a/apps/web/src/components/structures/auth/SoftLogout.tsx +++ b/apps/web/src/components/structures/auth/SoftLogout.tsx @@ -26,6 +26,7 @@ import Spinner from "../../views/elements/Spinner"; import AuthHeader from "../../views/auth/AuthHeader"; import AuthBody from "../../views/auth/AuthBody"; import { SDKContext } from "../../../contexts/SDKContext"; +import { type URLParams } from "../../../vector/url_utils.ts"; enum LoginView { Loading, @@ -43,14 +44,11 @@ const STATIC_FLOWS_TO_VIEWS: Record = { }; interface IProps { - // Query parameters from MatrixChat - realQueryParams: { - loginToken?: string; - }; - fragmentAfterLogin?: string; + urlParams: URLParams; + fragmentAfterLogin: string; // Called when the SSO login completes - onTokenLoginCompleted: () => void; + onTokenLoginCompleted: (urlParams: URLParams, fragmentAfterLogin: string) => void; } interface IState { @@ -98,8 +96,7 @@ export default class SoftLogout extends React.Component { }; private async initLogin(): Promise { - const queryParams = this.props.realQueryParams; - const hasAllParams = queryParams?.["loginToken"]; + const hasAllParams = !!this.props.urlParams?.legacy_sso; if (hasAllParams) { this.setState({ loginView: LoginView.Loading }); @@ -189,7 +186,7 @@ export default class SoftLogout extends React.Component { const isUrl = localStorage.getItem(SSO_ID_SERVER_URL_KEY) || MatrixClientPeg.safeGet().getIdentityServerUrl(); const loginType = "m.login.token"; const loginParams = { - token: this.props.realQueryParams["loginToken"], + token: this.props.urlParams?.legacy_sso?.loginToken, device_id: MatrixClientPeg.safeGet().getDeviceId() ?? undefined, }; @@ -204,9 +201,7 @@ export default class SoftLogout extends React.Component { return Lifecycle.hydrateSession(credentials) .then(() => { - if (this.props.onTokenLoginCompleted) { - this.props.onTokenLoginCompleted(); - } + this.props.onTokenLoginCompleted(this.props.urlParams, this.props.fragmentAfterLogin); return true; }) .catch((e) => { diff --git a/apps/web/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/apps/web/src/components/views/auth/InteractiveAuthEntryComponents.tsx index 49443bdd2e..edd44019ba 100644 --- a/apps/web/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/apps/web/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -459,8 +459,8 @@ export class EmailIdentityAuthEntry extends React.Component< { a: (text: string) => ( - - {text} + + {text} ), @@ -475,6 +475,7 @@ export class EmailIdentityAuthEntry extends React.Component< { a: (text: string) => ( { : member; }; -interface IDMRoomTileProps { - member: Member; - lastActiveTs?: number; - onToggle(member: Member): void; - isSelected: boolean; -} - -class DMRoomTile extends React.PureComponent { - private onClick = (e: ButtonEvent): void => { - // Stop the browser from highlighting text - e.preventDefault(); - e.stopPropagation(); - - this.props.onToggle(this.props.member); - }; - - public render(): React.ReactNode { - const avatarSize = "32px"; - const avatar = (this.props.member as ThreepidMember).isEmail ? ( - - ) : ( - - ); - - const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(this.props.member.userId, { - withDisplayName: true, - }); - - const caption = (this.props.member as ThreepidMember).isEmail - ? _t("invite|email_caption") - : userIdentifier || this.props.member.userId; - - return ( - - ); - } -} - interface BaseProps { // Takes a boolean which is true if a user / users were invited / // a call transfer was initiated or false if the dialog was cancelled @@ -1186,8 +1124,7 @@ export default class InviteDialog extends React.PureComponent - {buttonText} - - ); - return (

    {helpText}

    {this.renderEditor()} - {goButton} + + {buttonText} +
    {this.state.busy ? : this.renderSuggestions()}
    @@ -1342,7 +1276,12 @@ export default class InviteDialog extends React.PureComponent +
    {this.renderEditor()}
    + {this.state.busy ? : this.renderSuggestions()} + + ); const tabs: NonEmptyArray> = [ new Tab( diff --git a/apps/web/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx b/apps/web/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx index 33ca9c510b..6f71f0762c 100644 --- a/apps/web/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx +++ b/apps/web/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx @@ -104,7 +104,12 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< return (
    - this.onToggle(cap)} description={text.byline}> + this.onToggle(cap)} + description={text.byline} + formWrap={false} + > {text.primary}
    diff --git a/apps/web/src/components/views/dialogs/devtools/Crypto.tsx b/apps/web/src/components/views/dialogs/devtools/Crypto.tsx index 1c02f062c3..3e4cda666e 100644 --- a/apps/web/src/components/views/dialogs/devtools/Crypto.tsx +++ b/apps/web/src/components/views/dialogs/devtools/Crypto.tsx @@ -110,7 +110,11 @@ function KeyStorage(): JSX.Element { return ( - {_t("devtools|crypto|key_storage")} + + + + + @@ -212,7 +216,11 @@ function CrossSigning(): JSX.Element { return (
    {_t("devtools|crypto|key_storage")}
    {_t("devtools|crypto|key_backup_latest_version")}
    - {_t("devtools|crypto|cross_signing")} + + + + + @@ -303,7 +311,11 @@ function Session(): JSX.Element { return (
    {_t("devtools|crypto|cross_signing")}
    {_t("devtools|crypto|cross_signing_status")}
    - {_t("devtools|crypto|session")} + + + + + diff --git a/apps/web/src/components/views/dialogs/invite/DMRoomTile.tsx b/apps/web/src/components/views/dialogs/invite/DMRoomTile.tsx new file mode 100644 index 0000000000..954e792b17 --- /dev/null +++ b/apps/web/src/components/views/dialogs/invite/DMRoomTile.tsx @@ -0,0 +1,74 @@ +/* + Copyright 2026 Element Creations 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 React from "react"; +import { RichItem } from "@element-hq/web-shared-components"; + +import { type Member, type ThreepidMember } from "../../../../utils/direct-messages.ts"; +import type { ButtonEvent } from "../../elements/AccessibleButton.tsx"; +import BaseAvatar from "../../avatars/BaseAvatar.tsx"; +import { mediaFromMxc } from "../../../../customisations/Media.ts"; +import UserIdentifierCustomisations from "../../../../customisations/UserIdentifier.ts"; +import { _t } from "../../../../languageHandler.tsx"; +import { Icon as EmailPillAvatarIcon } from "../../../../../res/img/icon-email-pill-avatar.svg"; + +interface IDMRoomTileProps { + member: Member; + lastActiveTs?: number; + onToggle(member: Member): void; + isSelected: boolean; +} + +/** A tile representing a single user in the "suggestions"/"recents" section of the invite dialog. */ +export class DMRoomTile extends React.PureComponent { + private onClick = (e: ButtonEvent): void => { + // Stop the browser from highlighting text + e.preventDefault(); + e.stopPropagation(); + + this.props.onToggle(this.props.member); + }; + + public render(): React.ReactNode { + const avatarSize = "32px"; + const avatar = (this.props.member as ThreepidMember).isEmail ? ( + + ) : ( + + ); + + const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(this.props.member.userId, { + withDisplayName: true, + }); + + const caption = (this.props.member as ThreepidMember).isEmail + ? _t("invite|email_caption") + : userIdentifier || this.props.member.userId; + + return ( + + ); + } +} diff --git a/apps/web/src/components/views/elements/AccessibleButton.tsx b/apps/web/src/components/views/elements/AccessibleButton.tsx index a2018b2211..3032312b32 100644 --- a/apps/web/src/components/views/elements/AccessibleButton.tsx +++ b/apps/web/src/components/views/elements/AccessibleButton.tsx @@ -152,46 +152,49 @@ const AccessibleButton = function AccessibleButton) => { - const action = getKeyBindingsManager().getAccessibilityAction(e); - switch (action) { - case KeyBindingAction.Enter: - e.stopPropagation(); - e.preventDefault(); - return onClick?.(e); - case KeyBindingAction.Space: - e.stopPropagation(); - e.preventDefault(); - break; - default: - onKeyDown?.(e); - } - }; - newProps.onKeyUp = (e: KeyboardEvent) => { - const action = getKeyBindingsManager().getAccessibilityAction(e); + if (element !== "button") { + // We need to consume enter onKeyDown and space onKeyUp + // otherwise we are risking also activating other keyboard focusable elements + // that might receive focus as a result of the AccessibleButtonClick action + // It's because we are using html buttons at a few places e.g. inside dialogs + // And divs which we report as role button to assistive technologies. + // Browsers handle space and enter key presses differently and we are only adjusting to the + // inconsistencies here + newProps.onKeyDown = (e: KeyboardEvent) => { + const action = getKeyBindingsManager().getAccessibilityAction(e); - switch (action) { - case KeyBindingAction.Enter: - e.stopPropagation(); - e.preventDefault(); - break; - case KeyBindingAction.Space: - e.stopPropagation(); - e.preventDefault(); - return onClick?.(e); - default: - onKeyUp?.(e); - break; - } - }; + switch (action) { + case KeyBindingAction.Enter: + e.stopPropagation(); + e.preventDefault(); + return onClick?.(e); + case KeyBindingAction.Space: + e.stopPropagation(); + e.preventDefault(); + break; + default: + onKeyDown?.(e); + } + }; + newProps.onKeyUp = (e: KeyboardEvent) => { + const action = getKeyBindingsManager().getAccessibilityAction(e); + + switch (action) { + case KeyBindingAction.Enter: + e.stopPropagation(); + e.preventDefault(); + break; + case KeyBindingAction.Space: + e.stopPropagation(); + e.preventDefault(); + return onClick?.(e); + default: + onKeyUp?.(e); + break; + } + }; + } } // Pass through the ref - used for keyboard shortcut access to some buttons diff --git a/apps/web/src/components/views/elements/AppTile.tsx b/apps/web/src/components/views/elements/AppTile.tsx index c35a77489f..9f3a203666 100644 --- a/apps/web/src/components/views/elements/AppTile.tsx +++ b/apps/web/src/components/views/elements/AppTile.tsx @@ -63,7 +63,7 @@ import { toWidgetDescriptor } from "../../../modules/WidgetLifecycleApi"; import { parseUrl } from "../../../utils/UrlUtils"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore.ts"; import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases.ts"; -import { WidgetContextMenu } from "../../../viewmodels/right-panel/WidgetContextMenuViewModel.tsx"; +import { WidgetContextMenu } from "../../../viewmodels/room/right-panel/WidgetContextMenuViewModel.tsx"; // Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin // because that would allow the iframe to programmatically remove the sandbox attribute, but diff --git a/apps/web/src/components/views/elements/CopyableText.tsx b/apps/web/src/components/views/elements/CopyableText.tsx index 8deaad1d4f..834386e450 100644 --- a/apps/web/src/components/views/elements/CopyableText.tsx +++ b/apps/web/src/components/views/elements/CopyableText.tsx @@ -44,6 +44,7 @@ export const CopyTextButton: React.FC = ({ children, getTextToCopy, border = true }); return ( -
    + {children} -
    + ); }; diff --git a/apps/web/src/components/views/elements/LearnMore.tsx b/apps/web/src/components/views/elements/LearnMore.tsx index 7e61bcc789..71ae561ed4 100644 --- a/apps/web/src/components/views/elements/LearnMore.tsx +++ b/apps/web/src/components/views/elements/LearnMore.tsx @@ -13,7 +13,7 @@ import Modal from "../../../Modal"; import InfoDialog from "../dialogs/InfoDialog"; import AccessibleButton, { type ButtonProps } from "./AccessibleButton"; -type Props = Omit, "element" | "kind" | "onClick" | "className"> & { +type Props = Omit, "element" | "kind" | "onClick" | "className"> & { title: string; description: string | React.ReactNode; }; @@ -29,7 +29,13 @@ const LearnMore: React.FC = ({ title, description, ...rest }) => { }; return ( - + {_t("action|learn_more")} ); diff --git a/apps/web/src/components/views/elements/Spinner.tsx b/apps/web/src/components/views/elements/Spinner.tsx index a3ba625e89..4df3177976 100644 --- a/apps/web/src/components/views/elements/Spinner.tsx +++ b/apps/web/src/components/views/elements/Spinner.tsx @@ -15,6 +15,11 @@ interface IProps { size?: number; message?: string; onFinished: any; // XXX: Spinner pretends to be a dialog so it must accept an onFinished, but it never calls it + /** + * Whether to render the content in a div or span. + * @default "div" + */ + as?: "span" | "div"; } export default class Spinner extends React.PureComponent { @@ -23,16 +28,16 @@ export default class Spinner extends React.PureComponent { }; public render(): React.ReactNode { - const { size, message } = this.props; + const { size, message, as: Component = "div" } = this.props; return ( -
    + {message && (
    {message}
     
    )} -
    + ); } } diff --git a/apps/web/src/components/views/elements/StyledCheckbox.tsx b/apps/web/src/components/views/elements/StyledCheckbox.tsx index e4cde65d16..97f2166cc1 100644 --- a/apps/web/src/components/views/elements/StyledCheckbox.tsx +++ b/apps/web/src/components/views/elements/StyledCheckbox.tsx @@ -14,6 +14,7 @@ interface IProps extends React.InputHTMLAttributes { inputRef?: Ref; id?: string; description?: ReactNode; + formWrap?: boolean; } const StyledCheckbox: React.FC = ({ @@ -22,30 +23,36 @@ const StyledCheckbox: React.FC = ({ className, inputRef, description, + formWrap = true, ...otherProps }) => { const id = initialId || "checkbox_" + secureRandomString(10); const name = useId(); const descriptionId = useId(); - return ( - - - } - > - {label && } - {description && {description}} - - + + const field = ( + + } + > + {label && } + {description && {description}} + ); + + if (formWrap) { + return {field}; + } + + return field; }; export default StyledCheckbox; diff --git a/apps/web/src/components/views/right_panel/ExtensionsCard.tsx b/apps/web/src/components/views/right_panel/ExtensionsCard.tsx index 25aabadd55..51cd5599e9 100644 --- a/apps/web/src/components/views/right_panel/ExtensionsCard.tsx +++ b/apps/web/src/components/views/right_panel/ExtensionsCard.tsx @@ -31,7 +31,7 @@ import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; import EmptyState from "./EmptyState"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents.ts"; import { UIComponent } from "../../../settings/UIFeature.ts"; -import { WidgetContextMenu } from "../../../viewmodels/right-panel/WidgetContextMenuViewModel.tsx"; +import { WidgetContextMenu } from "../../../viewmodels/room/right-panel/WidgetContextMenuViewModel.tsx"; interface Props { room: Room; diff --git a/apps/web/src/components/views/right_panel/VerificationPanel.tsx b/apps/web/src/components/views/right_panel/VerificationPanel.tsx index fa3a4dc4db..d369f98d3a 100644 --- a/apps/web/src/components/views/right_panel/VerificationPanel.tsx +++ b/apps/web/src/components/views/right_panel/VerificationPanel.tsx @@ -255,7 +255,7 @@ export default class VerificationPanel extends React.PureComponent - +

    ); } diff --git a/apps/web/src/components/views/right_panel/WidgetCard.tsx b/apps/web/src/components/views/right_panel/WidgetCard.tsx index b9c7c23957..12b65e0702 100644 --- a/apps/web/src/components/views/right_panel/WidgetCard.tsx +++ b/apps/web/src/components/views/right_panel/WidgetCard.tsx @@ -18,7 +18,7 @@ import { ContextMenuButton, useContextMenu } from "../../structures/ContextMenu" import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import Heading from "../typography/Heading"; -import { WidgetContextMenu } from "../../../viewmodels/right-panel/WidgetContextMenuViewModel"; +import { WidgetContextMenu } from "../../../viewmodels/room/right-panel/WidgetContextMenuViewModel"; interface IProps { room: Room; diff --git a/apps/web/src/components/views/room_settings/RoomPublishSetting.tsx b/apps/web/src/components/views/room_settings/RoomPublishSetting.tsx index cac8a55e24..b23ecfc49c 100644 --- a/apps/web/src/components/views/room_settings/RoomPublishSetting.tsx +++ b/apps/web/src/components/views/room_settings/RoomPublishSetting.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type ChangeEventHandler } from "react"; import { JoinRule, Visibility } from "matrix-js-sdk/src/matrix"; -import { SettingsToggleInput } from "@vector-im/compound-web"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../languageHandler"; @@ -16,6 +16,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg"; import DirectoryCustomisations from "../../../customisations/Directory"; import Modal from "../../../Modal"; import ErrorDialog from "../dialogs/ErrorDialog"; +import { onSubmitPreventDefault } from "../../../utils/form.ts"; interface IProps { roomId: string; @@ -90,16 +91,18 @@ export default class RoomPublishSetting extends React.PureComponent + + + ); } } diff --git a/apps/web/src/components/views/settings/EventIndexPanel.tsx b/apps/web/src/components/views/settings/EventIndexPanel.tsx index 24acd0f8a6..4cd7a7d015 100644 --- a/apps/web/src/components/views/settings/EventIndexPanel.tsx +++ b/apps/web/src/components/views/settings/EventIndexPanel.tsx @@ -220,7 +220,12 @@ export default class EventIndexPanel extends React.Component

    - + {_t("action|reset")}

    diff --git a/apps/web/src/components/views/settings/Notifications.tsx b/apps/web/src/components/views/settings/Notifications.tsx index 15e530c57b..c52753907d 100644 --- a/apps/web/src/components/views/settings/Notifications.tsx +++ b/apps/web/src/components/views/settings/Notifications.tsx @@ -52,6 +52,7 @@ import { SettingsSubsectionHeading } from "./shared/SettingsSubsectionHeading"; import { SettingsSubsection } from "./shared/SettingsSubsection"; import { doesRoomHaveUnreadMessages } from "../../../Unread"; import SettingsFlag from "../elements/SettingsFlag"; +import { onSubmitPreventDefault } from "../../../utils/form.ts"; // TODO: this "view" component still has far too much application logic in it, // which should be factored out to other files. @@ -651,7 +652,7 @@ export default class Notifications extends React.PureComponent{masterSwitch}; } const emailSwitches = (this.state.threepids || []) @@ -669,19 +670,21 @@ export default class Notifications extends React.PureComponent - {masterSwitch} + + {masterSwitch} - + - {this.state.deviceNotificationsEnabled && ( - <> - - - - - )} + {this.state.deviceNotificationsEnabled && ( + <> + + + + + )} - {emailSwitches} + {emailSwitches} + ); } diff --git a/apps/web/src/components/views/settings/notifications/NotificationSettings2.tsx b/apps/web/src/components/views/settings/notifications/NotificationSettings2.tsx index c316d46129..7619e5acea 100644 --- a/apps/web/src/components/views/settings/notifications/NotificationSettings2.tsx +++ b/apps/web/src/components/views/settings/notifications/NotificationSettings2.tsx @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React, { type JSX, useState } from "react"; -import { SettingsToggleInput } from "@vector-im/compound-web"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import NewAndImprovedIcon from "../../../../../res/img/element-icons/new-and-improved.svg"; import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; @@ -33,6 +33,7 @@ import { SettingsSubsection } from "../shared/SettingsSubsection"; import { NotificationPusherSettings } from "./NotificationPusherSettings"; import SettingsFlag from "../../elements/SettingsFlag"; import { SettingsSubsectionHeading } from "../shared/SettingsSubsectionHeading"; +import { onSubmitPreventDefault } from "../../../../utils/form.ts"; enum NotificationDefaultLevels { AllMessages = "all_messages", @@ -111,7 +112,7 @@ export default function NotificationSettings2(): JSX.Element { )} -
    + -
    + - - + + + + diff --git a/apps/web/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx index 15f3a3cefa..224a70e62d 100644 --- a/apps/web/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx @@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details. import React, { type ContextType } from "react"; import { type Room } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; -import { Form } from "@vector-im/compound-web"; import { _t } from "../../../../../languageHandler"; import RoomProfileSettings from "../../../room_settings/RoomProfileSettings"; @@ -72,32 +71,25 @@ export default class GeneralRoomSettingsTab extends React.Component - { - evt.preventDefault(); - evt.stopPropagation(); - }} - > - - - + + + - - - + + + - - - - - {leaveSection} - - + + + + + {leaveSection} + ); } diff --git a/apps/web/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 15ca6e9788..5bb8780b3c 100644 --- a/apps/web/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -137,6 +137,7 @@ export default class SecurityRoomSettingsTab extends React.Component ( { dialog.close(); @@ -334,6 +335,7 @@ export default class SecurityRoomSettingsTab extends React.Component ( { dialog.close(); diff --git a/apps/web/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx index 6d6c25e122..b63e790736 100644 --- a/apps/web/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx @@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details. */ import React from "react"; -import { Form } from "@vector-im/compound-web"; import { Features } from "../../../../../settings/Settings"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -22,20 +21,13 @@ export default class NotificationUserSettingsTab extends React.Component { return ( - { - evt.preventDefault(); - evt.stopPropagation(); - }} - > - {newNotificationSettingsEnabled ? ( - - ) : ( - - - - )} - + {newNotificationSettingsEnabled ? ( + + ) : ( + + + + )} ); } diff --git a/apps/web/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/apps/web/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx index 8aeae9234a..caa379ba2e 100644 --- a/apps/web/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx +++ b/apps/web/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx @@ -184,14 +184,7 @@ const SpaceSettingsVisibilityTab: React.FC = ({ matrixClient: cli, space - { - evt.preventDefault(); - evt.stopPropagation(); - }} - > - {addressesSection} - + {addressesSection}
    ); diff --git a/apps/web/src/i18n/strings/cs.json b/apps/web/src/i18n/strings/cs.json index 218fa3b6c4..3f291cac32 100644 --- a/apps/web/src/i18n/strings/cs.json +++ b/apps/web/src/i18n/strings/cs.json @@ -2227,8 +2227,6 @@ "aliases_section": "Adresy místnosti", "avatar_field_label": "Avatar místnosti", "canonical_alias_field_label": "Hlavní adresa", - "default_url_previews_off": "Ve výchozím nastavení jsou náhledy URL adres zakázané pro členy této místnosti.", - "default_url_previews_on": "Ve výchozím nastavení jsou náhledy URL adres povolené pro členy této místnosti.", "description_space": "Upravte nastavení týkající se vašeho prostoru.", "error_creating_alias_description": "Při vytváření adresy došlo k chybě. Mohl to zakázat server, nebo mohlo dojít k dočasnému selhání.", "error_creating_alias_title": "Chyba při tvorbě adresy", @@ -2259,12 +2257,7 @@ "published_aliases_explainer_space": "Zveřejněné adresy může použít kdokoli na jakémkoli serveru, aby se připojil k vašemu prostoru.", "published_aliases_section": "Publikovaná adresa", "save": "Uložit změny", - "topic_field_label": "Téma místnosti", - "url_preview_encryption_warning": "V šifrovaných místnostech, jako je tato, jsou URL náhledy ve výchozím nastavení vypnuté, aby bylo možné zajistit, že váš domovský server neshromažďuje informace o odkazech, které v této místnosti vidíte.", - "url_preview_explainer": "Když někdo ve zprávě pošle URL adresu, může být zobrazen její náhled obsahující informace jako titulek, popis a obrázek z cílové stránky.", - "url_previews_section": "Náhledy webových adres", - "user_url_previews_default_off": "Vypnuli jste automatické náhledy webových adres.", - "user_url_previews_default_on": "Zapnuli jste automatické náhledy webových adres." + "topic_field_label": "Téma místnosti" }, "notifications": { "browse_button": "Procházet", @@ -2687,8 +2680,6 @@ "username": "Uživatelské jméno" }, "inline_url_previews_default": "Nastavit povolení náhledů URL adres jako výchozí", - "inline_url_previews_room": "Povolit náhledy URL adres pro členy této místnosti jako výchozí", - "inline_url_previews_room_account": "Povolit náhledy URL adres pro tuto místnost (ovlivňuje pouze vás)", "insert_trailing_colon_mentions": "Vložit dvojtečku za zmínku o uživateli na začátku zprávy", "invite_controls": { "default_label": "Povolit uživatelům pozvat vás do místností" diff --git a/apps/web/src/i18n/strings/cy.json b/apps/web/src/i18n/strings/cy.json index 4277fe0f92..00df88dfcd 100644 --- a/apps/web/src/i18n/strings/cy.json +++ b/apps/web/src/i18n/strings/cy.json @@ -2227,8 +2227,6 @@ "aliases_section": "Cyfeiriadau Ystafell", "avatar_field_label": "Afatar ystafell", "canonical_alias_field_label": "Prif gyfeiriad", - "default_url_previews_off": "Mae rhagolygon URL wedi'u hanalluogi fel rhagosodiad ar gyfer cyfranogwyr yn yr ystafell hon.", - "default_url_previews_on": "Mae rhagolygon URL wedi'u galluogi fel rhagosodiad ar gyfer cyfranogwyr yn yr ystafell hon.", "description_space": "Golygu gosodiadau sy'n ymwneud â'ch gofod.", "error_creating_alias_description": "Bu gwall wrth greu'r cyfeiriad hwnnw. Mae'n bosib na chaiff ei ganiatáu gan y gweinydd neu fe ddigwyddodd methiant dros dro.", "error_creating_alias_title": "Gwall wrth greu cyfeiriad", @@ -2259,12 +2257,7 @@ "published_aliases_explainer_space": "Gall unrhyw un ar unrhyw weinydd ddefnyddio cyfeiriadau cyhoeddedig i ymuno â'ch gofod.", "published_aliases_section": "Cyfeiriadau Cyhoeddedig", "save": "Cadw'r Newidiadau", - "topic_field_label": "Pwnc yr Ystafell", - "url_preview_encryption_warning": "Mewn ystafelloedd wedi'u hamgryptio, fel yr un hon, mae rhagolygon URL yn cael eu hanalluogi fel rhagosodiad i sicrhau na all eich gweinydd cartref (lle mae'r rhagolygon yn cael eu cynhyrchu) gasglu gwybodaeth am ddolenni welwch yn yr ystafell hon.", - "url_preview_explainer": "Pan fydd rhywun yn rhoi URL yn eu neges, mae modd dangos rhagolwg URL i roi mwy o wybodaeth am y ddolen honno fel y teitl, disgrifiad, a delwedd o'r wefan.", - "url_previews_section": "Rhagolygon URL", - "user_url_previews_default_off": "Rydych wedi analluogi rhagolygon URL fel rhagosodiad.", - "user_url_previews_default_on": "Rydych chi wedi galluogi rhagolygon URL fel rhagosodiad." + "topic_field_label": "Pwnc yr Ystafell" }, "notifications": { "browse_button": "Pori", @@ -2685,8 +2678,6 @@ "username": "Enw defnyddiwr" }, "inline_url_previews_default": "Galluogi rhagolygon URL mewnol fel rhagosodiad", - "inline_url_previews_room": "Galluogi rhagolygon URL fel rhagosodiad ar gyfer cyfranogwyr yn yr ystafell hon", - "inline_url_previews_room_account": "Galluogi rhagolygon URL ar gyfer yr ystafell hon (yn effeithio arnoch chi yn unig)", "insert_trailing_colon_mentions": "Mewnosod colon sy'n llusgo ar ôl i'r defnyddiwr sôn amdano ar ddechrau neges", "invite_controls": { "default_label": "Caniatáu i ddefnyddwyr eich gwahodd i ystafelloedd" diff --git a/apps/web/src/i18n/strings/da.json b/apps/web/src/i18n/strings/da.json index 1229114876..d10c9d15c7 100644 --- a/apps/web/src/i18n/strings/da.json +++ b/apps/web/src/i18n/strings/da.json @@ -114,7 +114,7 @@ "show_advanced": "Vis avanceret", "show_all": "Vis alle", "sign_in": "Log ind", - "sign_out": "Log ud", + "sign_out": "Fjern denne enhed", "skip": "Spring over", "start": "Start", "start_chat": "Start samtale", @@ -705,6 +705,7 @@ "decline_invitation_dialog": { "title": "Afvis invitation" }, + "desktop_default_device_name": "%(brand)s Skrivebord: %(platformName)s", "devtools": { "active_widgets": "Aktive widgets", "category_other": "Andre", @@ -979,6 +980,8 @@ "unknown_error_code": "Ukendt fejlkode", "update_power_level": "Kunne ikke ændre effektniveau" }, + "error_app_open_in_another_tab_title": "%(brand)s er åbnet i et andet faneblad", + "error_app_opened_in_another_window": "%(brand)s er åben i et andet vindue. Klik på \"%(label)s\" for at bruge %(brand)s her og frakoble det andet vindue.", "error_database_closed_description": { "for_desktop": "Din disk er måske fyldt. Vær venlig at rydde noget plads og genindlæse.", "for_web": "Hvis du har ryddet browserdata, er denne meddelelse forventet. %(brand)s kan også være åben i en anden fane, eller din disk er fuld. Ryd venligst lidt plads og genindlæs" @@ -1146,6 +1149,7 @@ "continue": "Fortsæt alligevel", "linux": "Linux", "macos": "Mac", + "title": "%(brand)s understøtter ikke denne browser", "windows_64bit": "Windows (64-bit)", "windows_arm_64bit": "Windows (ARM 64-bit)" }, @@ -1938,8 +1942,6 @@ "aliases_section": "Rummets adresser", "avatar_field_label": "Avatar for rummet", "canonical_alias_field_label": "Hovedadresse", - "default_url_previews_off": "URL-forhåndsvisninger er som standard deaktiveret for deltagere i dette rum.", - "default_url_previews_on": "URL-forhåndsvisninger er som standard aktiveret for deltagere i dette rum.", "description_space": "Redigér indstillinger for din klynge.", "error_creating_alias_description": "Der opstod en fejl under oprettelsen af ​​adressen. Den er muligvis ikke tilladt af serveren, eller der er opstået en midlertidig fejl.", "error_creating_alias_title": "Fejl ved oprettelse af adresse", @@ -1966,12 +1968,7 @@ "published_aliases_explainer_space": "Publicerede adresser kan bruges af alle til at tilslutte sig din klynge.", "published_aliases_section": "Offentliggjorte adresser", "save": "Gem ændringer", - "topic_field_label": "Rummets emne", - "url_preview_encryption_warning": "I krypterede rum, som dette, er URL-forhåndsvisninger som standard deaktiveret for at sikre, at din hjemmeserver (hvor forhåndsvisningerne genereres) ikke kan indsamle oplysninger om links, du ser i dette rum.", - "url_preview_explainer": "Når nogen indtaster en URL i sin besked, kan der vises en forhåndsvisning af URL'en, der giver flere oplysninger om linket, f.eks. titel, beskrivelse og et billede fra webstedet.", - "url_previews_section": "URL-forhåndsvisninger", - "user_url_previews_default_off": "Du har deaktiveret URL-forhåndsvisning som standard.", - "user_url_previews_default_on": "Du har aktiveret URL-forhåndsvisninger som standard." + "topic_field_label": "Rummets emne" }, "notifications": { "browse_button": "Gennemse", @@ -2291,9 +2288,7 @@ "remove_msisdn_prompt": "Fjern %(phone)s?", "spell_check_locale_placeholder": "Vælg en lokalitet" }, - "inline_url_previews_default": "Aktivér indbyggede URL-forhåndsvisninger som standard", - "inline_url_previews_room": "Aktivér forhåndsvisning af URL-adresser som standard for deltagere i dette rum", - "inline_url_previews_room_account": "Aktivér forhåndsvisning af URL-adresser for dette rum (påvirker kun dig)", + "inline_url_previews_default": "Aktivér forhåndsvisninger", "insert_trailing_colon_mentions": "Indsæt et afsluttende kolon efter brugeromtaler i starten af en meddelelse", "jump_to_bottom_on_send": "Gå til bunden af tidslinjen, når du sender en besked", "key_backup": { @@ -2462,20 +2457,20 @@ "best_security_note": "For at opnå den bedste sikkerhed skal du bekræfte dine sessioner og logge ud fra enhver session, som du ikke genkender eller bruger længere.", "browser": "Browser", "confirm_sign_out": { - "one": "Bekræft, at du logger af denne enhed", - "other": "Bekræft, at du logger af disse enheder" + "one": "Bekræft fjernelse af denne enhed", + "other": "Bekræft fjernelse af disse enheder" }, "confirm_sign_out_body": { - "one": "Klik på knappen herunder for at bekræfte, at du logger af denne enhed.", - "other": "Klik på knappen herunder for at bekræfte, at du logger af disse enheder." + "one": "Klik på knappen nedenfor for at bekræfte fjernelse af denne enhed.", + "other": "Klik på knappen nedenfor for at bekræfte fjernelse af disse enheder." }, "confirm_sign_out_continue": { - "one": "Log ud af enheden", - "other": "Log ud af enheder" + "one": "Fjern enhed", + "other": "Fjern enheder" }, "confirm_sign_out_sso": { - "one": "Bekræft, at du logger af denne enhed ved at bruge Single Sign On for at bevise din identitet.", - "other": "Bekræft, at du logger af disse enheder ved at bruge Single Sign On for at bevise din identitet." + "one": "Bekræft fjernelse af denne enhed ved at bruge Single Sign-On til at bevise din identitet.", + "other": "Bekræft fjernelse af disse enheder ved at bruge Single Sign-On til at bevise din identitet." }, "current_session": "Nuværende session", "desktop_session": "Skrivebordssession", @@ -2526,7 +2521,7 @@ "sign_in_with_qr": "Forbind ny enhed", "sign_in_with_qr_button": "Vis QR-kode", "sign_in_with_qr_description": "Brug en QR-kode til at logge ind på en anden enhed og opsætte sikker beskedkommunikation.", - "sign_out": "Log ud af denne session", + "sign_out": "Fjern denne session", "sign_out_all_other_sessions": "Log ud af alle andre sessioner (%(otherSessionsCount)s)", "sign_out_confirm_description": { "one": "Er du sikker på, at du vil logge ud af %(count)s sessionen?", @@ -3284,6 +3279,9 @@ "truncated_list_n_more": { "other": "Og %(count)s mere..." }, + "unsupported_browser": { + "title": "%(brand)s understøtter ikke denne browser" + }, "unsupported_server_description": "Denne server bruger en ældre version af Matrix. Opgrader til Matrix %(version)s for at kunne bruge %(brand)s uden fejl.", "unsupported_server_title": "Din server er ikke understøttet", "update": { @@ -3487,6 +3485,7 @@ "voice_call": "Stemmeopkald", "you_are_presenting": "Du præsenterer" }, + "web_default_device_name": "%(appName)s: %(browserName)s på %(osName)s", "welcome_to_element": "Velkommen til Element", "widget": { "added_by": "Widget tilføjet af", diff --git a/apps/web/src/i18n/strings/de_DE.json b/apps/web/src/i18n/strings/de_DE.json index a14cca6ee9..945fb8e718 100644 --- a/apps/web/src/i18n/strings/de_DE.json +++ b/apps/web/src/i18n/strings/de_DE.json @@ -2219,8 +2219,6 @@ "aliases_section": "Chatadressen", "avatar_field_label": "Chat Avatar", "canonical_alias_field_label": "Primäre Adresse", - "default_url_previews_off": "URL-Vorschau ist für Mitglieder des Chats standardmäßig deaktiviert.", - "default_url_previews_on": "URL-Vorschau ist für Mitglieder des Chats standardmäßig aktiviert.", "description_space": "Einstellungen vom Space bearbeiten.", "error_creating_alias_description": "Es gab einen Fehler beim Anlegen der Adresse. Entweder erlaubt es der Server nicht oder es gab ein temporäres Problem.", "error_creating_alias_title": "Fehler beim Anlegen der Adresse", @@ -2251,12 +2249,7 @@ "published_aliases_explainer_space": "Veröffentlichte Adressen erlauben jedem, den Space zu betreten.", "published_aliases_section": "Öffentliche Adresse", "save": "Speichern", - "topic_field_label": "Chat-Thema", - "url_preview_encryption_warning": "In verschlüsselten Chats wie diesem ist die Linkvorschau standardmäßig deaktiviert, damit dein Homeserver (der die Vorschau erzeugt) keine Informationen über Links in diesem Chat erhält.", - "url_preview_explainer": "Die URL-Vorschau kann Informationen wie den Titel, die Beschreibung sowie ein Vorschaubild der Website enthalten.", - "url_previews_section": "URL-Vorschau", - "user_url_previews_default_off": "Du hast die URL-Vorschau standardmäßig deaktiviert.", - "user_url_previews_default_on": "Du hast die URL-Vorschau standardmäßig aktiviert." + "topic_field_label": "Chat-Thema" }, "notifications": { "browse_button": "Durchsuchen", @@ -2678,8 +2671,6 @@ "username": "Benutzername" }, "inline_url_previews_default": "URL-Vorschau standardmäßig aktivieren", - "inline_url_previews_room": "URL-Vorschau für Chat Mitglieder", - "inline_url_previews_room_account": "URL-Vorschau für dich in diesem Chat", "insert_trailing_colon_mentions": "Doppelpunkt nach Erwähnungen einfügen", "invite_controls": { "default_label": "Nutzern erlauben, dich in Chats einzuladen" diff --git a/apps/web/src/i18n/strings/el.json b/apps/web/src/i18n/strings/el.json index f2d21753cb..8565897d1f 100644 --- a/apps/web/src/i18n/strings/el.json +++ b/apps/web/src/i18n/strings/el.json @@ -1728,8 +1728,6 @@ "aliases_section": "Διευθύνσεις δωματίων", "avatar_field_label": "Εικόνα δωματίου", "canonical_alias_field_label": "Κύρια διεύθυνση", - "default_url_previews_off": "Η προεπισκόπηση διευθύνσεων URL είναι απενεργοποιημένη από προεπιλογή για τους συμμετέχοντες σε αυτό το δωμάτιο.", - "default_url_previews_on": "Η προεπισκόπηση διευθύνσεων URL είναι ενεργοποιημένη από προεπιλογή για τους συμμετέχοντες σε αυτό το δωμάτιο.", "description_space": "Επεξεργαστείτε τις ρυθμίσεις που σχετίζονται με τον χώρο σας.", "error_creating_alias_description": "Παρουσιάστηκε σφάλμα κατά τη δημιουργία αυτής της διεύθυνσης. Ενδέχεται να μην επιτρέπεται από τον διακομιστή ή να έχει παρουσιαστεί προσωρινή αποτυχία.", "error_creating_alias_title": "Σφάλμα δημιουργίας διεύθυνσης", @@ -1756,12 +1754,7 @@ "published_aliases_explainer_space": "Οι δημοσιευμένες διευθύνσεις μπορούν να χρησιμοποιηθούν από οποιονδήποτε σε οποιονδήποτε διακομιστή για να συμμετάσχει στο χώρο σας.", "published_aliases_section": "Δημοσιευμένες Διευθύνσεις", "save": "Αποθήκευση Αλλαγών", - "topic_field_label": "Θέμα Δωματίου", - "url_preview_encryption_warning": "Σε κρυπτογραφημένα δωμάτια, όπως αυτό, οι προεπισκόπηση URL είναι απενεργοποιημένη από προεπιλογή για να διασφαλιστεί ότι ο κεντρικός σας διακομιστής (όπου δημιουργείται μια προεπισκόπηση) δεν μπορεί να συγκεντρώσει πληροφορίες σχετικά με συνδέσμους που βλέπετε σε αυτό το δωμάτιο.", - "url_preview_explainer": "Όταν κάποιος εισάγει μια διεύθυνση URL στο μήνυμά του, μπορεί να εμφανιστεί μια προεπισκόπηση του URL για να δώσει περισσότερες πληροφορίες σχετικά με αυτόν τον σύνδεσμο, όπως τον τίτλο, την περιγραφή και μια εικόνα από τον ιστότοπο.", - "url_previews_section": "Προεπισκόπηση συνδέσμων", - "user_url_previews_default_off": "Έχετε απενεργοποιημένη από προεπιλογή την προεπισκόπηση συνδέσμων.", - "user_url_previews_default_on": "Έχετε ενεργοποιημένη από προεπιλογή την προεπισκόπηση συνδέσμων." + "topic_field_label": "Θέμα Δωματίου" }, "notifications": { "browse_button": "Εξερεύνηση", @@ -2039,8 +2032,6 @@ "spell_check_locale_placeholder": "Επιλογή τοπικών ρυθμίσεων" }, "inline_url_previews_default": "Ενεργοποιήστε τις ενσωματωμένες προεπισκοπήσεις URL από προεπιλογή", - "inline_url_previews_room": "Ενεργοποιήστε τις προεπισκοπήσεις URL από προεπιλογή για τους συμμετέχοντες σε αυτό το δωμάτιο", - "inline_url_previews_room_account": "Ενεργοποίηση προεπισκόπισης URL για αυτό το δωμάτιο (επηρεάζει μόνο εσάς)", "insert_trailing_colon_mentions": "Εισαγάγετε άνω και κάτω τελεία μετά την αναφορά του χρήστη στην αρχή ενός μηνύματος", "jump_to_bottom_on_send": "Μεταβείτε στο τέλος του χρονολογίου όταν στέλνετε ένα μήνυμα", "key_backup": { diff --git a/apps/web/src/i18n/strings/eo.json b/apps/web/src/i18n/strings/eo.json index a4fba455da..40f0c10f28 100644 --- a/apps/web/src/i18n/strings/eo.json +++ b/apps/web/src/i18n/strings/eo.json @@ -1381,8 +1381,6 @@ "aliases_section": "Adresoj de ĉambro", "avatar_field_label": "Profilbildo de ĉambro", "canonical_alias_field_label": "Ĉefa adreso", - "default_url_previews_off": "Antaŭrigardoj de URL-oj estas implicite malŝaltitaj por anoj de tiu ĉi ĉambro.", - "default_url_previews_on": "Antaŭrigardoj de URL-oj estas implicite ŝaltitaj por anoj de tiu ĉi ĉambro.", "description_space": "Redaktu agordojn pri via aro.", "error_creating_alias_description": "Eraris kreado de tiu adreso. Eble ĝi ne estas permesata de la servilo, aŭ okazis portempa fiasko.", "error_creating_alias_title": "Eraris kreado de adreso", @@ -1409,12 +1407,7 @@ "published_aliases_explainer_space": "Publikigitajn adresojn povas uzi ajna persono sur ajna servilo por aliĝi al via aro.", "published_aliases_section": "Publikigitaj adresoj", "save": "Konservi ŝanĝojn", - "topic_field_label": "Temo de ĉambro", - "url_preview_encryption_warning": "En ĉifritaj ĉambroj, kiel ĉi tiu, antaŭrigardoj al URL-oj estas implicite malŝaltitaj por certigi, ke via hejmservilo (kie la antaŭrigardoj estas generataj) ne povas kolekti informojn pri ligiloj en ĉi tiu ĉambro.", - "url_preview_explainer": "Kiam iu metas URL-on en sian mesaĝon, antaŭrigardo al tiu URL povas montriĝi, por doni pliajn informojn pri tiu ligilo, kiel ekzemple la titolon, priskribon, kaj bildon el la retejo.", - "url_previews_section": "Antaŭrigardoj al retpaĝoj", - "user_url_previews_default_off": "Vi malŝaltis implicitajn antaŭrigardojn al retpaĝoj.", - "user_url_previews_default_on": "Vi ŝaltis implicitajn antaŭrigardojn al retpaĝoj." + "topic_field_label": "Temo de ĉambro" }, "notifications": { "browse_button": "Foliumi", @@ -1657,8 +1650,6 @@ "remove_msisdn_prompt": "Ĉu forigi %(phone)s?" }, "inline_url_previews_default": "Ŝalti entekstan antaŭrigardon al retadresoj", - "inline_url_previews_room": "Ŝalti URL-antaŭrigardon por anoj de ĉi tiu ĉambro", - "inline_url_previews_room_account": "Ŝalti URL-antaŭrigardon en ĉi tiu ĉambro (nur por vi)", "jump_to_bottom_on_send": "Salti al subo de historio sendinte mesaĝon", "key_backup": { "setup_secure_backup": { diff --git a/apps/web/src/i18n/strings/es.json b/apps/web/src/i18n/strings/es.json index e7857db6e1..e45d26f78f 100644 --- a/apps/web/src/i18n/strings/es.json +++ b/apps/web/src/i18n/strings/es.json @@ -1847,8 +1847,6 @@ "aliases_section": "Direcciones de la sala", "avatar_field_label": "Avatar de la sala", "canonical_alias_field_label": "Dirección principal", - "default_url_previews_off": "La vista previa de URLs se desactiva por defecto para los participantes de esta sala.", - "default_url_previews_on": "La vista previa de URLs se activa por defecto en los participantes de esta sala.", "description_space": "Edita los ajustes de tu espacio.", "error_creating_alias_description": "Hubo un error al crear esa dirección. Es posible que el servidor no lo permita o que haya ocurrido una falla temporal.", "error_creating_alias_title": "Error al crear la dirección", @@ -1875,12 +1873,7 @@ "published_aliases_explainer_space": "Los espacios publicados pueden usarse por cualquiera, independientemente de su servidor base.", "published_aliases_section": "Direcciones publicadas", "save": "Guardar cambios", - "topic_field_label": "Asunto de la sala", - "url_preview_encryption_warning": "En salas cifradas como ésta, la vista previa de las URLs se desactiva por defecto para asegurar que el servidor base (donde se generan) no pueda recopilar información de los enlaces que veas en esta sala.", - "url_preview_explainer": "Cuando alguien incluya una dirección URL en su mensaje, puede mostrarse una vista previa para ofrecer información sobre el enlace, que incluirá el título, descripción, y una imagen del sitio web.", - "url_previews_section": "Vista previa de enlaces", - "user_url_previews_default_off": "Has desactivado la vista previa de URLs por defecto.", - "user_url_previews_default_on": "Has activado las vista previa de URLs por defecto." + "topic_field_label": "Asunto de la sala" }, "notifications": { "browse_button": "Seleccionar", @@ -2172,8 +2165,6 @@ "spell_check_locale_placeholder": "Elige un idioma" }, "inline_url_previews_default": "Activar la vista previa de URLs en línea por defecto", - "inline_url_previews_room": "Activar la vista previa de URLs por defecto para los participantes de esta sala", - "inline_url_previews_room_account": "Activar la vista previa de URLs en esta sala (solo para ti)", "insert_trailing_colon_mentions": "Inserta automáticamente dos puntos después de las menciones que hagas al principio de los mensajes", "jump_to_bottom_on_send": "Saltar abajo del todo al enviar un mensaje", "key_backup": { diff --git a/apps/web/src/i18n/strings/et.json b/apps/web/src/i18n/strings/et.json index bd0ded64ac..82e8cd0c18 100644 --- a/apps/web/src/i18n/strings/et.json +++ b/apps/web/src/i18n/strings/et.json @@ -2219,8 +2219,6 @@ "aliases_section": "Jututubade aadressid", "avatar_field_label": "Jututoa tunnuspilt ehk avatar", "canonical_alias_field_label": "Põhiaadress", - "default_url_previews_off": "URL'ide eelvaated on vaikimisi lülitatud välja selles jututoas osalejate jaoks.", - "default_url_previews_on": "URL'ide eelvaated on vaikimisi kasutusel selles jututoas osalejate jaoks.", "description_space": "Muuda oma kogukonnakeskuse seadistusi.", "error_creating_alias_description": "Aadressi loomisel tekkis viga. See kas on serveri poolt keelatud või tekkis ajutine tõrge.", "error_creating_alias_title": "Viga aadressi loomisel", @@ -2251,12 +2249,7 @@ "published_aliases_explainer_space": "Avaldatud aadresse saab igaüks igast serverist kasutada liitumiseks sinu kogukonnakeskusega.", "published_aliases_section": "Avaldatud aadressid", "save": "Salvesta muutused", - "topic_field_label": "Jututoa teema", - "url_preview_encryption_warning": "Krüptitud jututubades, nagu see praegune, URL'ide eelvaated ei ole vaikimisi kasutusel. See tagab, et sinu koduserver (kus eelvaated luuakse) ei saaks koguda teavet viidete kohta, mida sa siin jututoas näed.", - "url_preview_explainer": "Kui keegi lisab oma sõnumisse URL'i, siis võidakse näidata selle URL'i eelvaadet, mis annab lisateavet tema kohta, nagu näiteks pealkiri, kirjeldus ja kuidas ta välja näeb.", - "url_previews_section": "URL'ide eelvaated", - "user_url_previews_default_off": "Vaikimisi oled URL'ide eelvaated lülitanud välja.", - "user_url_previews_default_on": "Vaikimisi oled URL'ide eelvaated võtnud kasutusele." + "topic_field_label": "Jututoa teema" }, "notifications": { "browse_button": "Sirvi", @@ -2678,8 +2671,6 @@ "username": "Kasutajanimi" }, "inline_url_previews_default": "Luba URL'ide vaikimisi eelvaated", - "inline_url_previews_room": "Luba URL'ide vaikimisi eelvaated selles jututoas osalejate jaoks", - "inline_url_previews_room_account": "Luba URL'ide eelvaated selle jututoa jaoks (mõjutab vaid sind)", "insert_trailing_colon_mentions": "Mainimiste järel näita sõnumi alguses koolonit", "invite_controls": { "default_label": "Luba kasutajatel sind kutsida jututubadesse" diff --git a/apps/web/src/i18n/strings/fa.json b/apps/web/src/i18n/strings/fa.json index 1caa6dee48..14ada631b6 100644 --- a/apps/web/src/i18n/strings/fa.json +++ b/apps/web/src/i18n/strings/fa.json @@ -1253,8 +1253,6 @@ "aliases_section": "آدرس‌های اتاق", "avatar_field_label": "آواتار اتاق", "canonical_alias_field_label": "آدرس اصلی", - "default_url_previews_off": "پیش نمایش URL به طور پیش فرض برای شرکت کنندگان در این اتاق غیرفعال است.", - "default_url_previews_on": "پیش نمایش URL به طور پیش فرض برای شرکت کنندگان در این اتاق فعال است.", "description_space": "تنظیمات مربوط به فضای کاری خود را ویرایش کنید.", "error_creating_alias_description": "هنگام ایجاد آدرس خطایی روی داد. ممکن است سرور مجاز نباشد و یا اینکه خطایی موقت رخ داده باشد.", "error_creating_alias_title": "خطا در ایجاد آدرس", @@ -1276,12 +1274,7 @@ "publish_toggle": "این اتاق را در فهرست اتاق %(domain)s برای عموم منتشر شود؟", "published_aliases_section": "آدرس‌های منتشر شده", "save": "ذخیره تغییرات", - "topic_field_label": "موضوع اتاق", - "url_preview_encryption_warning": "در اتاق های رمزگذاری شده، مانند این اتاق، پیش نمایش URL به طور پیش فرض غیرفعال است تا اطمینان حاصل شود که سرور شما (جایی که پیش نمایش ها ایجاد می شود) نمی تواند اطلاعات مربوط به پیوندهایی را که در این اتاق مشاهده می کنید جمع آوری کند.", - "url_preview_explainer": "هنگامی که فردی یک URL را در پیام خود قرار می دهد، می توان با مشاهده پیش نمایش آن URL، اطلاعات بیشتری در مورد آن پیوند مانند عنوان ، توضیحات و یک تصویر از وب سایت دریافت کرد.", - "url_previews_section": "پیش‌نمایش URL", - "user_url_previews_default_off": "شما به طور پیش فرض پیش نمایش url را غیر فعال کرده اید.", - "user_url_previews_default_on": "شما به طور پیش فرض پیش نمایش url را فعال کرده اید." + "topic_field_label": "موضوع اتاق" }, "notifications": { "browse_button": "جستجو", @@ -1460,8 +1453,6 @@ "remove_msisdn_prompt": "%(phone)s را پاک می‌کنید؟" }, "inline_url_previews_default": "فعال‌سازی پیش‌نمایش URL به صورت پیش‌فرض", - "inline_url_previews_room": "امکان پیش‌نمایش URL را به صورت پیش‌فرض برای اعضای این اتاق فعال کن", - "inline_url_previews_room_account": "فعال‌سازی پیش‌نمایش URL برای این اتاق (تنها شما را تحت تاثیر قرار می‌دهد)", "jump_to_bottom_on_send": "زمانی که پیام ارسال می‌کنید، به صورت خودکار به آخرین پیام پرش کن", "key_backup": { "setup_secure_backup": { diff --git a/apps/web/src/i18n/strings/fi.json b/apps/web/src/i18n/strings/fi.json index b5b7f8055e..edaa54aadf 100644 --- a/apps/web/src/i18n/strings/fi.json +++ b/apps/web/src/i18n/strings/fi.json @@ -1839,8 +1839,6 @@ "aliases_section": "Huoneen osoitteet", "avatar_field_label": "Huoneen kuva", "canonical_alias_field_label": "Pääosoite", - "default_url_previews_off": "URL-esikatselut ovat oletuksena pois päältä tämän huoneen jäsenillä.", - "default_url_previews_on": "URL-esikatselut on päällä oletusarvoisesti tämän huoneen jäsenillä.", "description_space": "Muokkaa tilaasi liittyviä asetuksia.", "error_creating_alias_description": "Osoitetta luotaessa tapahtui virhe. Voi olla, että palvelin ei salli sitä tai kyseessä oli tilapäinen virhe.", "error_creating_alias_title": "Virhe osoitetta luotaessa", @@ -1869,12 +1867,7 @@ "published_aliases_explainer_space": "Julkaistuja osoitteita voi käyttää kuka tahansa millä tahansa palvelimella liittyäkseen tilaasi.", "published_aliases_section": "Julkaistut osoitteet", "save": "Tallenna muutokset", - "topic_field_label": "Huoneen aihe", - "url_preview_encryption_warning": "Salatuissa huoneissa, kuten tässä, osoitteiden esikatselut ovat oletuksena pois käytöstä, jotta kotipalvelimesi (missä osoitteiden esikatselut luodaan) ei voi kerätä tietoa siitä, mitä linkkejä näet tässä huoneessa.", - "url_preview_explainer": "Kun joku asettaa osoitteen linkiksi viestiinsä, URL-esikatselu voi näyttää tietoja linkistä kuten otsikon, kuvauksen ja kuvan verkkosivulta.", - "url_previews_section": "URL-esikatselut", - "user_url_previews_default_off": "Olet oletusarvoisesti ottanut URL-esikatselut pois käytöstä.", - "user_url_previews_default_on": "Olet oletusarvoisesti ottanut URL-esikatselut käyttöön." + "topic_field_label": "Huoneen aihe" }, "notifications": { "browse_button": "Selaa", @@ -2222,8 +2215,6 @@ "username": "Käyttäjätunnus" }, "inline_url_previews_default": "Ota linkkien esikatselu käyttöön oletusarvoisesti", - "inline_url_previews_room": "Ota linkkien esikatselu käyttöön kaikille huoneen jäsenille", - "inline_url_previews_room_account": "Ota linkkien esikatselut käyttöön tässä huoneessa (koskee ainoastaan sinua)", "insert_trailing_colon_mentions": "Lisää kaksoispiste käyttäjän maininnan perään viestin alussa", "jump_to_bottom_on_send": "Siirry aikajanan pohjalle, kun lähetät viestin", "key_backup": { diff --git a/apps/web/src/i18n/strings/fr.json b/apps/web/src/i18n/strings/fr.json index ec23bc8a48..bef6cbf469 100644 --- a/apps/web/src/i18n/strings/fr.json +++ b/apps/web/src/i18n/strings/fr.json @@ -829,7 +829,7 @@ "invalid_json": "Ne semble pas être du JSON valide.", "level": "Rang", "low_bandwidth_mode": "Mode faible bande passante", - "low_bandwidth_mode_description": "Nécessite un serveur d’accueil compatible.", + "low_bandwidth_mode_description": "Désactive le chiffrement, la présence, les avatars, les accusés de lecture et les notifications de saisie.", "main_timeline": "Historique principal", "manual_device_verification": "Vérification manuelle de l'appareil", "no_receipt_found": "Aucun accusé disponible", @@ -1114,7 +1114,7 @@ "verification_requested_toast_title": "Vérification requise", "verified_identity_changed": "L'identité numérique de %(displayName)s (%(userId)s) a été réinitialisée. En savoir plus", "verified_identity_changed_no_displayname": "L'identité de %(userId)s a été réinitialisée. En savoir plus ", - "verify_toast_description": "À compter de fin avril 2026, les appareils non vérifiés ne pourront plus envoyer ni recevoir de messages.En savoir plus", + "verify_toast_description": "À compter de fin octobre 2026, les appareils non vérifiés ne pourront plus envoyer ni recevoir de messages.En savoir plus", "verify_toast_title": "Vérifiez cet appareil", "withdraw_verification_action": "Révoquer la vérification" }, @@ -2237,8 +2237,6 @@ "aliases_section": "Adresses du salon", "avatar_field_label": "Avatar du salon", "canonical_alias_field_label": "Adresse principale", - "default_url_previews_off": "Les aperçus d'URL sont désactivés par défaut pour les participants de ce salon.", - "default_url_previews_on": "Les aperçus d'URL sont activés par défaut pour les participants de ce salon.", "description_space": "Modifiez les paramètres de votre espace.", "error_creating_alias_description": "Une erreur est survenue lors de la création de l’adresse. Ce n’est peut-être pas autorisé par le serveur ou une erreur temporaire est survenue.", "error_creating_alias_title": "Erreur lors de la création de l’adresse", @@ -2269,12 +2267,7 @@ "published_aliases_explainer_space": "Les adresses publiées peuvent être utilisées par tout le monde sur tous les serveurs pour rejoindre votre espace.", "published_aliases_section": "Adresses publiées", "save": "Enregistrer les modifications", - "topic_field_label": "Sujet du salon", - "url_preview_encryption_warning": "Dans les salons chiffrés, comme celui-ci, l’aperçu des liens est désactivé par défaut pour s’assurer que le serveur d’accueil (où sont générés les aperçus) ne puisse pas collecter d’informations sur les liens qui apparaissent dans ce salon.", - "url_preview_explainer": "Quand quelqu’un met un lien dans son message, un aperçu du lien peut être affiché afin de fournir plus d’informations sur ce lien comme le titre, la description et une image du site.", - "url_previews_section": "Aperçus des liens", - "user_url_previews_default_off": "Vous avez désactivé les aperçus d’URL par défaut.", - "user_url_previews_default_on": "Vous avez activé les aperçus d’URL par défaut." + "topic_field_label": "Sujet du salon" }, "notifications": { "browse_button": "Parcourir", @@ -2696,9 +2689,8 @@ "unable_to_load_msisdns": "Impossible de charger les numéros de téléphone", "username": "Nom d’utilisateur" }, - "inline_url_previews_default": "Activer l’aperçu des URL par défaut", - "inline_url_previews_room": "Activer l’aperçu des URL par défaut pour les participants de ce salon", - "inline_url_previews_room_account": "Activer l’aperçu des URL pour ce salon (n’affecte que vous)", + "inline_url_previews_default": "Activer les aperçus", + "inline_url_previews_encrypted": "Activer les aperçus dans les sallons chiffrés", "insert_trailing_colon_mentions": "Insérer deux-points après les mentions de l'utilisateur au début d'un message", "invite_controls": { "default_label": "Autoriser les utilisateurs à vous inviter dans les salons" @@ -2836,6 +2828,8 @@ "enable_tray_icon": "Afficher l’icône dans la barre d’état et minimiser la fenêtre lors de la fermeture", "keyboard_heading": "Raccourcis clavier", "keyboard_view_shortcuts_button": "Pour voir tous les raccourcis claviers, cliquez ici.", + "link_previews_description": "Affiche des informations sur les liens sous les messages", + "link_previews_heading": "Aperçus des liens", "media_heading": "Images, GIF et vidéos", "presence_description": "Partager votre activité et votre statut avec les autres.", "publish_timezone": "Publier le fuseau horaire sur le profil public", @@ -4002,6 +3996,7 @@ "change_name_this_room": "Changer le nom de ce salon", "change_topic_active_room": "Changer le sujet dans le salon actuel", "change_topic_this_room": "Changer le sujet de ce salon", + "download_file": "Télécharger des fichiers depuis le dépôt multimédia", "receive_membership_active_room": "Afficher quand des personnes rejoignent, partent, ou sont invités dans votre salon actif", "receive_membership_this_room": "Voir quand une personne rejoint, quitte ou est invitée sur ce salon", "remove_ban_invite_leave_active_room": "Expulser, bannir ou inviter des personnes dans votre salon actif et en partir", diff --git a/apps/web/src/i18n/strings/gl.json b/apps/web/src/i18n/strings/gl.json index a1d49ff9f4..4c57a53c69 100644 --- a/apps/web/src/i18n/strings/gl.json +++ b/apps/web/src/i18n/strings/gl.json @@ -1608,8 +1608,6 @@ "aliases_section": "Enderezos da sala", "avatar_field_label": "Avatar da sala", "canonical_alias_field_label": "Enderezo principal", - "default_url_previews_off": "As vistas previas de URL están desactivadas por defecto para as participantes desta sala.", - "default_url_previews_on": "As vistas previas de URL están activas por defecto para os participantes desta sala.", "description_space": "Editar os axustes relativos ao teu espazo.", "error_creating_alias_description": "Algo fallou ao crear ese enderezo. Podería non estar autorizado polo servidor ou ser un fallo temporal.", "error_creating_alias_title": "Fallo ao crear o enderezo", @@ -1636,12 +1634,7 @@ "published_aliases_explainer_space": "Os enderezos publicados podense usar por calquera en calquera servidor para unirse ao teu espazo.", "published_aliases_section": "Enderezos publicados", "save": "Gardar cambios", - "topic_field_label": "Asunto da sala", - "url_preview_encryption_warning": "Nas salas cifradas, como é esta, está desactivado por defecto a previsualización das URL co fin de asegurarse de que o servidor local (que é onde se gardan as previsualizacións) non poida recoller información sobre das ligazóns que se ven nesta sala.", - "url_preview_explainer": "Cando alguén pon unha URL na mensaxe, esta previsualízarase para que así se coñezan xa cousas delas como o título, a descrición ou as imaxes que inclúe ese sitio web.", - "url_previews_section": "Vista previa de URL", - "user_url_previews_default_off": "Desactivou a vista previa de URL por defecto.", - "user_url_previews_default_on": "Activou a vista previa de URL por defecto." + "topic_field_label": "Asunto da sala" }, "notifications": { "browse_button": "Buscar", @@ -1907,8 +1900,6 @@ "spell_check_locale_placeholder": "Elixe o idioma" }, "inline_url_previews_default": "Activar por defecto as vistas previas en liña de URL", - "inline_url_previews_room": "Activar a vista previa de URL por defecto para as participantes nesta sala", - "inline_url_previews_room_account": "Activar vista previa de URL nesta sala (só che afecta a ti)", "insert_trailing_colon_mentions": "Inserir dous puntos tras mencionar a outra usuaria no inicio da mensaxe", "jump_to_bottom_on_send": "Ir ao final da cronoloxía cando envías unha mensaxe", "key_backup": { diff --git a/apps/web/src/i18n/strings/he.json b/apps/web/src/i18n/strings/he.json index 40028d2387..a25d699131 100644 --- a/apps/web/src/i18n/strings/he.json +++ b/apps/web/src/i18n/strings/he.json @@ -1325,8 +1325,6 @@ "aliases_section": "כתובות חדרים", "avatar_field_label": "אוואטר של החדר", "canonical_alias_field_label": "כתובת ראשית", - "default_url_previews_off": "תצוגות מקדימות של כתובות אתרים מושבתות כברירת מחדל עבור משתתפים בחדר זה.", - "default_url_previews_on": "תצוגות מקדימות של כתובות אתרים מופעלות כברירת מחדל עבור משתתפים בחדר זה.", "description_space": "שינוי הגדרות הנוגעות למרחב העבודה שלכם.", "error_creating_alias_description": "אירעה שגיאה ביצירת כתובת זו. ייתכן שהשרת אינו מאפשר זאת או שהתרחש כשל זמני.", "error_creating_alias_title": "שגיאה ביצירת כתובת", @@ -1353,12 +1351,7 @@ "published_aliases_explainer_space": "כל אחד בכל שרת יכול להשתמש בכתובות שפורסמו כדי להצטרף למרחב העבודה שלכם.", "published_aliases_section": "כתובות מפורסמות", "save": "שמור שינוייים", - "topic_field_label": "נושא החדר", - "url_preview_encryption_warning": "בחדרים מוצפנים, כמו זה, תצוגות מקדימות של כתובות אתרים מושבתות כברירת מחדל כדי להבטיח ששרת הבית שלך (במקום בו נוצרות התצוגות המקדימות) אינו יכול לאסוף מידע על קישורים שאתה רואה בחדר זה.", - "url_preview_explainer": "כאשר מישהו מכניס כתובת URL להודעה שלו, ניתן להציג תצוגה מקדימה של כתובת אתר כדי לתת מידע נוסף על קישור זה, כמו הכותרת, התיאור והתמונה מהאתר.", - "url_previews_section": "תצוגת קישורים", - "user_url_previews_default_off": "יש לך השבת תצוגות מקדימות של כתובות אתרים כברירת מחדל.", - "user_url_previews_default_on": "כברירת מחדל, הפעלת תצוגה מקדימה של כתובות אתרים." + "topic_field_label": "נושא החדר" }, "notifications": { "browse_button": "דפדף", @@ -1587,8 +1580,6 @@ "remove_msisdn_prompt": "הסר מספרי %(phone)s ?" }, "inline_url_previews_default": "אפשר צפייה של תצוגת קישורים בצאט כברירת מחדל", - "inline_url_previews_room": "אפשר לחברים בחדר זה לצפות בתצוגת קישורים", - "inline_url_previews_room_account": "הראה תצוגה מקדימה של קישורים בחדר זה (משפיע רק עליכם)", "insert_trailing_colon_mentions": "הוסף נקודתיים לאחר אזכור המשתמש בתחילת ההודעה", "jump_to_bottom_on_send": "קפוץ לתחתית השיחה בעת שליחת הודעה", "key_backup": { diff --git a/apps/web/src/i18n/strings/hr.json b/apps/web/src/i18n/strings/hr.json index 473875bc76..01a38d5c33 100644 --- a/apps/web/src/i18n/strings/hr.json +++ b/apps/web/src/i18n/strings/hr.json @@ -2250,8 +2250,6 @@ "aliases_section": "Adrese soba", "avatar_field_label": "Avatar sobe", "canonical_alias_field_label": "Glavna adresa", - "default_url_previews_off": "Pregledi URL-ova zadano su onemogućeni za sudionike u ovoj sobi.", - "default_url_previews_on": "Pregledi URL-ova zadano su omogućeni za sudionike u ovoj sobi.", "description_space": "Uredite postavke koje se odnose na vaš prostor.", "error_creating_alias_description": "Došlo je do pogreške prilikom izrade te adrese. Poslužitelj to možda ne dopušta ili je došlo do privremenog kvara.", "error_creating_alias_title": "Pogreška prilikom izrade adrese", @@ -2282,12 +2280,7 @@ "published_aliases_explainer_space": "Objavljene adrese može upotrebljavati svatko na svakom poslužitelju kako bi se pridružio vašem prostoru.", "published_aliases_section": "Objavljene adrese", "save": "Spremi promjene", - "topic_field_label": "Tema sobe", - "url_preview_encryption_warning": "U šifriranim sobama, poput ove, pregledi URL-ova zadano su onemogućeni kako bi se osiguralo da vaš matični poslužitelj (na kojem se generiraju pregledi) ne može prikupljati informacije o poveznicama koje vidite u ovoj sobi.", - "url_preview_explainer": "Kada netko u svoju poruku unese URL, može se prikazati pregled URL-a kako bi se dobile dodatne informacije o toj poveznici, kao što su naslov, opis i slika s mrežnog mjesta.", - "url_previews_section": "Pregledi URL-ova", - "user_url_previews_default_off": "Zadano onemogućili preglede URL-ova.", - "user_url_previews_default_on": "Zadano ste omogućili preglede URL-ova." + "topic_field_label": "Tema sobe" }, "notifications": { "browse_button": "Pregledaj", @@ -2713,8 +2706,6 @@ "username": "Korisničko ime" }, "inline_url_previews_default": "Zadano omogući umetnute pretpreglede URL-ova", - "inline_url_previews_room": "Omogući preglede URL-ova prema zadanim postavkama za sudionike u ovoj sobi", - "inline_url_previews_room_account": "Omogući preglede URL-ova za ovu sobu (utječe samo na vas)", "insert_trailing_colon_mentions": "Umetni dvotočku nakon što spominjanja korisnika na početku poruke", "invite_controls": { "default_label": "Dopustite korisnicima da vas pozovu u sobe" diff --git a/apps/web/src/i18n/strings/hu.json b/apps/web/src/i18n/strings/hu.json index 5b9765dc7e..78e9cf2222 100644 --- a/apps/web/src/i18n/strings/hu.json +++ b/apps/web/src/i18n/strings/hu.json @@ -2219,8 +2219,6 @@ "aliases_section": "Szobacímek", "avatar_field_label": "Szoba profilképe", "canonical_alias_field_label": "Fő cím", - "default_url_previews_off": "A webcím-előnézet alapértelmezetten tiltva van a szobában lévőknek.", - "default_url_previews_on": "A webcím-előnézet alapértelmezetten engedélyezve van a szobában lévőknek.", "description_space": "A tér beállításainak szerkesztése.", "error_creating_alias_description": "Hiba történt a cím létrehozása során. Nincs engedélyezve a kiszolgálón, vagy átmeneti hiba történt.", "error_creating_alias_title": "Cím beállítási hiba", @@ -2251,12 +2249,7 @@ "published_aliases_explainer_space": "A közzétett címet bárki használhatja a térbe való belépéshez, bármelyik kiszolgálóról.", "published_aliases_section": "Nyilvánosságra hozott cím", "save": "Változtatások mentése", - "topic_field_label": "Szoba témája", - "url_preview_encryption_warning": "A titkosított szobákban, mint például ez is, a webcím-előnézet alapértelmezetten ki van kapcsolva, hogy biztosított legyen, hogy a Matrix-kiszolgáló (amelyen az előnézet készül) ne tudjon információt gyűjteni arról, hogy milyen hivatkozásokat lát ebben a szobában.", - "url_preview_explainer": "Ha valaki webcímet helyez az üzenetébe, akkor lehetőség van egy előnézet megjelenítésére, amellyel további információt kaphat a hivatkozásról, mint a cím, a leírás és a weboldal képe.", - "url_previews_section": "Webcím-előnézet", - "user_url_previews_default_off": "A webcím-előnézet alapból tiltva van.", - "user_url_previews_default_on": "A webcím-előnézet alapból engedélyezve van." + "topic_field_label": "Szoba témája" }, "notifications": { "browse_button": "Böngészés", @@ -2678,8 +2671,6 @@ "username": "Felhasználónév" }, "inline_url_previews_default": "Beágyazott webcím-előnézetek alapértelmezett engedélyezése", - "inline_url_previews_room": "Webcím-előnézetek alapértelmezett engedélyezése a szobatagok számára", - "inline_url_previews_room_account": "Webcím-előnézetek engedélyezése ebben a szobában (csak Önt érinti)", "insert_trailing_colon_mentions": "Záró kettőspont beszúrása egy felhasználó üzenet elején való megemlítésekor", "invite_controls": { "default_label": "Felhasználók meghívhatják szobákba" diff --git a/apps/web/src/i18n/strings/hy.json b/apps/web/src/i18n/strings/hy.json index 5180b09bde..c75bef0ccb 100644 --- a/apps/web/src/i18n/strings/hy.json +++ b/apps/web/src/i18n/strings/hy.json @@ -2143,8 +2143,6 @@ "aliases_section": "Սենյակի հասցեներ", "avatar_field_label": "Սենյակի ավատար", "canonical_alias_field_label": "Հիմնական հասցե", - "default_url_previews_off": "URL նախադիտումները լռելյայն անջատված են այս սենյակի մասնակիցների համար:", - "default_url_previews_on": "URL- ի նախադիտումները միացված են լռելյայն այս սենյակի մասնակիցների համար:", "description_space": "Խմբագրել ձեր տարածքին վերաբերող կարգավորումները:", "error_creating_alias_description": "Այդ հասցեն ստեղծելիս սխալ տեղի ունեցավ։ Հնարավոր է՝ սերվերը թույլ չի տվել դա, կամ ժամանակավոր խափանում է տեղի ունեցել։", "error_creating_alias_title": "Հասցեի ստեղծման սխալ", @@ -2173,12 +2171,7 @@ "published_aliases_explainer_space": "Հրապարակված հասցեները կարող են օգտագործվել ցանկացած մեկի կողմից ցանկացած սերվերում` ձեր տարածքին միանալու համար:", "published_aliases_section": "Հրապարակված հասցեներ", "save": "Պահպանել փոփոխությունները", - "topic_field_label": "Սենյակի թեմա", - "url_preview_encryption_warning": "Գաղտնագրված սենյակներում, ինչպես այս մեկը, URL նախադիտումները անջատվում են լռելյայն `ապահովելու համար, որ ձեր homeserver-ը (որտեղ առաջանում են նախադիտումները) չկարողանա տեղեկություններ հավաքել այս սենյակում տեսած հղումների մասին:", - "url_preview_explainer": "Երբ ինչոր մեկը URL է դնում իր հաղորդագրության մեջ, URL- ի նախադիտումը կարող է ցուցադրվել` այդ հղման մասին լրացուցիչ տեղեկություններ տալու համար, ինչպիսիք են վերնագիրը, նկարագրությունը և կայքէջից պատկերը:", - "url_previews_section": "URL- ի նախադիտումներ", - "user_url_previews_default_off": "Դուք լռելյայն անջատել եք URL- ի նախադիտումները:", - "user_url_previews_default_on": "Դուք լռելյայն միացրել եք URL- ի նախադիտումները:" + "topic_field_label": "Սենյակի թեմա" }, "notifications": { "browse_button": "Զննել", @@ -2591,8 +2584,6 @@ "username": "Օգտանուն" }, "inline_url_previews_default": "Լռելյայն միացնել URL- ի ներդիրային նախադիտումները", - "inline_url_previews_room": "Միացնել URL-ների նախադիտումները լռելյայնորեն այս սենյակի մասնակիցների համար", - "inline_url_previews_room_account": "Միացնել URL-ների նախադիտումները այս սենյակի համար (ազդում է միայն ձեզ վրա)", "insert_trailing_colon_mentions": "Հաղորդագրության սկզբում գտնվող օգտատիրոջ հիշատակումից հետո ավելացնել երկկետ", "invite_controls": { "default_label": "Թույլատրել օգտատերերին հրավիրել ձեզ սենյակներ" diff --git a/apps/web/src/i18n/strings/id.json b/apps/web/src/i18n/strings/id.json index e38a50f039..f38c311c4c 100644 --- a/apps/web/src/i18n/strings/id.json +++ b/apps/web/src/i18n/strings/id.json @@ -115,7 +115,7 @@ "show_advanced": "Tampilkan lanjutan", "show_all": "Tampilkan semua", "sign_in": "Masuk", - "sign_out": "Keluar", + "sign_out": "Hapus perangkat ini", "skip": "Lewat", "start": "Mulai", "start_chat": "Mulai obrolan", @@ -2208,8 +2208,6 @@ "aliases_section": "Alamat Ruangan", "avatar_field_label": "Avatar ruangan", "canonical_alias_field_label": "Alamat utama", - "default_url_previews_off": "Tampilan URL dinonaktifkan secara bawaan untuk anggota di ruangan ini.", - "default_url_previews_on": "Tampilan URL diaktifkan secara bawaan untuk anggota di ruangan ini.", "description_space": "Edit pengaturan yang berkaitan dengan space Anda.", "error_creating_alias_description": "Terjadi sebuah kesalahan membuat alamat. Ini mungkin tidak diperbolehkan oleh servernya atau ada kegagalan sementara.", "error_creating_alias_title": "Terjadi kesalahan membuat alamat", @@ -2240,12 +2238,7 @@ "published_aliases_explainer_space": "Alamat yang dipublikasikan dapat digunakan oleh siapa saja di server apa saja untuk bergabung dengan space Anda.", "published_aliases_section": "Alamat yang Dipublikasikan", "save": "Simpan Perubahan", - "topic_field_label": "Topik Ruangan", - "url_preview_encryption_warning": "Di ruangan terenkripsi, seperti ruangan ini, tampilan URL dinonaktifkan secara bawaan untuk memastikan homeserver Anda (di mana tampilannya dibuat) tidak mendapatkan informasi tentang tautan yang Anda lihat di ruangan ini.", - "url_preview_explainer": "Ketika seseorang menambahkan URL di pesannya, sebuah tampilan URL dapat ditampilkan untuk memberikan informasi lainnya tentang tautan itu seperti judul, deskripsi, dan sebuah gambar dari website.", - "url_previews_section": "Tampilan URL", - "user_url_previews_default_off": "Anda telah menonaktifkan tampilan URL secara bawaan.", - "user_url_previews_default_on": "Anda telah mengaktifkan tampilan URL secara bawaan." + "topic_field_label": "Topik Ruangan" }, "notifications": { "browse_button": "Jelajahi", @@ -2667,8 +2660,6 @@ "username": "Nama pengguna" }, "inline_url_previews_default": "Aktifkan tampilan URL secara bawaan", - "inline_url_previews_room": "Aktifkan tampilan URL secara bawaan untuk anggota di ruangan ini", - "inline_url_previews_room_account": "Aktifkan tampilan URL secara bawaan (hanya memengaruhi Anda)", "insert_trailing_colon_mentions": "Tambahkan sebuah karakter titik dua sesudah sebutan pengguna dari awal pesan", "invite_controls": { "default_label": "Izinkan pengguna mengundang Anda ke ruangan" diff --git a/apps/web/src/i18n/strings/is.json b/apps/web/src/i18n/strings/is.json index afe6602bcc..9eba2eb287 100644 --- a/apps/web/src/i18n/strings/is.json +++ b/apps/web/src/i18n/strings/is.json @@ -1543,8 +1543,6 @@ "aliases_section": "Vistföng spjallrása", "avatar_field_label": "Auðkennismynd spjallrásar", "canonical_alias_field_label": "Aðalvistfang", - "default_url_previews_off": "Forskoðun vefslóða er sjálfgefið óvirk fyrir þátttakendur í þessari spjallrás.", - "default_url_previews_on": "Forskoðun vefslóða er sjálfgefið virk fyrir þátttakendur í þessari spjallrás.", "description_space": "Breyta stillingum viðkomandi svæðinu þínu.", "error_creating_alias_title": "Villa við að búa til vistfang", "error_deleting_alias_description_forbidden": "Þú hefur ekki heimild til að eyða vistfanginu.", @@ -1564,11 +1562,7 @@ "publish_toggle": "Birta þessa spjallrás opinberlega á skrá %(domain)s yfir spjallrásir?", "published_aliases_section": "Birt vistföng", "save": "Vista breytingar", - "topic_field_label": "Umfjöllunarefni spjallrásar", - "url_preview_encryption_warning": "Í dulrituðum spjallrásum, eins og þessari, er sjálfgefið slökkt á forskoðun vefslóða til að tryggja að heimaþjónn þinn (þar sem forskoðunin myndast) geti ekki safnað upplýsingum um tengla sem þú sérð í þessari spjallrás.", - "url_previews_section": "Forskoðun vefslóða", - "user_url_previews_default_off": "Þú hefur óvirkt forskoðun vefslóða sjálfgefið.", - "user_url_previews_default_on": "Þú hefur virkt forskoðun vefslóða sjálfgefið." + "topic_field_label": "Umfjöllunarefni spjallrásar" }, "notifications": { "browse_button": "Skoða", @@ -1809,8 +1803,6 @@ "spell_check_locale_placeholder": "Veldu staðfærslu" }, "inline_url_previews_default": "Sjálfgefið virkja forskoðun innfelldra vefslóða", - "inline_url_previews_room": "Virkja forskoðun vefslóða sjálfgefið fyrir þátttakendur í þessari spjallrás", - "inline_url_previews_room_account": "Virkja forskoðun vefslóða fyrir þessa spjallrás (einungis fyrir þig)", "insert_trailing_colon_mentions": "Setja tvípunkt á eftir þar sem minnst er á notanda í upphafi skilaboða", "jump_to_bottom_on_send": "Hoppa neðst á tímalínuna þegar þú sendir skilaboð", "key_backup": { diff --git a/apps/web/src/i18n/strings/it.json b/apps/web/src/i18n/strings/it.json index 9d76f15d7d..8189af9104 100644 --- a/apps/web/src/i18n/strings/it.json +++ b/apps/web/src/i18n/strings/it.json @@ -1908,8 +1908,6 @@ "aliases_section": "Indirizzi stanza", "avatar_field_label": "Avatar della stanza", "canonical_alias_field_label": "Indirizzo principale", - "default_url_previews_off": "Le anteprime degli URL sono inattive in modo predefinito per i partecipanti di questa stanza.", - "default_url_previews_on": "Le anteprime degli URL sono attive in modo predefinito per i partecipanti di questa stanza.", "description_space": "Modifica le impostazioni relative al tuo spazio.", "error_creating_alias_description": "Si è verificato un errore creando l'indirizzo. Potrebbe non essere permesso dal server o un problema temporaneo.", "error_creating_alias_title": "Errore creazione indirizzo", @@ -1936,12 +1934,7 @@ "published_aliases_explainer_space": "Gli indirizzi pubblicati possono essere usati da chiunque su tutti i server per entrare nel tuo spazio.", "published_aliases_section": "Indirizzi pubblicati", "save": "Salva modifiche", - "topic_field_label": "Argomento stanza", - "url_preview_encryption_warning": "Nelle stanze criptate, come questa, le anteprime degli URL sono disattivate in modo predefinito per garantire che il tuo homeserver (dove vengono generate le anteprime) non possa raccogliere informazioni sui collegamenti che vedi in questa stanza.", - "url_preview_explainer": "Quando qualcuno inserisce un URL nel proprio messaggio, è possibile mostrare un'anteprima dell'URL per fornire maggiori informazioni su quel collegamento, come il titolo, la descrizione e un'immagine dal sito web.", - "url_previews_section": "Anteprime URL", - "user_url_previews_default_off": "Hai disattivato le anteprime degli URL in modo predefinito.", - "user_url_previews_default_on": "Hai attivato le anteprime degli URL in modo predefinito." + "topic_field_label": "Argomento stanza" }, "notifications": { "browse_button": "Sfoglia", @@ -2247,8 +2240,6 @@ "spell_check_locale_placeholder": "Scegli una lingua" }, "inline_url_previews_default": "Attiva le anteprime URL in modo predefinito", - "inline_url_previews_room": "Attiva le anteprime URL in modo predefinito per i partecipanti in questa stanza", - "inline_url_previews_room_account": "Attiva le anteprime URL in questa stanza (riguarda solo te)", "insert_trailing_colon_mentions": "Inserisci dei due punti dopo le citazioni degli utenti all'inizio di un messaggio", "jump_to_bottom_on_send": "Salta in fondo alla linea temporale quando invii un messaggio", "key_backup": { diff --git a/apps/web/src/i18n/strings/ja.json b/apps/web/src/i18n/strings/ja.json index 966462b0cd..490416472f 100644 --- a/apps/web/src/i18n/strings/ja.json +++ b/apps/web/src/i18n/strings/ja.json @@ -1739,8 +1739,6 @@ "aliases_section": "ルームのアドレス", "avatar_field_label": "ルームのアバター", "canonical_alias_field_label": "メインアドレス", - "default_url_previews_off": "このルームの参加者には、既定でURLプレビューが無効です。", - "default_url_previews_on": "このルームの参加者には、既定でURLプレビューが有効です。", "description_space": "スペースの設定を変更します。", "error_creating_alias_description": "アドレスを作成する際にエラーが発生しました。サーバーで許可されていないか、一時的な障害が発生した可能性があります。", "error_creating_alias_title": "アドレスを作成する際にエラーが発生しました", @@ -1767,12 +1765,7 @@ "published_aliases_explainer_space": "公開アドレスを設定すると、どのサーバーのユーザーでも、あなたのスペースに参加できるようになります。", "published_aliases_section": "公開アドレス", "save": "変更を保存", - "topic_field_label": "ルームのトピック", - "url_preview_encryption_warning": "このルームを含めて、暗号化されたルームでは、あなたのホームサーバー(これがプレビューを作成します)によるリンクの情報の収集を防ぐため、URLプレビューは既定で無効になっています。", - "url_preview_explainer": "メッセージにURLが含まれる場合、タイトル、説明、ウェブサイトの画像などがURLプレビューとして表示されます。", - "url_previews_section": "URLプレビュー", - "user_url_previews_default_off": "URLプレビューが既定で無効です。", - "user_url_previews_default_on": "URLプレビューが既定で有効です。" + "topic_field_label": "ルームのトピック" }, "notifications": { "browse_button": "参照", @@ -2048,8 +2041,6 @@ "spell_check_locale_placeholder": "ロケールを選択" }, "inline_url_previews_default": "既定でインラインURLプレビューを有効にする", - "inline_url_previews_room": "このルームの参加者のために既定でURLプレビューを有効にする", - "inline_url_previews_room_account": "このルームのURLプレビューを有効にする(あなたにのみ適用)", "insert_trailing_colon_mentions": "ユーザーをメンションする際にコロンを挿入", "jump_to_bottom_on_send": "メッセージを送信する際にタイムラインの最下部に移動", "key_backup": { diff --git a/apps/web/src/i18n/strings/ka.json b/apps/web/src/i18n/strings/ka.json index 5d43204f97..9c14feaf3a 100644 --- a/apps/web/src/i18n/strings/ka.json +++ b/apps/web/src/i18n/strings/ka.json @@ -1386,8 +1386,6 @@ "aliases_section": "ოთახის მისამართები", "avatar_field_label": "ოთახის ავატარი", "canonical_alias_field_label": "მთავარი მისამართი", - "default_url_previews_off": "URL-ის გადახედვა ნაგულისხმევად გამორთულია ამ ოთახში მონაწილეებისთვის.", - "default_url_previews_on": "URL-ის გადახედვა ნაგულისხმევად ჩართულია ამ ოთახში მონაწილეებისთვის.", "description_space": "შეცვალეთ პარამეტრები, რომლებიც დაკავშირებულია თქვენს სივრცესთან.", "error_creating_alias_description": "ამ მისამართის შექმნისას მოხდა შეცდომა. ეს შეიძლება არ იყოს დაშვებული სერვერის მიერ ან მოხდა დროებითი უკმარისობა.", "error_creating_alias_title": "შეცდომა მისამართის შექმნისას", @@ -1414,12 +1412,7 @@ "published_aliases_explainer_space": "გამოქვეყნებული მისამართები შეიძლება გამოყენებულ იქნას ნებისმიერ სერვერზე, რათა შეუერთდეს თქვენს სივრცეს.", "published_aliases_section": "გამოქვეყნებული მისამართები", "save": "ცვლილებების შენახვა", - "topic_field_label": "ოთახის თემა", - "url_preview_encryption_warning": "დაშიფრულ ოთახებში, როგორიცაა ეს, URL-ის გადახედვა ნაგულისხმევად გამორთულია, რათა უზრუნველყოს, რომ თქვენი სახლის სერვერი (სადაც იქმნება გადახედვები) ვერ შეაგროვებს ინფორმაციას ამ ოთახში ნახულ ბმულებზე.", - "url_preview_explainer": "როდესაც ვინმე ათავსებს URL-ს თავის შეტყობინებაში, URL-ის გადახედვა შეიძლება ნაჩვენები იყოს ამ ბმულის შესახებ მეტი ინფორმაციის მისაღებად, როგორიცაა სათაური, აღწერა და სურათი ვებსაიტიდან.", - "url_previews_section": "URL გადახედვები", - "user_url_previews_default_off": "თქვენ გაქვთ ინვალიდი URL-ის გადახედვა ნაგულისხმევად.", - "user_url_previews_default_on": "თქვენ გაქვთ ჩართულია URL-ის გადახედვა ნაგულისხმევად." + "topic_field_label": "ოთახის თემა" }, "notifications": { "browse_button": "დათვალიერება", @@ -1685,8 +1678,6 @@ "spell_check_locale_placeholder": "აირჩიეთ ლოკალი" }, "inline_url_previews_default": "ნაგულისხმევად ჩართული URL-ის გადახედვის ჩართვა", - "inline_url_previews_room": "URL-ის გადახედვის ჩართვა ნაგულისხმევად ამ ოთახში მონაწილეებისთვის", - "inline_url_previews_room_account": "ამ ოთახისთვის URL-ის გადახედვის ჩართვა (მხოლოდ თქვენზე მოქმედებს)", "insert_trailing_colon_mentions": "ჩადეთ ბოლო ორწერტილი მას შემდეგ, რაც მომხმარებელი აღნიშნავს შეტყობინების დასაწყისში", "jump_to_bottom_on_send": "შეტყობინების გაგზავნისას გადადით ქრონოლოგიის ბოლოში", "key_backup": { diff --git a/apps/web/src/i18n/strings/ko.json b/apps/web/src/i18n/strings/ko.json index 987969ea21..474b3f567f 100644 --- a/apps/web/src/i18n/strings/ko.json +++ b/apps/web/src/i18n/strings/ko.json @@ -2196,8 +2196,6 @@ "aliases_section": "방 주소", "avatar_field_label": "방 아바타", "canonical_alias_field_label": "기본 주소", - "default_url_previews_off": "기본으로 URL 미리 보기가 이 방에 참여한 사람들 모두에게 꺼졌습니다.", - "default_url_previews_on": "기본으로 URL 미리 보기가 이 방에 참여한 사람들 모두에게 켜졌습니다.", "description_space": "스페이스와 관련된 설정을 편집하세요.", "error_creating_alias_description": "해당 주소를 생성하는 과정에서 오류가 발생했습니다. 서버에서 허용하지 않거나 일시적인 오류가 발생했을 수 있습니다.", "error_creating_alias_title": "주소 생성 중 오류 발생", @@ -2226,12 +2224,7 @@ "published_aliases_explainer_space": "공개된 주소는 어떤 서버의 누구든지 귀하의 공간에 참여하는 데 사용할 수 있습니다.", "published_aliases_section": "공개된 주소", "save": "변경 사항 저장", - "topic_field_label": "방 주제", - "url_preview_encryption_warning": "지금 이 방처럼, 암호화된 방에서는 홈서버 (미리 보기가 만들어지는 곳)에서 이 방에서 보여지는 링크에 대해 알 수 없도록 기본으로 URL 미리 보기가 꺼집니다.", - "url_preview_explainer": "누군가 메시지에 URL을 넣으면, URL 미리 보기로 웹사이트에서 온 제목, 설명, 그리고 이미지 등 그 링크에 대한 정보가 표시됩니다.", - "url_previews_section": "URL 미리보기", - "user_url_previews_default_off": "기본으로 URL 미리 보기를 껐습니다.", - "user_url_previews_default_on": "기본으로 URL 미리 보기를 켰습니다." + "topic_field_label": "방 주제" }, "notifications": { "browse_button": "찾기", @@ -2648,8 +2641,6 @@ "username": "사용자 이름" }, "inline_url_previews_default": "기본으로 인라인 URL 미리 보기 사용하기", - "inline_url_previews_room": "이 방에 참여한 모두에게 기본으로 URL 미리보기 사용하기", - "inline_url_previews_room_account": "이 방에서 URL 미리보기 사용하기 (오직 나만 영향을 받음)", "insert_trailing_colon_mentions": "메시지 시작 부분에 사용자 언급 뒤에 콜론을 추가합니다", "invite_controls": { "default_label": "사용자가 방에 초대할 수 있도록 허용" diff --git a/apps/web/src/i18n/strings/lo.json b/apps/web/src/i18n/strings/lo.json index 23be8ff319..4ae16559a0 100644 --- a/apps/web/src/i18n/strings/lo.json +++ b/apps/web/src/i18n/strings/lo.json @@ -1574,8 +1574,6 @@ "aliases_section": "ທີ່ຢູ່ຂອງຫ້ອງ", "avatar_field_label": "ຮູບ avatar ຫ້ອງ", "canonical_alias_field_label": "ທີ່ຢູ່ຫຼັກ", - "default_url_previews_off": "ການສະແດງຕົວຢ່າງ URL ຖືກປິດການນຳໃຊ້ໂດຍຄ່າເລີ່ມຕົ້ນສຳລັບຜູ້ເຂົ້າຮ່ວມໃນຫ້ອງນີ້.", - "default_url_previews_on": "ການສະແດງຕົວຢ່າງ URL ຖືກເປີດໃຊ້ໂດຍຄ່າເລີ່ມຕົ້ນສໍາລັບຜູ້ເຂົ້າຮ່ວມໃນຫ້ອງນີ້.", "description_space": "ແກ້ໄຂການຕັ້ງຄ່າທີ່ກ່ຽວຂ້ອງກັບພື້ນທີ່ຂອງທ່ານ.", "error_creating_alias_description": "ເກີດຄວາມຜິດພາດໃນການສ້າງທີ່ຢູ່ນັ້ນ. ມັນອາດຈະບໍ່ໄດ້ຮັບການອະນຸຍາດຈາກເຊີບເວີ ຫຼືບໍ່ສຳເລັດ ຊົ່ວຄາວເກີດຂຶ້ນ.", "error_creating_alias_title": "ເກີດຄວາມຜິດພາດໃນການສ້າງທີ່ຢູ່", @@ -1602,12 +1600,7 @@ "published_aliases_explainer_space": "ທີ່ຢູ່ທີ່ເຜີຍແຜ່ສາມາດຖືກນໍາໃຊ້ໂດຍຜູ້ໃດຜູ້ຫນຶ່ງໃນເຊີບເວີຂອງການເຂົ້າຮ່ວມຊ່ອງຂອງທ່ານ.", "published_aliases_section": "ທີ່ຢູ່ເຜີຍແຜ່", "save": "ບັນທຶກການປ່ຽນແປງ", - "topic_field_label": "ຫົວຂໍ້ຫ້ອງ", - "url_preview_encryption_warning": "ໃນຫ້ອງທີ່ເຂົ້າລະຫັດ, ເຊັ່ນດຽວກັບ, ການສະແດງຕົວຢ່າງ URLໄດ້ປິດໃຊ້ງານໂດຍຄ່າເລີ່ມຕົ້ນເພື່ອຮັບປະກັນວ່າ homeserver ຂອງທ່ານ (ບ່ອນສະແດງຕົວຢ່າງ) ບໍ່ສາມາດລວບລວມຂໍ້ມູນກ່ຽວກັບການເຊື່ອມຕໍ່ທີ່ທ່ານເຫັນຢູ່ໃນຫ້ອງນີ້.", - "url_preview_explainer": "ເມື່ອຜູ້ໃດຜູ້ນຶ່ງໃສ່ URL ໃນຂໍ້ຄວາມຂອງພວກເຂົາ, ການສະແດງຕົວຢ່າງ URL ສາມາດສະແດງເພື່ອໃຫ້ຂໍ້ມູນເພີ່ມເຕີມກ່ຽວກັບການເຊື່ອມຕໍ່ນັ້ນເຊັ່ນຫົວຂໍ້, ຄໍາອະທິບາຍແລະຮູບພາບຈາກເວັບໄຊທ໌.", - "url_previews_section": "ຕົວຢ່າງ URL", - "user_url_previews_default_off": "ທ່ານໄດ້ ປິດໃຊ້ງານ ຕົວຢ່າງ URL ຕາມຄ່າເລີ່ມຕົ້ນ.", - "user_url_previews_default_on": "ທ່ານໄດ້ ເປີດໃຊ້ງານ ຕົວຢ່າງ URL ຕາມຄ່າເລີ່ມຕົ້ນ." + "topic_field_label": "ຫົວຂໍ້ຫ້ອງ" }, "notifications": { "browse_button": "ຄົ້ນຫາ", @@ -1869,8 +1862,6 @@ "remove_msisdn_prompt": "ລຶບ %(phone)sອອກບໍ?" }, "inline_url_previews_default": "ເປີດໃຊ້ການສະແດງຕົວຢ່າງ URL ໃນແຖວຕາມຄ່າເລີ່ມຕົ້ນ", - "inline_url_previews_room": "ເປີດໃຊ້ການສະແດງຕົວຢ່າງ URL ໂດຍຄ່າເລີ່ມຕົ້ນສໍາລັບຜູ້ເຂົ້າຮ່ວມໃນຫ້ອງນີ້", - "inline_url_previews_room_account": "ເປີດໃຊ້ຕົວຢ່າງ URL ສໍາລັບຫ້ອງນີ້ (ມີຜົນຕໍ່ທ່ານເທົ່ານັ້ນ)", "insert_trailing_colon_mentions": "ຈໍ້າສອງເມັດພາຍຫຼັງຈາກຜູ້ໃຊ້ກ່າວເຖິງໃນຕອນເລີ່ມຕົ້ນຂອງຂໍ້ຄວາມ", "jump_to_bottom_on_send": "ໄປຫາລຸ່ມສຸດຂອງທາມລາຍເມື່ອທ່ານສົ່ງຂໍ້ຄວາມ", "key_backup": { diff --git a/apps/web/src/i18n/strings/lt.json b/apps/web/src/i18n/strings/lt.json index a0bc2f2b9c..9b90d0b829 100644 --- a/apps/web/src/i18n/strings/lt.json +++ b/apps/web/src/i18n/strings/lt.json @@ -1197,8 +1197,6 @@ "aliases_section": "Kambario Adresai", "avatar_field_label": "Kambario pseudoportretas", "canonical_alias_field_label": "Pagrindinis adresas", - "default_url_previews_off": "URL nuorodų peržiūros šio kambario dalyviams yra išjungtos kaip numatytosios.", - "default_url_previews_on": "URL nuorodų peržiūros šio kambario dalyviams yra įjungtos kaip numatytosios.", "description_space": "Redaguoti su savo erdve susijusius nustatymus.", "error_creating_alias_description": "Kuriant šį adresą įvyko klaida. Gali būti, kad serveris jo neleidžia arba įvyko laikina klaida.", "error_creating_alias_title": "Klaida kuriant adresą", @@ -1225,12 +1223,7 @@ "published_aliases_explainer_space": "Paskelbtus adresus gali naudoti bet kas bet kuriame serveryje, prisijungimui prie jūsų erdvės.", "published_aliases_section": "Paskelbti Adresai", "save": "Išsaugoti Pakeitimus", - "topic_field_label": "Kambario Tema", - "url_preview_encryption_warning": "Šifruotuose kambariuose, tokiuose kaip šis, URL nuorodų peržiūros pagal numatymą yra išjungtos, kad būtų užtikrinta, jog jūsų serveris (kur yra generuojamos peržiūros) negali rinkti informacijos apie jūsų šiame kambaryje peržiūrėtas nuorodas.", - "url_preview_explainer": "Kai kas nors į savo žinutę įtraukia URL, gali būti rodoma URL peržiūra, suteikianti daugiau informacijos apie tą nuorodą, tokios kaip pavadinimas, aprašymas ir vaizdas iš svetainės.", - "url_previews_section": "URL nuorodų peržiūros", - "user_url_previews_default_off": "Jūs išjungėte URL nuorodų peržiūras kaip numatytasias.", - "user_url_previews_default_on": "Jūs įjungėte URL nuorodų peržiūras kaip numatytasias." + "topic_field_label": "Kambario Tema" }, "notifications": { "browse_button": "Naršyti", @@ -1475,8 +1468,6 @@ "remove_msisdn_prompt": "Pašalinti %(phone)s?" }, "inline_url_previews_default": "Įjungti URL nuorodų peržiūras kaip numatytasias", - "inline_url_previews_room": "Įjungti URL nuorodų peržiūras kaip numatytasias šiame kambaryje esantiems dalyviams", - "inline_url_previews_room_account": "Įjungti URL nuorodų peržiūras šiame kambaryje (įtakoja tik jus)", "insert_trailing_colon_mentions": "Įterpti dvitaškį po naudotojo paminėjimų žinutės pradžioje", "jump_to_bottom_on_send": "Peršokti į laiko juostos apačią, kai siunčiate žinutę", "key_backup": { diff --git a/apps/web/src/i18n/strings/lv.json b/apps/web/src/i18n/strings/lv.json index 6ebf006013..d0c60566f7 100644 --- a/apps/web/src/i18n/strings/lv.json +++ b/apps/web/src/i18n/strings/lv.json @@ -1841,8 +1841,6 @@ "aliases_section": "Istabas adreses", "avatar_field_label": "Istabas avatars", "canonical_alias_field_label": "Galvenā adrese", - "default_url_previews_off": "ULR priekšskatījumi šīs istabas dalībniekiem pēc noklusējuma ir atspējoti.", - "default_url_previews_on": "URL priekšskatījumi šīs istabas dalībniekiem pēc noklusējuma ir iespējoti.", "description_space": "Rediģējiet ar jūsu telpu saistītos iestatījumus.", "error_creating_alias_description": "Veidojot šo adresi, radās kļūda. Iespējams, serveris to neatļauj vai arī ir radusies īslaicīga kļūme.", "error_creating_alias_title": "Veidojot adresi, radās kļūda", @@ -1869,12 +1867,7 @@ "published_aliases_explainer_space": "Publicētās adreses var izmantot ikviens jebkurā serverī, lai pievienotos jūsu telpai.", "published_aliases_section": "Publiskotās adreses", "save": "Saglabāt izmaiņas", - "topic_field_label": "Istabas temats", - "url_preview_encryption_warning": "Šifrētās istabās, tādās kā šī, URL priekšskatījumi pēc noklusējumi ir atspējoti, lai nodrošinātu, ka mājasserveris (kurā priekšskatījumi tiek izveidoti) nevar iegūt informāciju par šajā istabā redzamajām saitēm.", - "url_preview_explainer": "Kad kāds savā ziņā ievieto URL, var tikt parādīts priekšskatījums, lai sniegtu vairāk informācijas par saiti, kā piemēram, virsraksts, apraksts un attēls no tīmekļvietnes.", - "url_previews_section": "URL priekšskatījumi", - "user_url_previews_default_off": "Tu pēc noklusējuma esi atspējojis URL priekšskatījumus.", - "user_url_previews_default_on": "Tu pēc noklusējuma esi iespējojis URL priekšskatījumus." + "topic_field_label": "Istabas temats" }, "notifications": { "browse_button": "Pārlūkot", @@ -2164,8 +2157,6 @@ "spell_check_locale_placeholder": "Izvēlieties lokalizāciju" }, "inline_url_previews_default": "Iespējot URL priekšskatījumus pēc noklusējuma", - "inline_url_previews_room": "Iespējot URL priekšskatījumus pēc noklusējuma visiem šīs istabas dalībniekiem", - "inline_url_previews_room_account": "Iespējot URL priekšskatījumus šajā istabā (ietekmē tikai Tevi)", "insert_trailing_colon_mentions": "Jāievieto beigu kols pēc lietotāja pieminēšanas ziņas sākumā", "jump_to_bottom_on_send": "Nosūtot ziņu, pāriet uz laika skalas beigām", "key_backup": { diff --git a/apps/web/src/i18n/strings/mg_MG.json b/apps/web/src/i18n/strings/mg_MG.json index 9012242c9f..c290f91b3e 100644 --- a/apps/web/src/i18n/strings/mg_MG.json +++ b/apps/web/src/i18n/strings/mg_MG.json @@ -1892,8 +1892,6 @@ "aliases_section": "Adiresy ao amin'ny efitrano", "avatar_field_label": "Avatar efitra", "canonical_alias_field_label": "Adiresy lehibe", - "default_url_previews_off": "Ny fijeriny URL dia esorina hoany mpandray anjara amin'ity efitrano ity.", - "default_url_previews_on": "Ny fijerena URL dia alefa amin'ny alàlany default hoany mpandray anjara amin'ity efitrano ity.", "description_space": "Ahitsio ny kira mifandraika amin'ny habakabaka.", "error_creating_alias_description": "Nisy lesoka tamin'ny famoronana io adiresy io. Mety tsy avelany mpizara izany na nisy tsy fahombiazana vonjimaika.", "error_creating_alias_title": "Hadisoana tamin'ny famoronana adiresy", @@ -1920,12 +1918,7 @@ "published_aliases_explainer_space": "Ny adiresy navoaka dia azon'ny olona ampiasaina aminy mpizara rehetra mba hiditra amin'ny habakao.", "published_aliases_section": "Adiresy navoaka", "save": "Alaina ny fanovana", - "topic_field_label": "Lohahevitra Efitrano", - "url_preview_encryption_warning": "Ao aminy efitrano misy miafina, toa an'ity iray ity, ny fijerena URL dia kilemaina amin'ny alàlany default mba hahazoana antoka fa tsy afaka manangona fampahalalana momba ny rohy hitanao ao amin'ity efitrano ity ny mpampiantrano anao (izay anaovana ny preview).", - "url_preview_explainer": "Rehefa misy olona mametraka URL ao amin'ny hafany, dia azo aseho ny fijerena URL mba hanomezana fampahalalana bebe kokoa momba an'io rohy io toy ny lohateny, famaritana, ary sary avy aminy tranokala.", - "url_previews_section": "Topi-maso amin'ny Url", - "user_url_previews_default_off": "Ianao dia manana sembana Zahao ny URL amin'ny alàlany default.", - "user_url_previews_default_on": "Ianao dia manana afaka Zahao ny URL aminy alàlan'ny default." + "topic_field_label": "Lohahevitra Efitrano" }, "notifications": { "browse_button": "Mikaroka", @@ -2231,8 +2224,6 @@ "spell_check_locale_placeholder": "Misafidiana toerana iray" }, "inline_url_previews_default": "Alefaso ny fijerena URL an-tserasera aminy alàlan'ny default", - "inline_url_previews_room": "Alefaso amin'ny alàlany ara-pototra hoany mpandray anjara amin'ity efitrano ity ny fijerena URL", - "inline_url_previews_room_account": "Alefaso ny fijerena URL ho an'ity efitrano ity (miantraika aminao ihany)", "insert_trailing_colon_mentions": "Ampidiro tsangambato aoriana aorian'ny fiteny mpampiasa eo am-piandohany hafatra", "jump_to_bottom_on_send": "Mankanesa any amin'ny farany fandaharam-potoana rehefa mandefa hafatra ianao", "key_backup": { diff --git a/apps/web/src/i18n/strings/nb_NO.json b/apps/web/src/i18n/strings/nb_NO.json index 0c1a647d0e..70c828465e 100644 --- a/apps/web/src/i18n/strings/nb_NO.json +++ b/apps/web/src/i18n/strings/nb_NO.json @@ -2218,8 +2218,6 @@ "aliases_section": "Rom-adresser", "avatar_field_label": "Rommets avatar", "canonical_alias_field_label": "Hovedadresse", - "default_url_previews_off": "URL-forhåndsvisninger er skrudd av som standard for deltakerene i dette rommet.", - "default_url_previews_on": "URL-forhåndsvisninger er skrudd på som standard for deltakerene i dette rommet.", "description_space": "Rediger innstillinger knyttet til området ditt.", "error_creating_alias_description": "Det oppstod en feil ved opprettelsen av adressen. Det kan hende at serveren ikke tillater det, eller at det oppstod en midlertidig feil.", "error_creating_alias_title": "Feil ved oppretting av adresse", @@ -2250,12 +2248,7 @@ "published_aliases_explainer_space": "Publiserte adresser kan brukes av hvem som helst på hvilken som helst server for å bli med i ditt område.", "published_aliases_section": "Publiserte adresser", "save": "Lagre endringer", - "topic_field_label": "Rommets tema", - "url_preview_encryption_warning": "I krypterte rom som denne, er URL-forhåndsvisninger skrudd av som standard for å sikre at hjemmeserveren din (der forhåndsvisningene blir generert) ikke kan samle inn informasjon om lenkene som du ser i dette rommet.", - "url_preview_explainer": "Når noen legger til en URL i meldingene deres, kan en URL-forhåndsvisning bli vist for å gi mere informasjonen om den lenken, f.eks. tittelen, beskrivelsen, og et bilde fra nettstedet.", - "url_previews_section": "URL-forhåndsvisninger", - "user_url_previews_default_off": "Du har skrudd av URL-forhåndsvisninger som standard.", - "user_url_previews_default_on": "Du har skrudd på URL-forhåndsvisninger som standard." + "topic_field_label": "Rommets tema" }, "notifications": { "browse_button": "Bla", @@ -2677,8 +2670,6 @@ "username": "Brukernavn" }, "inline_url_previews_default": "Skru på URL-forhåndsvisninger inni meldinger som standard", - "inline_url_previews_room": "Skru på URL-forhåndsvisninger som standard for deltakerne i dette rommet", - "inline_url_previews_room_account": "Skru på URL-forhåndsvisninger for dette rommet (Påvirker bare deg)", "insert_trailing_colon_mentions": "Sett inn et etterfølgende kolon etter at brukeromtaler i starten av en melding", "invite_controls": { "default_label": "Tillat brukere å invitere deg til rom" diff --git a/apps/web/src/i18n/strings/nl.json b/apps/web/src/i18n/strings/nl.json index d1262d0bb1..39df2fb253 100644 --- a/apps/web/src/i18n/strings/nl.json +++ b/apps/web/src/i18n/strings/nl.json @@ -1619,8 +1619,6 @@ "aliases_section": "Kameradressen", "avatar_field_label": "Kamerafbeelding", "canonical_alias_field_label": "Hoofdadres", - "default_url_previews_off": "URL-voorvertoningen zijn voor deelnemers van deze kamer standaard uitgeschakeld.", - "default_url_previews_on": "URL-voorvertoningen zijn voor deelnemers van deze kamer standaard ingeschakeld.", "description_space": "Bewerk instellingen gerelateerd aan jouw space.", "error_creating_alias_description": "Er is een fout opgetreden bij het aanmaken van dit adres. Dit wordt mogelijk niet toegestaan door de server, of er is een tijdelijk probleem opgetreden.", "error_creating_alias_title": "Fout bij aanmaken van het adres", @@ -1647,12 +1645,7 @@ "published_aliases_explainer_space": "Gepubliceerde adressen kunnen door iedereen op elke server gebruikt worden om jouw Space te betreden.", "published_aliases_section": "Gepubliceerde adressen", "save": "Wijzigingen opslaan", - "topic_field_label": "Kameronderwerp", - "url_preview_encryption_warning": "In versleutelde kamers zoals deze zijn URL-voorvertoningen standaard uitgeschakeld, om te voorkomen dat jouw homeserver (waar de voorvertoningen worden gemaakt) informatie kan verzamelen over de koppelingen die je hier ziet.", - "url_preview_explainer": "Als iemand een URL in een bericht invoegt, kan er een URL-voorvertoning weergegeven worden met meer informatie over de koppeling, zoals de titel, omschrijving en een afbeelding van de website.", - "url_previews_section": "URL-voorvertoningen", - "user_url_previews_default_off": "Je hebt URL-voorvertoningen standaard uitgeschakeld.", - "user_url_previews_default_on": "Je hebt URL-voorvertoningen standaard ingeschakeld." + "topic_field_label": "Kameronderwerp" }, "notifications": { "browse_button": "Bladeren", @@ -1924,8 +1917,6 @@ "spell_check_locale_placeholder": "Kies een landinstelling" }, "inline_url_previews_default": "Inline URL-voorvertoning standaard inschakelen", - "inline_url_previews_room": "URL-voorvertoning voor alle deelnemers aan deze kamer standaard inschakelen", - "inline_url_previews_room_account": "URL-voorvertoning in dit kamer inschakelen (geldt alleen voor jou)", "insert_trailing_colon_mentions": "Voeg een dubbele punt in nadat de persoon het aan het begin van een bericht heeft vermeld", "jump_to_bottom_on_send": "Naar de onderkant van de tijdlijn springen wanneer je een bericht verstuurd", "key_backup": { diff --git a/apps/web/src/i18n/strings/pl.json b/apps/web/src/i18n/strings/pl.json index 7ca20bc041..53a1edb041 100644 --- a/apps/web/src/i18n/strings/pl.json +++ b/apps/web/src/i18n/strings/pl.json @@ -2184,8 +2184,6 @@ "aliases_section": "Adresy pokoju", "avatar_field_label": "Awatar pokoju", "canonical_alias_field_label": "Główny adres", - "default_url_previews_off": "Podgląd linków są domyślnie wyłączone dla uczestników tego pokoju.", - "default_url_previews_on": "Podgląd linków są domyślnie włączone dla uczestników tego pokoju.", "description_space": "Edytuj ustawienia powiązane z twoją przestrzenią.", "error_creating_alias_description": "Wystąpił błąd podczas aktualizowania tego adresu. Serwer mógł odrzucić żądanie lub wystąpił błąd tymczasowy.", "error_creating_alias_title": "Wystąpił błąd podczas tworzenia adresu", @@ -2214,12 +2212,7 @@ "published_aliases_explainer_space": "Opublikowane adresy mogą być używane przez każdego, kto dołączył do Twojej przestrzeni.", "published_aliases_section": "Opublikowane adresy", "save": "Zapisz zmiany", - "topic_field_label": "Temat pokoju", - "url_preview_encryption_warning": "W pokojach szyfrowanych, takich jak ten, podgląd adresów URL jest domyślnie wyłączony, aby upewnić się, że serwer (na którym generowane są podglądy) nie może zbierać informacji o linkach, które widzisz w pokoju.", - "url_preview_explainer": "Gdy ktoś umieści URL w wiadomości, można wyświetlić podgląd adresu URL, aby podać więcej informacji o tym łączu, takich jak tytuł, opis i obraz ze strony internetowej.", - "url_previews_section": "Podgląd linków", - "user_url_previews_default_off": "Podgląd linków jest domyślnie wyłączony.", - "user_url_previews_default_on": "Podgląd linków jest domyślnie włączony." + "topic_field_label": "Temat pokoju" }, "notifications": { "browse_button": "Przeglądaj", @@ -2639,8 +2632,6 @@ "username": "Nazwa użytkownika" }, "inline_url_previews_default": "Włącz domyślny podgląd URL w tekście", - "inline_url_previews_room": "Włącz domyślny podgląd URL dla uczestników w tym pokoju", - "inline_url_previews_room_account": "Włącz podgląd URL dla tego pokoju (dotyczy tylko Ciebie)", "insert_trailing_colon_mentions": "Wstawiaj dwukropek po wzmiance użytkownika na początku wiadomości", "invite_controls": { "default_label": "Zezwól użytkownikom na zapraszanie Cię do pokojów" diff --git a/apps/web/src/i18n/strings/pt.json b/apps/web/src/i18n/strings/pt.json index 9c5b4ca25f..64957224e6 100644 --- a/apps/web/src/i18n/strings/pt.json +++ b/apps/web/src/i18n/strings/pt.json @@ -2082,8 +2082,6 @@ "aliases_section": "Endereços de salas", "avatar_field_label": "Avatar da sala", "canonical_alias_field_label": "Endereço principal", - "default_url_previews_off": "As pré-visualizações de URL estão desactivadas por predefinição para os participantes nesta sala.", - "default_url_previews_on": "As pré-visualizações de URL estão activadas por predefinição para os participantes nesta sala.", "description_space": "Edita as definições relativas ao teu espaço.", "error_creating_alias_description": "Ocorreu um erro ao criares esse endereço. Pode não ser permitido pelo servidor ou ocorreu uma falha temporária.", "error_creating_alias_title": "Erro ao criar o endereço", @@ -2112,12 +2110,7 @@ "published_aliases_explainer_space": "Os endereços publicados podem ser usados por qualquer pessoa em qualquer servidor para se juntar ao teu espaço.", "published_aliases_section": "Endereços publicados", "save": "Salvar Alterações", - "topic_field_label": "Tópico da sala", - "url_preview_encryption_warning": "Em salas encriptadas, como esta, as pré-visualizações de URL são desactivadas por predefinição para garantir que o teu servidor doméstico (onde as pré-visualizações são geradas) não pode recolher informações sobre as ligações que vês nesta sala.", - "url_preview_explainer": "Quando alguém coloca um URL na sua mensagem, pode ser mostrada uma pré-visualização do URL para dar mais informações sobre essa ligação, como o título, a descrição e uma imagem do sítio Web.", - "url_previews_section": "Pré-visualização de links", - "user_url_previews_default_off": "Você desabilitou pré-visualizações de links por padrão.", - "user_url_previews_default_on": "Você habilitou pré-visualizações de links por padrão." + "topic_field_label": "Tópico da sala" }, "notifications": { "browse_button": "Navegar", @@ -2520,8 +2513,6 @@ "username": "Nome de utilizador" }, "inline_url_previews_default": "Ativar pré-visualizações de URL embutidas por predefinição", - "inline_url_previews_room": "Ativar pré-visualizações de URL por defeito para os participantes nesta sala", - "inline_url_previews_room_account": "Ativar pré-visualizações URL para esta sala (só te afeta a ti)", "insert_trailing_colon_mentions": "Insere dois pontos após as menções do utilizador no início de uma mensagem", "jump_to_bottom_on_send": "Salta para o fundo da linha de tempo quando enviar uma mensagem", "key_backup": { diff --git a/apps/web/src/i18n/strings/pt_BR.json b/apps/web/src/i18n/strings/pt_BR.json index 3f8d69fed1..6941da75d6 100644 --- a/apps/web/src/i18n/strings/pt_BR.json +++ b/apps/web/src/i18n/strings/pt_BR.json @@ -2195,8 +2195,6 @@ "aliases_section": "Endereços da sala", "avatar_field_label": "Foto da sala", "canonical_alias_field_label": "Endereço principal", - "default_url_previews_off": "Pré-visualizações de links estão desativadas por padrão para participantes desta sala.", - "default_url_previews_on": "Pré-visualizações de links estão ativadas por padrão para participantes desta sala.", "description_space": "Editar configurações relacionadas ao seu espaço.", "error_creating_alias_description": "Ocorreu um erro ao criar esse endereço. Isso pode não ser permitido pelo servidor ou houve um problema temporário.", "error_creating_alias_title": "Erro ao criar o endereço", @@ -2225,12 +2223,7 @@ "published_aliases_explainer_space": "Endereços publicados podem ser usados por qualquer um em qualquer servidor para entrar em seu espaço.", "published_aliases_section": "Endereços publicados", "save": "Salvar alterações", - "topic_field_label": "Descrição da sala", - "url_preview_encryption_warning": "Em salas criptografadas, como esta, as pré-visualizações de links estão desativadas por padrão para garantir que o seu servidor local (onde as visualizações são geradas) não possa coletar informações sobre os links que você vê nesta sala.", - "url_preview_explainer": "Quando alguém inclui um link em uma mensagem, a pré-visualização do link pode ser exibida para fornecer mais informações sobre esse link, como o título, a descrição e uma imagem do site.", - "url_previews_section": "Pré-visualização de links", - "user_url_previews_default_off": "Você desativou pré-visualizações de links por padrão.", - "user_url_previews_default_on": "Você ativou pré-visualizações de links por padrão." + "topic_field_label": "Descrição da sala" }, "notifications": { "browse_button": "Buscar", @@ -2650,8 +2643,6 @@ "username": "Nome de usuário" }, "inline_url_previews_default": "Ativar, por padrão, a visualização de resumo de links", - "inline_url_previews_room": "Ativar, para todos os participantes desta sala, a visualização de links", - "inline_url_previews_room_account": "Ativar, para esta sala, a visualização de links (só afeta você)", "insert_trailing_colon_mentions": "Insira dois pontos à direita após o usuário mencionar no início de uma mensagem", "invite_controls": { "default_label": "Permitir que usuários convidem você para salas" diff --git a/apps/web/src/i18n/strings/ru.json b/apps/web/src/i18n/strings/ru.json index d89f064ffc..0cc7277f64 100644 --- a/apps/web/src/i18n/strings/ru.json +++ b/apps/web/src/i18n/strings/ru.json @@ -11,18 +11,18 @@ "other": "%(count)s непрочитанных сообщения(-й), включая упоминания.", "one": "1 непрочитанное упоминание." }, - "recent_rooms": "Недавние комнаты", - "room_name": "Комната %(name)s", - "room_status_bar": "Строка состояния комнаты", + "recent_rooms": "Недавние чаты", + "room_name": "Чат %(name)s", + "room_status_bar": "Строка состояния чата", "seek_bar_label": "Панель поиска аудио", "unread_messages": "Непрочитанные сообщения.", "user_menu": "Меню пользователя" }, - "a11y_jump_first_unread_room": "Перейти в первую непрочитанную комнату.", + "a11y_jump_first_unread_room": "Перейти в первый непрочитанный чат.", "action": { "accept": "Принять", "add": "Добавить", - "add_existing_room": "Добавить существующую комнату", + "add_existing_room": "Добавить существующий чат", "add_people": "Добавить людей", "apply": "Применить", "approve": "Согласиться", @@ -41,7 +41,7 @@ "copy": "Копировать", "copy_link": "Копировать ссылку", "create": "Создать", - "create_a_room": "Создать комнату", + "create_a_room": "Создать чат", "create_account": "Создать учётную запись", "decline": "Отклонить", "decline_and_block": "Отклонить и заблокировать", @@ -58,7 +58,7 @@ "enter_fullscreen": "Перейти в полноэкранный режим", "exit_fullscreeen": "Выйти из полноэкранного режима", "expand": "Развернуть", - "explore_public_rooms": "Просмотреть публичные комнаты", + "explore_public_rooms": "Просмотреть публичные групповые чаты", "explore_rooms": "Обзор комнат", "export": "Экспорт", "forward": "Переслать", @@ -75,14 +75,14 @@ "join": "Войти", "learn_more": "Узнать больше", "leave": "Покинуть", - "leave_room": "Покинуть комнату", + "leave_room": "Покинуть чат", "logout": "Выйти", "manage": "Управление", "maximise": "Развернуть", "mention": "Упомянуть", "minimise": "Свернуть", - "new_room": "Новая комната", - "new_video_room": "Новая видеокомната", + "new_room": "Новый чат", + "new_video_room": "Новый видеочат", "next": "Далее", "no": "Нет", "ok": "ОК", @@ -147,7 +147,7 @@ }, "analytics": { "accept_button": "Всё в порядке", - "bullet_1": "Мы <не записываем и не профилируем любые данные учетной записи", + "bullet_1": "Мы не записываем и не профилируем любые данные аккаунта", "bullet_2": "Мы не передаем информацию третьим лицам", "consent_migration": "Ранее вы давали согласие на передачу нам анонимных данных об использовании. Мы изменили порядок предоставления этих данных.", "disable_prompt": "Вы можете отключить это в любое время в настройках", @@ -219,7 +219,7 @@ "incorrect_password": "Неверный пароль", "log_in_new_account": "Войти в новую учётную запись.", "logout_dialog": { - "description": "Уверены, что хотите выйти?", + "description": "Вы уверены, что хотите удалить это устройство?", "megolm_export": "Выгрузить ключи вручную", "setup_key_backup_title": "Вы потеряете доступ к вашим шифрованным сообщениям", "setup_secure_backup_description_1": "Эти сообщения защищены сквозным шифрованием. Только вы и ваш собеседник имеете ключи для их расшифровки и чтения.", @@ -229,7 +229,7 @@ "misconfigured_body": "Попросите администратора %(brand)s проверить конфигурационный файл на наличие неправильных или повторяющихся записей.", "misconfigured_title": "Ваш %(brand)s неправильно настроен", "mobile_create_account_title": "Вы собираетесь создать учетную запись на %(hsName)s", - "msisdn_field_description": "Другие пользователи могут приглашать вас в комнаты, используя ваши контактные данные", + "msisdn_field_description": "Другие пользователи могут приглашать вас в чаты, используя ваши контактные данные", "msisdn_field_label": "Телефон", "msisdn_field_number_invalid": "Этот номер телефона неправильный, проверьте его и повторите попытку", "msisdn_field_required_invalid": "Введите номер телефона", @@ -298,7 +298,7 @@ "confirm_new_password": "Подтвердите новый пароль", "devices_logout_success": "Вы вышли из всех устройств и больше не будете получать push-уведомления. Для повторного включения уведомлений снова войдите на каждом устройстве.", "other_devices_logout_warning_1": "При выходе из устройств удаляются хранящиеся на них ключи шифрования сообщений, что сделает зашифрованную историю чатов нечитаемой.", - "other_devices_logout_warning_2": "Если вы хотите сохранить доступ к истории общения в зашифрованных комнатах, настройте резервное копирование ключей или экспортируйте ключи сообщений с одного из других ваших устройств, прежде чем продолжить.", + "other_devices_logout_warning_2": "Если вы хотите сохранить доступ к истории общения в зашифрованных чатах, настройте резервное копирование ключей или экспортируйте ключи сообщений с одного из других ваших устройств, прежде чем продолжить.", "password_not_entered": "Введите новый пароль.", "passwords_mismatch": "Новые пароли должны совпадать.", "rate_limit_error": "Слишком много попыток за короткое время. Подождите некоторое время, прежде чем повторить попытку.", @@ -329,7 +329,7 @@ "server_picker_title_default": "Параметры сервера", "server_picker_title_registration": "Ваша учётная запись обслуживается", "session_logged_out_description": "Для обеспечения безопасности ваш сеанс был завершён. Пожалуйста, войдите снова.", - "session_logged_out_title": "Выполнен выход", + "session_logged_out_title": "Сеанс удалён", "set_email": { "description": "Это позволит при необходимости сбросить пароль и получать уведомления.", "verification_pending_description": "Проверьте свою электронную почту и нажмите на ссылку в письме. После этого нажмите кнопку Продолжить.", @@ -359,7 +359,7 @@ "sso_complete_in_browser_dialog_title": "Перейдите в браузер для завершения входа", "sso_failed_missing_storage": "Мы попросили браузер запомнить, какой домашний сервер вы используете для входа в систему, но, к сожалению, ваш браузер забыл об этом. Перейдите на страницу входа и попробуйте ещё раз.", "sso_or_username_password": "%(ssoButtons)s или %(usernamePassword)s", - "sync_footer_subtitle": "Если вы присоединились к большому количеству комнат, это может занять некоторое время", + "sync_footer_subtitle": "Если вы присоединились к большому количеству чатов, это может занять некоторое время", "syncing": "Синхронизация…", "uia": { "code": "Код", @@ -369,8 +369,8 @@ "email_resent": "Отправлено повторно!", "fallback_button": "Начать аутентификацию", "mas_cross_signing_reset_cta": "Перейти к учетной записи", - "mas_cross_signing_reset_description": "Сбросьте свои данные через поставщика учетной записи, а затем вернитесь и нажмите «Повторить».", - "mas_cross_signing_reset_title": "Перейдите в свою учетную запись, чтобы сбросить свою личность", + "mas_cross_signing_reset_description": "Вы собираетесь перейти в свой аккаунт %(serverName)s, чтобы сбросить настройки своей идентификации. Как только вы завершите сброс настроек в аккаунте, вернитесь сюда и нажми «Повторить».", + "mas_cross_signing_reset_title": "Перейдите в свою учетную запись, чтобы сбросить идентификацию", "msisdn": "Текстовое сообщение отправлено на %(msisdn)s", "msisdn_token_incorrect": "Неверный код проверки", "msisdn_token_prompt": "Введите полученный код:", @@ -529,7 +529,7 @@ "offline": "Не в сети", "on": "Включить", "options": "Дополнительно", - "orphan_rooms": "Прочие комнаты", + "orphan_rooms": "Прочие чаты", "password": "Пароль", "people": "Люди", "preferences": "Параметры", @@ -537,20 +537,20 @@ "preview_message": "Эй! Ты лучший!", "privacy": "Конфиденциальность", "private": "Приватное", - "private_room": "Приватная комната", + "private_room": "Приватный чат", "private_space": "Приватное пространство", "profile": "Профиль", "public": "Публичное", - "public_room": "Публичная комната", + "public_room": "Публичный чат", "public_space": "Публичное пространство", "qr_code": "QR-код", "random": "Случайный", "reactions": "Реакции", "recommended": "Рекомендуемое", "report_a_bug": "Сообщить об ошибке", - "room": "Комната", - "room_name": "Название комнаты", - "rooms": "Комнаты", + "room": "Чат", + "room_name": "Название чата", + "rooms": "Чаты", "save": "Сохранить", "saved": "Сохранено", "saving": "Сохранение…", @@ -577,7 +577,7 @@ "unavailable": "недоступен", "unencrypted": "Не зашифровано", "unmute": "Вернуть право речи", - "unnamed_room": "Комната без названия", + "unnamed_room": "Чат без названия", "unnamed_space": "Безымянное пространство", "unverified": "Не заверено", "updating": "Обновление…", @@ -587,20 +587,20 @@ "verified": "Заверено", "version": "Версия", "video": "Видео", - "video_room": "Видеокомната", + "video_room": "Видеочат", "view_message": "Посмотреть сообщение", "voice": "Голос", "warning": "Внимание" }, "composer": { "autocomplete": { - "@room_description": "Уведомить всю комнату", + "@room_description": "Уведомить весь чат", "command_a11y": "Автозаполнение команды", "command_description": "Команды", "emoji_a11y": "Автодополнение смайлов", "notification_a11y": "Автозаполнение уведомлений", - "notification_description": "Уведомления комнаты", - "room_a11y": "Автозаполнение комнаты", + "notification_description": "Уведомления чата", + "room_a11y": "Автозаполнение чата", "space_a11y": "Автозаполнение пространства", "user_a11y": "Автозаполнение пользователя", "user_description": "Пользователи" @@ -629,7 +629,7 @@ }, "mode_plain": "Скрыть форматирование", "mode_rich_text": "Показать форматирование", - "no_perms_notice": "Вы не можете писать в эту комнату", + "no_perms_notice": "Вы не можете писать в этот чат", "placeholder": "Отправить незашифрованное сообщение...", "placeholder_encrypted": "Отправить зашифрованное сообщение…", "placeholder_reply": "Отправить незашифрованный ответ…", @@ -637,12 +637,12 @@ "placeholder_thread": "Ответить в небезопасном обсуждение…", "placeholder_thread_encrypted": "Ответить в обсуждение…", "poll_button": "Опрос", - "poll_button_no_perms_description": "У вас нет разрешения начинать опросы в этой комнате.", + "poll_button_no_perms_description": "У вас нет разрешения начинать опросы в этом чате", "poll_button_no_perms_title": "Требуется разрешение", "replying_title": "Отвечает", "room_unencrypted": "Сообщения в этой комнате не защищены сквозным шифрованием.", "room_upgraded_link": "Разговор продолжается здесь.", - "room_upgraded_notice": "Эта комната заменена и более неактивна.", + "room_upgraded_notice": "Этот чат заменён и более неактивен.", "send_button_title": "Отправить сообщение", "send_button_voice_message": "Отправить голосовое сообщение", "send_voice_message": "Отправить голосовое сообщение", @@ -653,33 +653,33 @@ "console_scam_warning": "Если кто-то сказал вам скопировать/вставить что-то здесь, велика вероятность, что вас пытаются обмануть!", "console_wait": "Подождите!", "create_room": { - "action_create_room": "Создать комнату", - "action_create_video_room": "Создать видеокомнату", - "encrypted_video_room_warning": "Вы не сможете отключить это позже. Комната будет зашифрована, а встроенный вызов — нет.", + "action_create_room": "Создать чат", + "action_create_video_room": "Создать видеочат", + "encrypted_video_room_warning": "Вы не сможете отключить это позже. Чат будет зашифрован, а встроенный вызов — нет.", "encrypted_warning": "Вы не сможете отключить это позже. Мосты и большинство ботов пока не будут работать.", - "encryption_forced": "Вашему серверу необходимо включить шифрование в приватных комнатах.", + "encryption_forced": "Вашему серверу необходимо включить шифрование в приватных чатах.", "encryption_label": "Включить сквозное шифрование", - "error_title": "Не удалось создать комнату", + "error_title": "Не удалось создать чат", "generic_error": "Возможно, сервер недоступен, перегружен или случилась ошибка.", - "join_rule_change_notice": "Вы можете изменить это в любое время из настроек комнаты.", - "join_rule_invite": "Приватная комната (только по приглашению)", - "join_rule_invite_label": "Только приглашенные люди смогут найти и присоединиться к этой комнате.", + "join_rule_change_notice": "Вы можете изменить это в любое время из настроек чата.", + "join_rule_invite": "Приватный чат (только по приглашению)", + "join_rule_invite_label": "Только приглашенные люди смогут найти и присоединиться к этому чату.", "join_rule_knock_label": "Любой желающий может подать заявку на участие, но администраторы или модераторы должны предоставить доступ. Это можно изменить позже.", - "join_rule_public_label": "Любой желающий сможет найти эту комнату и присоединиться к ней.", - "join_rule_public_parent_space_label": "Любой сможет найти и присоединиться к этой комнате, а не только участники .", + "join_rule_public_label": "Любой желающий сможет найти этот чат и присоединиться к нему.", + "join_rule_public_parent_space_label": "Любой сможет найти и присоединиться к этому чату, а не только участники .", "join_rule_restricted": "Стандартная", "join_rule_restricted_label": "Любой может присоединиться в .", - "name_validation_required": "Пожалуйста, введите название комнаты", - "room_visibility_label": "Видимость комнаты", + "name_validation_required": "Пожалуйста, введите название чата", + "room_visibility_label": "Видимость чата", "state_encryption_label": "Шифровать события состояния", - "title_private_room": "Создать приватную комнату", - "title_public_room": "Создать публичную комнату", + "title_private_room": "Создать приватный чат", + "title_public_room": "Создать групповой чат", "title_video_room": "Создайте видеокомнату", "topic_label": "Тема (опционально)", - "unfederated": "Запретить кому-либо, не входящему в %(serverName)s, когда-либо присоединяться к этой комнате.", + "unfederated": "Запретить кому-либо, не входящему в %(serverName)s, когда-либо присоединяться к этому чату.", "unfederated_label_default_off": "Вы можете включить это, если комната будет использоваться только для совместной работы с внутренними командами на вашем домашнем сервере. Это не может быть изменено позже.", "unfederated_label_default_on": "Вы можете отключить это, если комната будет использоваться для совместной работы с внешними командами, у которых есть собственный домашний сервер. Это не может быть изменено позже.", - "unsupported_version": "Сервер не поддерживает указанную версию комнаты." + "unsupported_version": "Сервер не поддерживает указанную версию чата." }, "create_space": { "add_details_prompt": "Добавьте некоторые подробности, чтобы помочь людям узнать его.", @@ -689,11 +689,11 @@ "address_label": "Адрес", "address_placeholder": "например, my-space", "creating": "Создание…", - "creating_rooms": "Создание комнат…", + "creating_rooms": "Создание чата…", "done_action": "В моё пространство", - "done_action_first_room": "Перейти в мою первую комнату", - "explainer": "Пространства — это новый способ организации комнат и людей. Какой вид пространства вы хотите создать? Вы можете изменить это позже.", - "failed_create_initial_rooms": "Не удалось создать первоначальные комнаты пространства", + "done_action_first_room": "Перейти в мой первый чат", + "explainer": "Пространства — это новый способ организации комнат. Какой тип пространства вы хотите создать?", + "failed_create_initial_rooms": "Не удалось создать первоначальные чаты пространства", "failed_invite_users": "Не удалось пригласить следующих пользователей в ваше пространство: %(csvUsers)s", "invite_teammates_by_username": "Пригласить по имени пользователя", "invite_teammates_description": "Убедитесь, что правильные люди имеют доступ. Вы можете пригласить больше людей позже.", @@ -703,20 +703,20 @@ "name_required": "Пожалуйста, введите название пространства", "personal_space": "Только я", "personal_space_description": "Приватное пространство для организации ваших комнат", - "private_description": "Только по приглашениям, лучший вариант для себя или команды", + "private_description": "Только по приглашению, для личного пользования или для команды.", "private_heading": "Ваше приватное пространство", "private_only_heading": "Ваше пространство", "private_personal_description": "Убедитесь, что правильные люди имеют доступ к %(name)s", "private_personal_heading": "С кем вы работаете?", "private_space": "Я и мои товарищи по команде", "private_space_description": "Приватное пространство для вас и ваших товарищей по команде", - "public_description": "Открытое пространство для всех, лучший вариант для сообществ", + "public_description": "Присоединиться может кто угодно, лучший вариант для создания сообществ.", "public_heading": "Ваше публичное пространство", "search_public_button": "Поиск публичных пространств", - "setup_rooms_community_description": "Давайте создадим для каждого из них отдельную комнату.", + "setup_rooms_community_description": "Для начала создайте несколько чатов.", "setup_rooms_community_heading": "Какие вещи вы хотите обсуждать в %(spaceName)s?", "setup_rooms_description": "Позже можно добавить и другие, в том числе уже существующие.", - "setup_rooms_private_description": "Мы создадим комнаты для каждого из них.", + "setup_rooms_private_description": "Для начала создайте несколько комнат.", "setup_rooms_private_heading": "Над какими проектами ваша команда работает?", "share_description": "Сейчас здесь только ты, с другими будет ещё лучше.", "share_heading": "Поделиться %(name)s", @@ -738,16 +738,16 @@ }, "decline_invitation_dialog": { "confirm": "Вы действительно хотите отклонить приглашение присоединиться \"%(roomName)s\"?", - "ignore_user_help": "Вы не увидите сообщений или приглашений в комнату от этого пользователя.", + "ignore_user_help": "Вы не увидите сообщений или приглашений в чатах от этого пользователя.", "reason_description": "Опишите причину сообщения о проблеме.", - "report_room_description": "Сообщите об этой комнате своему поставщику учетной записи.", + "report_room_description": "Сообщите об этом чате своему поставщику учетной записи.", "title": "Отклонить приглашение" }, "desktop_default_device_name": "%(brand)s Рабочий стол: %(platformName)s", "devtools": { "active_widgets": "Активные виджеты", "category_other": "Другие", - "category_room": "Комната", + "category_room": "Чат", "caution_colon": "Предупреждение:", "client_versions": "Версия клиента", "crypto": { @@ -810,9 +810,10 @@ "event_sent": "Событие отправлено!", "event_type": "Тип события", "expired": "Срок действия истек", + "expires_in": "Истекает через", "explore_account_data": "Посмотреть данные учётной записи", - "explore_room_account_data": "Посмотреть данные учётной записи комнаты", - "explore_room_state": "Посмотреть состояние комнаты", + "explore_room_account_data": "Посмотреть данные учётной записи чата", + "explore_room_state": "Посмотреть состояние чата", "failed_to_find_widget": "При обнаружении этого виджета произошла ошибка.", "failed_to_load": "Не удалось загрузить.", "failed_to_save": "Не удалось сохранить настройки.", @@ -832,9 +833,9 @@ "only_joined_members": "Только зарегистрированные пользователи", "original_event_source": "Оригинальный исходный код", "restore_from_backup": "Восстановить из резервной копии", - "room_encrypted": "Комната зашифрована ✅", - "room_id": "ID комнаты: %(roomId)s", - "room_not_encrypted": "Комната не имеет шифрования 🚨", + "room_encrypted": "Чат зашифрован ✅", + "room_id": "ID чата: %(roomId)s", + "room_not_encrypted": "Чат не имеет шифрования 🚨", "room_notifications_dot": "Точка: ", "room_notifications_highlight": "Выделение: ", "room_notifications_last_event": "Последнее событие:", @@ -842,7 +843,7 @@ "room_notifications_thread_id": "Id обсуждения: ", "room_notifications_total": "Всего: ", "room_notifications_type": "Тип: ", - "room_status": "Статус комнаты", + "room_status": "Статус чата", "room_unread_status_count": { "one": "Статус непрочитанной комнаты: %(status)s, количество: %(count)s", "few": "Статус непрочитанных комнат: %(status)s, количество: %(count)s", @@ -851,13 +852,13 @@ "save_setting_values": "Сохранить значения настроек", "see_history": "Посмотреть историю", "send_custom_account_data_event": "Отправить пользовательское событие данных учётной записи", - "send_custom_room_account_data_event": "Отправить пользовательское событие данных учётной записи комнаты", + "send_custom_room_account_data_event": "Отправить пользовательское событие данных учётной записи чата", "send_custom_state_event": "Оправить пользовательское событие состояния", "send_custom_timeline_event": "Отправить пользовательское событие ленты сообщений", "server_info": "Информация сервера", "server_versions": "Версия сервера", "settable_global": "Устанавливается на глобальном уровне", - "settable_room": "Устанавливается для комнаты", + "settable_room": "Устанавливается для чата", "setting_colon": "Настройки:", "setting_definition": "Установка определения:", "setting_id": "ID настроек", @@ -898,7 +899,7 @@ "users": "Пользователи", "value": "Значение", "value_colon": "Значение:", - "value_in_this_room": "Значение в этой комнате", + "value_in_this_room": "Значение в этом чате", "value_this_room_colon": "Значение в этой комнате:", "values_explicit": "Значения на явных уровнях", "values_explicit_colon": "Значения на явных уровнях:", @@ -959,7 +960,7 @@ "event_shield_reason_unverified_identity": "Зашифровано неподтвержденным пользователем.", "export_unsupported": "Ваш браузер не поддерживает необходимые криптографические расширения", "forgot_recovery_key": "Забыли ключ восстановления?", - "identity_needs_reset_description": "Для обеспечения доступа к истории сообщений необходимо сбросить криптографическую идентификацию.", + "identity_needs_reset_description": "Для обеспечения доступа к истории сообщений необходимо сбросить идентификацию.", "import_invalid_keyfile": "Недействительный файл ключей %(brand)s", "import_invalid_passphrase": "Ошибка аутентификации: возможно, неправильный пароль?", "key_storage_out_of_sync": "Хранилище ключей не синхронизировано.", @@ -986,7 +987,7 @@ "title": "Метод восстановления удален", "warning": "Если вы не убрали метод восстановления, злоумышленник может получить доступ к вашей учётной записи. Смените пароль учётной записи и сразу задайте новый способ восстановления в настройках." }, - "set_up_recovery": "Настроить восстановление", + "set_up_recovery": "Создать резервную копию своих чатов.", "set_up_recovery_toast_description": "Создайте ключ восстановления, который можно использовать для восстановления зашифрованной истории сообщений в случае потери доступа к своим устройствам.", "set_up_toast_title": "Настроить безопасное резервное копирование", "setup_secure_backup": { @@ -1114,7 +1115,7 @@ "cannot_load_config": "Не удалось загрузить файл конфигурации. Попробуйте обновить страницу.", "connection": "Возникла проблема при обмене данными с домашним сервером. Повторите попытку позже.", "dialog_description_default": "Произошла ошибка.", - "download_media": "Не удалось загрузить исходный медиафайл, исходный URL-адрес не найден", + "download_media": "Не удалось скачать исходный медиафайл, исходный URL-адрес не найден", "edit_history_unsupported": "Ваш сервер, похоже, не поддерживает эту возможность.", "failed_copy": "Не удалось скопировать", "hs_blocked": "Доступ к этому домашнему серверу заблокирован вашим администратором.", @@ -1129,7 +1130,7 @@ "non_urgent_echo_failure_toast": "Ваш сервер не отвечает на некоторые запросы.", "resource_limits": "Превышен один из лимитов на ресурсы сервера.", "session_restore": { - "clear_storage_button": "Очистить хранилище и выйти", + "clear_storage_button": "Удалить это устройство", "clear_storage_description": "Выйти и удалить ключи шифрования?", "description_1": "Произошла ошибка при попытке восстановить предыдущий сеанс.", "description_2": "Если вы использовали более новую версию %(brand)s, то ваш сеанс может быть несовместим с ней. Закройте это окно и вернитесь к более новой версии.", @@ -1549,7 +1550,8 @@ "render_reaction_images": "Обработка пользовательских изображений в реакциях", "render_reaction_images_description": "Иногда их называют \"пользовательскими эмодзи\".", "report_to_moderators": "Пожаловаться модераторам", - "report_to_moderators_description": "В поддерживающих модерирование комнатах, кнопка \"Пожаловаться\" позволит вам сообщить о нарушении модераторам комнаты.", + "report_to_moderators_description": "В поддерживающих модерирование чатах, кнопка \"Пожаловаться\" позволит вам сообщить о нарушении модераторам комнаты.", + "room_list_sections": "Разделы списка чатов", "share_history_on_invite": "Поделиться зашифрованной историей с новыми участниками", "share_history_on_invite_description": "Приглашая пользователя в зашифрованную комнату, для которой установлена видимость истории как «общая», поделитесь зашифрованной историей с этим пользователем и примите зашифрованную историю, когда вас приглашают в такую комнату.", "share_history_on_invite_warning": "Эта функция ЭКСПЕРИМЕНТАЛЬНАЯ и в ней реализованы не все меры безопасности. Не включайте её в рабочих учётных записях.", @@ -1754,6 +1756,11 @@ "failed_send_poll_title": "Не удалось отправить опрос", "notes": "Результаты отображаются только после завершения опроса", "option_label": "Вариант %(number)s, %(answer)s", + "option_label_with_total": { + "one": "Вариант %(number)s, %(answer)s, %(count)s голос", + "few": "Вариант %(number)s, %(answer)s, %(count)s голоса", + "many": "Вариант %(number)s, %(answer)s, %(count)s голосов" + }, "options_add_button": "Добавить вариант", "options_heading": "Создать варианты", "options_label": "Вариант %(number)s", @@ -1976,6 +1983,7 @@ "error_join_incompatible_version_1": "К сожалению, ваш домашний сервер слишком старый для участия.", "error_join_incompatible_version_2": "Пожалуйста, свяжитесь с администратором вашего сервера.", "error_join_title": "Не удалось войти", + "error_join_unknown": "Произошла неизвестная ошибка.", "error_jump_to_date": "Сервер вернул %(statusCode)s с кодом ошибки %(errorCode)s", "error_jump_to_date_connection": "При попытке найти указанную дату и перейти к ней, произошла сетевая ошибка. Возможно, ваш домашний сервер не работает или возникла временная проблема с подключением к Интернету. Пожалуйста, попробуйте еще раз. Если это продолжится, обратитесь к администратору homeserver.", "error_jump_to_date_details": "Сведения об ошибке", @@ -2005,7 +2013,9 @@ "few": "%(count)s пользователя запрашивают присоединения", "many": "%(count)s пользователей запрашивают присоединения" }, - "room_is_public": "Это публичная комната" + "room_is_public": "Это публичная комната", + "shared_history_tooltip": "Новые участники могут видеть историю", + "world_readable_history_tooltip": "Кто угодно может видеть историю" }, "header_avatar_open_settings_label": "Открыть настройки комнаты", "header_face_pile_tooltip": "Люди", @@ -2034,11 +2044,11 @@ "invite_email_mismatch_suggestion": "Введите адрес эл.почты в Настройках, чтобы получать приглашения прямо в %(brand)s.", "invite_sent_to_email": "Это приглашение было отправлено на %(email)s", "invite_sent_to_email_room": "Это приглашение в %(roomName)s было отправлено на %(email)s", - "invite_subtitle": " пригласил(а) вас", + "invite_subtitle": " приглашает вас", "invite_this_room": "Пригласить в комнату", "invite_title": "Хотите присоединиться к %(roomName)s?", "inviter_unknown": "Неизвестно", - "invites_you_text": " пригласил(а) тебя", + "invites_you_text": " приглашает вас", "join_button_account": "Зарегистрироваться", "join_failed_needs_invite": "Для просмотра %(roomName)s необходимо приглашение", "join_the_discussion": "Войти в комнату", @@ -2050,7 +2060,7 @@ "jump_read_marker": "Перейти к первому непрочитанному сообщению.", "jump_to_bottom_button": "Перейти к последним сообщениям", "kick_reason": "Причина: %(reason)s", - "kicked_by": "%(memberName)s исключил(а) вас", + "kicked_by": "%(memberName)s исключил вас", "kicked_from_room_by": "%(memberName)s удалил(а) вас из %(roomName)s", "knock_cancel_action": "Отменить запрос", "knock_denied_subtitle": "Поскольку вам отказали в доступе, вы не сможете присоединиться к группе, пока вас не пригласит администратор или модератор группы.", @@ -2079,6 +2089,7 @@ "button_view_all": "Посмотреть все", "description": "Закрепленные сообщения", "go_to_newest_message": "Посмотрите закрепленное сообщение на временной шкале и самое новое закрепленное сообщение здесь.", + "go_to_next_message": "Просмотрите это и предыдущее закрепленное сообщение в истории сообщений здесь", "title": "%(index)s из %(length)s Закрепленные сообщения" }, "read_topic": "Нажмите, чтобы увидеть тему", @@ -2129,8 +2140,9 @@ "home_menu_label": "Параметры раздела \"Главная\"", "join_public_room_label": "Присоединиться к публичной комнате", "joining_rooms_status": { - "one": "Сейчас вы состоите в %(count)s комнате", - "other": "Сейчас вы состоите в %(count)s комнатах" + "one": "Сейчас вы состоите в %(count)s чате", + "few": "Сейчас вы состоите в %(count)s чатах", + "many": "Сейчас вы состоите в %(count)s чатах" }, "list_title": "Список комнат", "more_options": { @@ -2141,6 +2153,11 @@ "one": "Удаляются сообщения в %(count)s комнате", "other": "Удаляются сообщения в %(count)s комнатах" }, + "section": { + "chats": "Чаты", + "favourites": "Избранное", + "low_priority": "Низкий приоритет" + }, "show_less": "Показать меньше", "show_n_more": { "other": "Показать ещё %(count)s", @@ -2214,8 +2231,6 @@ "aliases_section": "Адреса комнаты", "avatar_field_label": "Аватар комнаты", "canonical_alias_field_label": "Главный адрес", - "default_url_previews_off": "Предпросмотр ссылок по умолчанию выключен для участников этой комнаты.", - "default_url_previews_on": "Предпросмотр ссылок по умолчанию включен для участников этой комнаты.", "description_space": "Редактировать настройки, относящиеся к вашему пространству.", "error_creating_alias_description": "При создании этого адреса произошла ошибка. Это может быть запрещено сервером или произошел временный сбой.", "error_creating_alias_title": "Ошибка при создании адреса", @@ -2246,12 +2261,7 @@ "published_aliases_explainer_space": "Опубликованные адреса могут быть использованы любым человеком на любом сервере для присоединения к вашему пространству.", "published_aliases_section": "Публичные адреса", "save": "Сохранить изменения", - "topic_field_label": "Тема комнаты", - "url_preview_encryption_warning": "В зашифрованных комнатах, подобных этой, предварительный просмотр URL-адресов отключен по умолчанию, чтобы гарантировать, что ваш сервер (где создаются предварительные просмотры) не может собирать информацию о ссылках, которые вы видите в этой комнате.", - "url_preview_explainer": "Когда кто-то вставляет URL-адрес в свое сообщение, то можно просмотреть его, чтобы получить дополнительную информацию об этой ссылке, такую как название, описание и изображение с веб-сайта.", - "url_previews_section": "Предпросмотр содержимого ссылок", - "user_url_previews_default_off": "Предпросмотр ссылок по умолчанию выключен для вас.", - "user_url_previews_default_on": "Предпросмотр ссылок по умолчанию включен для вас." + "topic_field_label": "Тема комнаты" }, "notifications": { "browse_button": "Просматривать", @@ -2283,7 +2293,7 @@ "error_unbanning": "Не удалось разблокировать", "events_default": "Отправить сообщения", "invite": "Пригласить пользователей", - "kick": "Удалять пользователей", + "kick": "Исключить пользователей", "m.call": "Начать %(brand)s звонок", "m.call.member": "Присоединяйтесь к %(brand)s звонку", "m.reaction": "Отправлять реакции", @@ -2341,25 +2351,26 @@ "history_visibility_warning": "Изменения не затронут старые сообщения, только новые. Подробнее", "history_visibility_world_readable": "Любой (история общедоступна)", "join_rule_description": "Укажите, кто может присоединиться к %(roomName)s.", - "join_rule_invite": "Приватное (только по приглашению)", + "join_rule_invite": "Только по приглашению", "join_rule_invite_description": "Присоединиться могут только приглашенные люди.", "join_rule_knock": "Присоединиться", "join_rule_knock_description": "Люди не могут присоединиться до тех пор, пока им не будет предоставлен доступ.", - "join_rule_public_description": "Любой желающий может найти и присоединиться.", + "join_rule_public": "Любой", + "join_rule_public_description": "Любой желающий может присоединиться.", "join_rule_restricted": "Участники пространства", - "join_rule_restricted_description": "Любой человек в пространстве может найти и присоединиться. Укажите здесь, какие пространства могут получить доступ.", - "join_rule_restricted_description_active_space": "Любой человек в может найти и присоединиться. Вы можете выбрать и другие пространства.", - "join_rule_restricted_description_prompt": "Любой человек в пространстве может найти и присоединиться. Вы можете выбрать несколько пространств.", - "join_rule_restricted_description_spaces": "Пространства с доступом", + "join_rule_restricted_description": "Любой человек в пространстве может присоединиться без приглашения. Укажите здесь, какие пространства могут получить доступ.", + "join_rule_restricted_description_active_space": "Любой человек в может присоединиться.", + "join_rule_restricted_description_prompt": "Любой человек в пространстве может присоединиться.", + "join_rule_restricted_description_spaces": "Разрешённые пространства", "join_rule_restricted_dialog_description": "Определите, какие пространства могут получить доступ к этой комнате. Если пространство выбрано, его члены могут найти и присоединиться к .", "join_rule_restricted_dialog_empty_warning": "Вы удаляете все пространства. Доступ будет по умолчанию только по приглашениям", "join_rule_restricted_dialog_filter_placeholder": "Поиск пространств", - "join_rule_restricted_dialog_heading_known": "Другие пространства, которые вы знаете", - "join_rule_restricted_dialog_heading_other": "Другие пространства или комнаты, которые вы могли не знать", - "join_rule_restricted_dialog_heading_room": "Пространства, которые вы знаете, уже содержат эту комнату", + "join_rule_restricted_dialog_heading_known": "Ваши пространства, которые не содержат эту комнату", + "join_rule_restricted_dialog_heading_other": "Другие пространства, в которых вы не участвуете", + "join_rule_restricted_dialog_heading_room": "Ваши пространства, которые содержат эту комнату", "join_rule_restricted_dialog_heading_space": "Пространства, которые вы знаете, уже содержат эту комнату", "join_rule_restricted_dialog_heading_unknown": "Это, скорее всего, те, в которых участвуют другие администраторы комнат.", - "join_rule_restricted_dialog_title": "Выберите места", + "join_rule_restricted_dialog_title": "Выберите пространства", "join_rule_restricted_n_more": { "one": "и еще %(count)s", "few": "и еще %(count)s", @@ -2396,7 +2407,7 @@ "upload_avatar_label": "Загрузить аватар", "visibility": { "alias_section": "Адрес", - "error_failed_save": "Не удалось обновить видимость этого пространства", + "error_failed_save": "Не удалось обновить параметры этого пространства", "error_update_guest_access": "Не удалось обновить гостевой доступ к этому пространству", "error_update_history_visibility": "Не удалось обновить видимость истории этого пространства", "guest_access_disabled": "У тебя нет прав на изменение гостевого доступа.", @@ -2406,7 +2417,7 @@ "history_visibility_anyone_space_description": "Дайте людям возможность предварительно ознакомиться с вашим пространством, прежде чем они присоединятся к нему.", "history_visibility_anyone_space_disabled": "У тебя нет прав на изменение видимости истории.", "history_visibility_anyone_space_recommendation": "Рекомендуется для публичных пространств.", - "title": "Видимость" + "title": "Безопасность и приватность" }, "voip": { "call_type_section": "Тип звонка", @@ -2476,7 +2487,7 @@ "settings": { "account": { "dialog_title": "Настройки: Учетная запись", - "title": "Учетная запись" + "title": "Аккаунт" }, "all_rooms_home": "Показывать все комнаты на Главной", "all_rooms_home_description": "Все комнаты, в которых вы находитесь, будут отображаться на Главной.", @@ -2490,8 +2501,8 @@ "custom_font_name": "Название системного шрифта", "custom_font_size": "Использовать другой размер", "custom_theme_add": "Добавить пользовательскую тему", - "custom_theme_downloading": "Загрузка пользовательской темы…", - "custom_theme_error_downloading": "Ошибка при загрузке темы", + "custom_theme_downloading": "Скачивание пользовательской темы…", + "custom_theme_error_downloading": "Ошибка при скачивании темы", "custom_theme_help": "Введите URL-адрес пользовательской темы, которую вы хотите применить.", "custom_theme_invalid": "Неверная схема темы.", "dialog_title": "Настройки: Внешний вид", @@ -2534,7 +2545,7 @@ "export_keys": "Экспортировать ключи", "import_keys": "Импортировать ключи", "other_people_device_description": "Внимание: пользователи, которые явно не подтвердили вашу личность (например, с помощью эмодзи), не получат ваши зашифрованные сообщения. Кроме того, неверифицированные устройства верифицированных пользователей не будут получать ваши зашифрованные сообщения.", - "other_people_device_label": "В зашифрованных комнатах отправляйте сообщения только проверенным пользователям", + "other_people_device_label": "В зашифрованных чатах отправляйте сообщения только проверенным пользователям", "other_people_device_title": "Устройства других людей", "reset_identity": "Сбросить криптографическую идентификацию", "reset_in_progress": "Выполняется сброс...", @@ -2575,7 +2586,7 @@ "key_storage_warning": "Хранилище ключей не синхронизировано. Нажмите кнопку ниже, чтобы устранить проблему.", "save_key_description": "Не сообщайте эту информацию никому!", "save_key_title": "Ключ восстановления", - "set_up_recovery": "Настройка восстановления", + "set_up_recovery": "Получить ключ восстановления", "set_up_recovery_confirm_button": "Завершить настройку", "set_up_recovery_confirm_description": "Введите ключ восстановления, показанный на предыдущем экране, чтобы завершить настройку восстановления.", "set_up_recovery_confirm_title": "Для подтверждения введите ключ восстановления", @@ -2677,8 +2688,7 @@ "username": "Имя пользователя" }, "inline_url_previews_default": "Предпросмотр ссылок по умолчанию", - "inline_url_previews_room": "Включить предпросмотр ссылок для участников этой комнаты по умолчанию", - "inline_url_previews_room_account": "Включить предпросмотр ссылок в этой комнате (влияет только на вас)", + "inline_url_previews_encrypted": "Включить предварительный просмотр в зашифрованных комнатах", "insert_trailing_colon_mentions": "Вставлять двоеточие после упоминания пользователя в начале сообщения", "invite_controls": { "default_label": "Разрешить пользователям приглашать вас в комнаты" @@ -2823,7 +2833,7 @@ "rm_lifetime_offscreen": "Задержка прочтения сообщения при отсутствии активности (мс)", "room_directory_heading": "Каталог комнат", "room_list_heading": "Список комнат", - "show_avatars_pills": "Показывать аватары в упоминаниях пользователей, комнатах и событиях", + "show_avatars_pills": "Показывать аватары в упоминаниях пользователей, чатах и событиях", "show_polls_button": "Показывать кнопку опроса", "startup_window_behaviour_label": "Запуск и поведение окна", "surround_text": "Обводить выделенный текст при вводе специальных символов", @@ -2841,7 +2851,7 @@ "dehydrated_device_enabled": "Устройство в автономном режиме", "dialog_title": "Настройки: Безопасность и конфиденциальность", "e2ee_default_disabled_warning": "Администратор вашего сервера отключил сквозное шифрование по умолчанию в приватных комнатах и диалогах.", - "enable_message_search": "Включить поиск сообщений в зашифрованных комнатах", + "enable_message_search": "Включить поиск сообщений в зашифрованных чатах", "encryption_section": "Шифрование", "ignore_users_empty": "У вас нет игнорируемых пользователей.", "ignore_users_section": "Игнорируемые пользователи", @@ -2861,7 +2871,7 @@ "message_search_pending_rooms": "Комнаты, ожидающие индексации: %(pendingRooms)s", "message_search_room_progress": "%(doneRooms)s из %(totalRooms)s", "message_search_section": "Поиск по сообщениям", - "message_search_sleep_time": "Как быстро сообщения должны быть загружены.", + "message_search_sleep_time": "Как быстро сообщения должны быть скачаны.", "message_search_space_used": "Занято места:", "message_search_unsupported": "Отсутствуют некоторые необходимые компоненты для %(brand)s, чтобы безопасно кэшировать шифрованные сообщения локально. Если вы хотите попробовать эту возможность, соберите самостоятельно %(brand)s Desktop с добавлением поисковых компонентов.", "message_search_unsupported_web": "%(brand)s не может безопасно кэшировать зашифрованные сообщения локально во время работы в веб-браузере. Используйте %(brand)s Desktop, чтобы зашифрованные сообщения появились в результатах поиска.", @@ -2938,7 +2948,7 @@ "rename_form_caption": "Пожалуйста, имейте в виду, что названия сеансов также видны людям, с которыми вы общаетесь.", "rename_form_heading": "Переименовать сеанс", "rename_form_learn_more": "Переименование сеансов", - "rename_form_learn_more_description_1": "Другие пользователи, будучи в личных сообщениях и посещаемых вами комнатах, могут видеть полный перечень ваших сеансов.", + "rename_form_learn_more_description_1": "Другие пользователи, будучи в личных сообщениях и посещаемых вами чатах, могут видеть полный перечень ваших сеансов.", "rename_form_learn_more_description_2": "Это даёт им уверенности в том, с кем они общаются, но также означает, что они могут видеть вводимое здесь название сеанса.", "security_recommendations": "Рекомендации по безопасности", "security_recommendations_description": "Усильте защиту учётной записи, следуя этим рекомендациям.", @@ -2964,7 +2974,7 @@ "unknown_session": "Неизвестный тип сеанса", "unverified_session": "Незаверенный сеанс", "unverified_session_explainer_1": "Этот сеанс не поддерживает шифрование, потому и не может быть подтверждён.", - "unverified_session_explainer_2": "Через этот сеанс вы не можете участвовать в комнатах с шифрованием.", + "unverified_session_explainer_2": "Через этот сеанс вы не можете участвовать в чатах с шифрованием.", "unverified_session_explainer_3": "Для лучшей безопасности и конфиденциальности, рекомендуется использовать клиенты Matrix с поддержкой шифрования.", "unverified_sessions": "Незаверенные сеансы", "unverified_sessions_explainer_1": "Неподтверждённые сеансы — это сеансы, вошедшие с вашими учётными данными, но до сих пор не подтверждённые.", @@ -3029,11 +3039,13 @@ "connection_section": "Соединение", "dialog_title": "Настройки: Голос и видео", "echo_cancellation": "Эхоподавление", + "echo_cancellation_description": "Устраняет эхо микрофона во время звонков. Эта настройка также влияет на Element Call.", "enable_fallback_ice_server": "Разрешить резервный сервер помощи при вызове (%(server)s)", "enable_fallback_ice_server_description": "Только применяется, когда у домашнего сервера нет своего TURN-сервера. Ваш IP-адрес будет виден на время звонка.", "mirror_local_feed": "Зеркально отражать видео со своей камеры", "missing_permissions_prompt": "Отсутствуют разрешения для доступа к камере/микрофону. Нажмите кнопку ниже, чтобы запросить их.", "noise_suppression": "Подавление шума", + "noise_suppression_description": "Уменьшает фоновый шум микрофона во время звонков. Эта настройка также влияет на Element Call.", "request_permissions": "Запросить доступ к медиа устройству", "title": "Голос и видео", "video_input_empty": "Веб-камера не обнаружена", @@ -3100,7 +3112,7 @@ "manual_device_verification_confirm_title": "Внимание: ручная проверка устройства", "me": "Отображение действий", "msg": "Отправить сообщение данному пользователю", - "myavatar": "Меняет изображение профиля во всех комнатах", + "myavatar": "Меняет изображение профиля во всех чатах", "myroomavatar": "Меняет изображение профиля только в текущей комнате", "myroomnick": "Изменяет ваш псевдоним только для текущей комнаты", "nick": "Изменяет ваш псевдоним", @@ -3341,9 +3353,9 @@ "unable_to_decrypt": "Не удалось расшифровать сообщение" }, "disambiguated_profile": "%(displayName)s (%(matrixId)s)", - "download_action_downloading": "Загрузка", - "download_failed": "Загрузка не удалась", - "download_failed_description": "Произошла ошибка при загрузке этого файла", + "download_action_downloading": "Скачивание", + "download_failed": "Скачивание не удалось", + "download_failed_description": "Произошла ошибка при скачивании этого файла", "e2e_state": "Состояние сквозного шифрования", "edits": { "tooltip_label": "Изменено %(date)s. Нажмите для посмотра истории изменений.", @@ -3362,7 +3374,7 @@ "unable_to_find": "Попытка загрузить выбранный интервал истории чата этой комнаты не удалась, так как запрошенный элемент не найден." }, "m.audio": { - "error_downloading_audio": "Ошибка загрузки аудио", + "error_downloading_audio": "Ошибка скачивания аудио", "error_processing_audio": "Ошибка обработки звукового сообщения", "error_processing_voice_message": "Ошибка при обработке голосового сообщения" }, @@ -3459,7 +3471,7 @@ "m.room.history_visibility": { "invited": "%(senderName)s сделал(а) историю разговора видимой для всех собеседников с момента их приглашения.", "joined": "%(senderName)s сделал(а) историю разговора видимой для всех собеседников с момента их входа в комнату.", - "shared": "%(senderName)s сделал(а) историю разговора видимой для всех собеседников.", + "shared": "%(senderName)s делает историю разговора видимой для всех собеседников.", "unknown": "%(senderName)s сделал(а) историю комнаты видимой в неизвестном режиме (%(visibility)s).", "world_readable": "%(senderName)s сделал(а) историю разговора видимой для всех." }, @@ -3481,11 +3493,11 @@ "change_name_avatar": "%(oldDisplayName)s изменил(а) имя и аватар", "invite": "%(senderName)s пригласил(а) %(targetName)s", "join": "%(targetName)s теперь с нами", - "kick": "%(senderName)s удалил(а) %(targetName)s", - "kick_reason": "%(senderName)s удалил(а) %(targetName)s: %(reason)s", + "kick": "%(senderName)s исключает %(targetName)s", + "kick_reason": "%(senderName)s исключил %(targetName)s: %(reason)s", "left": "%(targetName)s покинул(а) комнату", "left_reason": "%(targetName)s покинул(а) комнату: %(reason)s", - "no_change": "%(senderName)s не сделал(а) изменений", + "no_change": "%(senderName)s не сделано изменений", "reject_invite": "%(targetName)s отклонил(а) приглашение", "reject_invite_reason": "%(targetName)s отклонил приглашение: %(reason)s", "remove_avatar": "%(senderName)s удалил(а) аватар", @@ -3651,8 +3663,9 @@ "one": "%(oneUser)s присоединился(лась)" }, "joined_and_left": { - "other": "%(oneUser)s присоединился(лась) и покинул(а) %(count)s раз(а)", - "one": "%(oneUser)s присоединился(лась) и покинул(а)" + "one": "%(oneUser)s присоединяется и покидает", + "few": "%(oneUser)s присоединяется и покидает %(count)s раза", + "many": "%(oneUser)s присоединяется и покидает %(count)s раз" }, "joined_and_left_multiple": { "other": "%(severalUsers)s присоединились и покинули %(count)s раз(а)", @@ -3663,16 +3676,18 @@ "one": "%(severalUsers)s присоединились" }, "kicked": { - "one": "был удалён", - "other": "удалено %(count)s раз(а)" + "one": "исключили", + "few": "исключили %(count)s раза", + "many": "исключили %(count)s раз" }, "kicked_multiple": { "one": "были удалены", "other": "удалены %(count)s раз(а)" }, "left": { - "other": "%(oneUser)s покинул(а) %(count)s раз(а)", - "one": "%(oneUser)s покинул(а)" + "one": "%(oneUser)s покидает", + "few": "%(oneUser)s покидает %(count)s раза", + "many": "%(oneUser)s покидает %(count)s раз" }, "left_multiple": { "other": "%(severalUsers)s покинули %(count)s раз(а)", @@ -3753,7 +3768,7 @@ "changelog": "История изменений", "check_action": "Проверить наличие обновлений", "checking": "Проверка наличия обновлений…", - "downloading": "Загрузка обновления…", + "downloading": "Скачивание обновления…", "error_encountered": "Обнаружена ошибка (%(errorDetail)s).", "error_unable_load_commit": "Не возможно загрузить детали подтверждения:: %(msg)s", "new_version_available": "Доступна новая версия. Обновить сейчас.", @@ -3808,7 +3823,7 @@ "disinvite_button_space": "Отозвать приглашение в пространство", "error_ban_user": "Не удалось заблокировать пользователя", "error_deactivate": "Не удалось деактивировать пользователя", - "error_kicking_user": "Не удалось удалить пользователя", + "error_kicking_user": "Не удалось исключить пользователя", "error_mute_user": "Не удалось заглушить пользователя", "error_revoke_3pid_invite_description": "Не удалось отозвать приглашение. Возможно, на сервере возникла вре́менная проблема или у вас недостаточно прав для отзыва приглашения.", "error_revoke_3pid_invite_title": "Не удалось отменить приглашение", @@ -3817,11 +3832,11 @@ "ignore_confirm_title": "Игнорировать %(user)s", "invited_by": "Приглашен %(sender)s", "jump_to_rr_button": "Перейти к последнему прочитанному сообщению", - "kick_button_room": "Удалить из комнаты", - "kick_button_room_name": "Удалить из %(roomName)s", + "kick_button_room": "Исключить из комнаты", + "kick_button_room_name": "Исключить из %(roomName)s", "kick_button_space": "Исключить из пространства", - "kick_button_space_everything": "Удалить их отовсюду, где я могу", - "kick_space_specific": "Удалить их из некоторых мест, где я могу", + "kick_button_space_everything": "Исключить участника отовсюду, где я могу", + "kick_space_specific": "Исключить участника из некоторых мест, где я могу", "kick_space_warning": "Они по-прежнему смогут получить доступ ко всему, где вы не являетесь администратором.", "promote_warning": "Вы не сможете отменить это действие, так как этот пользователь получит уровень прав, равный вашему.", "redact": { @@ -3845,7 +3860,7 @@ "room_encrypted": "Сообщения в этой комнате защищены сквозным шифрованием.", "room_encrypted_detail": "Ваши сообщения в безопасности, ключи для расшифровки есть только у вас и получателя.", "room_unencrypted": "Сообщения в этой комнате не защищены сквозным шифрованием.", - "room_unencrypted_detail": "В зашифрованных комнатах ваши сообщения в безопасности: только у вас и у получателя есть ключи для расшифровки.", + "room_unencrypted_detail": "В зашифрованных чатах ваши сообщения в безопасности: только у вас и у получателя есть ключи для расшифровки.", "send_message": "Отправить сообщение", "share_button": "Поделиться профилем", "unban_button_room": "Разблокировать в комнате", @@ -4021,8 +4036,8 @@ "see_videos_sent_active_room": "Посмотрите видео размещённые в вашей активной комнате", "see_videos_sent_this_room": "Посмотрите видео размещённые в этой комнате", "send_emotes_active_room": "Отправляйте эмоции от своего имени в активную комнату", - "send_emotes_this_room": "Отправляйте эмоции от своего имени в эту комнату", - "send_event_type_active_room": "Отправляйте %(eventType)s события от своего имени в вашей активной комнате", + "send_emotes_this_room": "Отправляйте смайлики от своего имени в это чат", + "send_event_type_active_room": "Отправляйте события %(eventType)s от своего имени в вашем активном чате", "send_event_type_this_room": "Отправляйте события %(eventType)s от своего имени в этой комнате", "send_files_active_room": "Отправьте файлы от своего имени в активной комнате", "send_files_this_room": "Отправьте файлы от своего имени в этой комнате", @@ -4053,7 +4068,7 @@ "remove": "Убрать для всех", "revoke": "Отозвать разрешения", "screenshot": "Сделать снимок", - "start_audio_stream": "Запустить аудио трансляцию" + "start_audio_stream": "Запустить аудиотрансляцию" }, "cookie_warning": "Этот виджет может использовать куки.", "error_hangup_description": "Вас отключили от звонка. (Ошибка: %(message)s)", @@ -4118,10 +4133,10 @@ "recentYears": "Последние года легко угадываемы", "sequences": "Последовательности типа abc или 6543 легко угадываемы", "similarToCommon": "Это похоже на распространённый пароль", - "simpleRepeat": "Повторы типа \"ааа\" легко угадываемы", + "simpleRepeat": "Повторы типа «ааа» легко угадываемы", "straightRow": "Прямые ряды клавиш легко угадываемы", - "topHundred": "Это топ-100 распространённых паролей", - "topTen": "Это топ-10 распространённых паролей", + "topHundred": "Это 100 самых распространённых паролей", + "topTen": "Это 10 самых распространённых паролей", "userInputs": "Не должно быть никаких личных данных или данных, связанных со страницами.", "wordByItself": "Общеупотребительные слова легко угадываемы" } diff --git a/apps/web/src/i18n/strings/sk.json b/apps/web/src/i18n/strings/sk.json index 47d4474b1c..3ba4c4db1e 100644 --- a/apps/web/src/i18n/strings/sk.json +++ b/apps/web/src/i18n/strings/sk.json @@ -2251,8 +2251,6 @@ "aliases_section": "Adresy miestnosti", "avatar_field_label": "Obrázok miestnosti", "canonical_alias_field_label": "Hlavná adresa", - "default_url_previews_off": "Náhľady URL adries sú predvolene zakázané pre členov tejto miestnosti.", - "default_url_previews_on": "Náhľady URL adries sú predvolene povolené pre členov tejto miestnosti.", "description_space": "Upravte nastavenia týkajúce sa vášho priestoru.", "error_creating_alias_description": "Pri vytváraní tejto adresy došlo k chybe. Je možné, že ju server nepovoľuje alebo došlo k dočasnému zlyhaniu.", "error_creating_alias_title": "Chyba pri vytváraní adresy", @@ -2283,12 +2281,7 @@ "published_aliases_explainer_space": "Zverejnené adresy môže použiť ktokoľvek na akomkoľvek serveri, aby sa pripojil k vášmu priestoru.", "published_aliases_section": "Zverejnené adresy", "save": "Uložiť zmeny", - "topic_field_label": "Téma miestnosti", - "url_preview_encryption_warning": "Náhľady URL adries sú v šifrovaných miestnostiach ako je táto predvolene zakázané, aby ste si mohli byť istí, že obsah odkazov z vašej konverzácii nebude zaznamenaný na vašom domovskom serveri počas ich generovania.", - "url_preview_explainer": "Ak niekto vo svojej správe pošle URL adresu, môže byť zobrazený jej náhľad obsahujúci názov, popis a obrázok z cieľovej web stránky.", - "url_previews_section": "Náhľady URL adries", - "user_url_previews_default_off": "Predvolene máte zakázané náhľady URL adries.", - "user_url_previews_default_on": "Predvolene máte povolené náhľady URL adries." + "topic_field_label": "Téma miestnosti" }, "notifications": { "browse_button": "Prechádzať", @@ -2713,8 +2706,6 @@ "username": "Používateľské meno" }, "inline_url_previews_default": "Predvolene povoliť náhľady URL adries", - "inline_url_previews_room": "Predvolene povoliť náhľady URL adries pre členov tejto miestnosti", - "inline_url_previews_room_account": "Povoliť náhľady URL adries pre túto miestnosť (ovplyvňuje len vás)", "insert_trailing_colon_mentions": "Vložiť na koniec dvojbodku za zmienkou používateľa na začiatku správy", "invite_controls": { "default_label": "Povoliť používateľom pozývať vás do miestností" diff --git a/apps/web/src/i18n/strings/sq.json b/apps/web/src/i18n/strings/sq.json index 7a41ab152a..cef1c7e45e 100644 --- a/apps/web/src/i18n/strings/sq.json +++ b/apps/web/src/i18n/strings/sq.json @@ -1786,8 +1786,6 @@ "aliases_section": "Adresa Dhomash", "avatar_field_label": "Avatar dhome", "canonical_alias_field_label": "Adresë kryesore", - "default_url_previews_off": "Për pjesëmarrësit në këtë dhomë paraparja e URL-ve është e çaktivizuar, si parazgjedhje.", - "default_url_previews_on": "Për pjesëmarrësit në këtë dhomë paraparja e URL-ve është e aktivizuar, si parazgjedhje.", "description_space": "Përpunoni rregullime që lidhen me hapësirën tuaj.", "error_creating_alias_description": "Pati një gabim në krijimin e asaj adrese. Mund të mos lejohet nga shërbyesi, ose ndodhi një gabim i përkohshëm.", "error_creating_alias_title": "Gabim në krijim adrese", @@ -1814,12 +1812,7 @@ "published_aliases_explainer_space": "Adresat e publikuara mund të përdoren nga cilido, në cilindo shërbyes, për të hyrë në hapësirën tuaj.", "published_aliases_section": "Adresa të Publikuara", "save": "Ruaji Ndryshimet", - "topic_field_label": "Temë Dhome", - "url_preview_encryption_warning": "Në dhoma të fshehtëzuara, si kjo, paraparja e URL-ve është e çaktivizuar, si parazgjedhje, për të garantuar që shërbyesi juaj home (ku edhe prodhohen paraparjet) të mos grumbullojë të dhëna rreth lidhjesh që shihni në këtë dhomë.", - "url_preview_explainer": "Kur dikush vë një URL në mesazh, për të dhënë rreth lidhjes më tepër të dhëna, të tilla si titulli, përshkrimi dhe një figurë e sajtit, do të shfaqet një paraparje e URL-së.", - "url_previews_section": "Paraparje URL-sh", - "user_url_previews_default_off": "E keni çaktivizuar, si parazgjedhje, paraparjen e URL-ve.", - "user_url_previews_default_on": "E keni aktivizuar, si parazgjedhje, paraparjen e URL-ve." + "topic_field_label": "Temë Dhome" }, "notifications": { "browse_button": "Shfletoni", @@ -2108,8 +2101,6 @@ "spell_check_locale_placeholder": "Zgjidhni vendore" }, "inline_url_previews_default": "Aktivizo, si parazgjedhje, paraparje URL-sh brendazi", - "inline_url_previews_room": "Aktivizo, si parazgjedhje, paraparje URL-sh për pjesëmarrësit në këtë dhomë", - "inline_url_previews_room_account": "Aktivizo paraparje URL-sh për këtë dhomë (prek vetëm ju)", "insert_trailing_colon_mentions": "Fut dy pika pas përmendjesh përdoruesi, në fillim të një mesazhi", "jump_to_bottom_on_send": "Kalo te fundi i rrjedhës kohore, kur dërgoni një mesazh", "key_backup": { diff --git a/apps/web/src/i18n/strings/sv.json b/apps/web/src/i18n/strings/sv.json index a50fddfe6c..48715ab1ae 100644 --- a/apps/web/src/i18n/strings/sv.json +++ b/apps/web/src/i18n/strings/sv.json @@ -2141,8 +2141,6 @@ "aliases_section": "Rumsadresser", "avatar_field_label": "Rumsavatar", "canonical_alias_field_label": "Huvudadress", - "default_url_previews_off": "URL-förhandsgranskning är inaktiverat som förval för deltagare i detta rum.", - "default_url_previews_on": "URL-förhandsgranskning är aktiverat som förval för deltagare i detta rum.", "description_space": "Redigera inställningar relaterat till ditt utrymme.", "error_creating_alias_description": "Ett fel inträffade vid skapande av adressen. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", "error_creating_alias_title": "Fel vid skapande av adress", @@ -2171,12 +2169,7 @@ "published_aliases_explainer_space": "Publicerade adresser kan användas av vem som helst på vilken server som helst för att gå med i ditt utrymme.", "published_aliases_section": "Publicerade adresser", "save": "Spara ändringar", - "topic_field_label": "Rumsämne", - "url_preview_encryption_warning": "I krypterade rum, som detta, är URL-förhandsgranskning inaktiverad som förval för att säkerställa att din hemserver (där förhandsgranskningar genereras) inte kan samla information om länkar du ser i rummet.", - "url_preview_explainer": "När någon lägger en URL i sitt meddelande, kan URL-förhandsgranskning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", - "url_previews_section": "URL-förhandsgranskning", - "user_url_previews_default_off": "Du har inaktiverat URL-förhandsgranskning som förval.", - "user_url_previews_default_on": "Du har aktiverat URL-förhandsgranskning som förval." + "topic_field_label": "Rumsämne" }, "notifications": { "browse_button": "Bläddra", @@ -2589,8 +2582,6 @@ "username": "Användarnamn" }, "inline_url_previews_default": "Aktivera inbäddad URL-förhandsgranskning som standard", - "inline_url_previews_room": "Aktivera URL-förhandsgranskning som standard för deltagare i detta rum", - "inline_url_previews_room_account": "Aktivera URL-förhandsgranskning för detta rum (påverkar bara dig)", "insert_trailing_colon_mentions": "Infoga kolon efter användaromnämnande på början av ett meddelande", "invite_controls": { "default_label": "Tillåt användare att bjuda in dig till rum" diff --git a/apps/web/src/i18n/strings/tr.json b/apps/web/src/i18n/strings/tr.json index 3b5b7e2a4f..142d45f61f 100644 --- a/apps/web/src/i18n/strings/tr.json +++ b/apps/web/src/i18n/strings/tr.json @@ -2078,8 +2078,6 @@ "aliases_section": "Oda Adresleri", "avatar_field_label": "Oda avatarı", "canonical_alias_field_label": "Ana adres", - "default_url_previews_off": "URL ön izlemeleri, bu odadaki kullanıcılar için varsayılan olarak devre dışı bıraktırılmıştır.", - "default_url_previews_on": "URL önizlemeleri, bu odadaki katılımcılar için varsayılan olarak etkin.", "description_space": "Alanınızla ilgili ayarları düzenleyin.", "error_creating_alias_description": "Adres oluşturulurken hata ile karşılaşıldı. Sunucu tarafından izin verilmemiş yada geçici bir hata olabilir.", "error_creating_alias_title": "Adres oluşturulurken hata", @@ -2108,12 +2106,7 @@ "published_aliases_explainer_space": "Yayınlanan adresler, herhangi bir sunucudaki herkes tarafından alanınıza katılmak için kullanılabilir.", "published_aliases_section": "Yayınlanmış adresler", "save": "Değişiklikleri Kaydet", - "topic_field_label": "Oda Başlığı", - "url_preview_encryption_warning": "Bunun gibi şifreli odalarda, ana sunucunuzun (önizlemelerin oluşturulduğu yer) bu odada gördüğünüz bağlantılar hakkında bilgi toplayamamasını sağlamak için URL önizlemeleri varsayılan olarak devre dışı bırakılır.", - "url_preview_explainer": "Birisi mesajına bir URL eklediğinde, bu bağlantı hakkında başlık, açıklama ve web sitesinden bir görüntü gibi daha fazla bilgi vermek için bir URL önizlemesi gösterilebilir.", - "url_previews_section": "URL önizlemeleri", - "user_url_previews_default_off": "URL önizlemelerini varsayılan olarak devre dışı bıraktınız.", - "user_url_previews_default_on": "URL önizlemelerini varsayılan olarak etkinleştirdiniz." + "topic_field_label": "Oda Başlığı" }, "notifications": { "browse_button": "Gözat", @@ -2510,8 +2503,6 @@ "username": "Kullanıcı Adı" }, "inline_url_previews_default": "Varsayılan olarak satır içi URL önizlemeleri aç", - "inline_url_previews_room": "Bu odadaki katılımcılar için URL önizlemeyi varsayılan olarak açık hale getir", - "inline_url_previews_room_account": "Bu oda için URL önizlemeyi aç (sadece sizi etkiler)", "insert_trailing_colon_mentions": "Mesajın başında kullanıcı etiketlerinden sonra iki nokta üst üste ekle", "jump_to_bottom_on_send": "Mesaj gönderdiğinizde zaman çizelgesinin en sonuna atla", "key_backup": { diff --git a/apps/web/src/i18n/strings/uk.json b/apps/web/src/i18n/strings/uk.json index 0ed408e2f9..d1a9e78af1 100644 --- a/apps/web/src/i18n/strings/uk.json +++ b/apps/web/src/i18n/strings/uk.json @@ -2215,8 +2215,6 @@ "aliases_section": "Адреси кімнати", "avatar_field_label": "Аватар кімнати", "canonical_alias_field_label": "Основна адреса", - "default_url_previews_off": "Попередній перегляд URL-адрес типово вимкнений для учасників цієї кімнати.", - "default_url_previews_on": "Попередній перегляд URL-адрес типово увімкнений для учасників цієї кімнати.", "description_space": "Змінити налаштування, що стосуються вашого простору.", "error_creating_alias_description": "Помилка створення такої адреси. Можливо, сервер цього не дозволяє або стався тимчасовий збій.", "error_creating_alias_title": "Помилка створення адреси", @@ -2247,12 +2245,7 @@ "published_aliases_explainer_space": "Загальнодоступні адреси можуть бути використані будь-ким на будь-якому сервері для приєднання до вашого простору.", "published_aliases_section": "Загальнодоступні адреси", "save": "Зберегти зміни", - "topic_field_label": "Тема кімнати", - "url_preview_encryption_warning": "У кімнатах з шифруванням, як у цій, попередній перегляд посилань усталено вимкнено. Це робиться, щоб гарантувати, що ваш домашній сервер (на якому генеруються перегляди) не матиме змоги збирати дані щодо посилань, які ви бачите у цій кімнаті.", - "url_preview_explainer": "Коли хтось додає URL-адресу у повідомлення, можливо автоматично показувати для цієї URL-адресу попередній перегляд його заголовку, опису й зображення.", - "url_previews_section": "Попередній перегляд URL-адрес", - "user_url_previews_default_off": "Ви вимкнули усталений попередній перегляд URL-адрес.", - "user_url_previews_default_on": "Ви увімкнули усталений попередній перегляд URL-адрес." + "topic_field_label": "Тема кімнати" }, "notifications": { "browse_button": "Огляд", @@ -2674,8 +2667,6 @@ "username": "Ім'я користувача" }, "inline_url_previews_default": "Увімкнути вбудований перегляд гіперпосилань за умовчанням", - "inline_url_previews_room": "Увімкнути попередній перегляд гіперпосилань за умовчанням для учасників цієї кімнати", - "inline_url_previews_room_account": "Увімкнути попередній перегляд гіперпосилань в цій кімнаті (стосується тільки вас)", "insert_trailing_colon_mentions": "Додавати двокрапку після згадки користувача на початку повідомлення", "invite_controls": { "default_label": "Дозволити користувачам запрошувати вас до кімнат" diff --git a/apps/web/src/i18n/strings/vi.json b/apps/web/src/i18n/strings/vi.json index 945a7054ea..109d09a3aa 100644 --- a/apps/web/src/i18n/strings/vi.json +++ b/apps/web/src/i18n/strings/vi.json @@ -1726,8 +1726,6 @@ "aliases_section": "Các địa chỉ Phòng", "avatar_field_label": "Hình đại diện phòng", "canonical_alias_field_label": "Địa chỉ chính", - "default_url_previews_off": "Xem trước URL bị tắt theo mặc định đối với những người tham gia trong phòng này.", - "default_url_previews_on": "Xem trước URL được bật theo mặc định cho những người tham gia trong phòng này.", "description_space": "Chỉnh sửa cài đặt liên quan đến space của bạn.", "error_creating_alias_description": "Đã xảy ra lỗi khi tạo địa chỉ đó. Nó có thể không được máy chủ cho phép hoặc xảy ra lỗi tạm thời.", "error_creating_alias_title": "Lỗi khi tạo địa chỉ", @@ -1754,12 +1752,7 @@ "published_aliases_explainer_space": "Địa chỉ đã xuất bản có thể được sử dụng bởi bất kỳ ai trên bất kỳ máy chủ nào để tham gia space của bạn.", "published_aliases_section": "Các địa chỉ công khai", "save": "Lưu thay đổi", - "topic_field_label": "Chủ đề phòng", - "url_preview_encryption_warning": "Trong các phòng được mã hóa, như phòng này, tính năng xem trước URL bị tắt theo mặc định để đảm bảo rằng máy chủ của bạn (nơi tạo bản xem trước) không thể thu thập thông tin về các liên kết mà bạn nhìn thấy trong phòng này.", - "url_preview_explainer": "Khi ai đó đặt URL trong tin nhắn của họ, bản xem trước URL có thể được hiển thị để cung cấp thêm thông tin về liên kết đó như tiêu đề, mô tả và hình ảnh từ trang web.", - "url_previews_section": "Xem trước URL", - "user_url_previews_default_off": "Bạn đã tắt disabled xem trước URL theo mặc định.", - "user_url_previews_default_on": "Bạn đã bật enabled URL xem trước URL theo mặc định." + "topic_field_label": "Chủ đề phòng" }, "notifications": { "browse_button": "Duyệt qua", @@ -2053,8 +2046,6 @@ "spell_check_locale_placeholder": "Chọn vùng miền" }, "inline_url_previews_default": "Bật xem trước nội dung liên kết theo mặc định", - "inline_url_previews_room": "Bật xem trước nội dung liên kết cho mọi người trong phòng này", - "inline_url_previews_room_account": "Bật xem trước nội dung liên kết trong phòng này (chỉ với bạn)", "insert_trailing_colon_mentions": "Chèn dấu hai chấm phía sau các đề cập người dùng ở đầu một tin nhắn", "jump_to_bottom_on_send": "Chuyển đến cuối dòng thời gian khi bạn gửi tin nhắn", "key_backup": { diff --git a/apps/web/src/i18n/strings/zh_Hans.json b/apps/web/src/i18n/strings/zh_Hans.json index 1202dd5930..bc5eeb0696 100644 --- a/apps/web/src/i18n/strings/zh_Hans.json +++ b/apps/web/src/i18n/strings/zh_Hans.json @@ -1,24 +1,29 @@ { "a11y": { - "jump_first_invite": "跳转至第一个邀请。", + "emoji_picker": "Emoji 选择器", + "jump_first_invite": "跳转到首个邀请", + "message_composer": "消息编辑器", "n_unread_messages": { - "other": "%(count)s 个未读消息。", - "one": "1 个未读消息。" + "one": "1 个未读消息", + "other": "%(count)s 个未读消息。" }, "n_unread_messages_mentions": { - "other": "包括提及在内有 %(count)s 个未读消息。", - "one": "1 个未读提及。" + "one": "1 个未读提及", + "other": "%(count)s 个包含提及在内的未读消息" }, + "recent_rooms": "最近的房间", "room_name": "房间 %(name)s", + "room_status_bar": "房间状态栏", + "seek_bar_label": "音频搜索栏", "unread_messages": "未读消息。", "user_menu": "用户菜单" }, - "a11y_jump_first_unread_room": "跳转至第一个未读房间。", + "a11y_jump_first_unread_room": "跳转到首个未读房间。", "action": { "accept": "接受", "add": "添加", - "add_existing_room": "添加现有的房间", - "add_people": "加人", + "add_existing_room": "添加现有房间", + "add_people": "添加人员", "apply": "应用", "approve": "批准", "ask_to_join": "申请加入", @@ -39,6 +44,8 @@ "create_a_room": "创建房间", "create_account": "创建账户", "decline": "拒绝", + "decline_and_block": "拒绝并屏蔽", + "decline_invite": "拒绝邀请", "delete": "删除", "deny": "拒绝", "disable": "禁用", @@ -51,15 +58,15 @@ "enter_fullscreen": "进入全屏", "exit_fullscreeen": "退出全屏", "expand": "展开", - "explore_public_rooms": "查找公开房间", + "explore_public_rooms": "浏览公共房间", "explore_rooms": "查找房间", "export": "导出", "forward": "转发", "go": "前往", - "go_back": "返回", - "got_it": "知道了", - "hide_advanced": "隐藏高级", - "hold": "挂起", + "go_back": "后退", + "got_it": "明白", + "hide_advanced": "隐藏高级选项", + "hold": "保持", "ignore": "忽略", "import": "导入", "invite": "邀请", @@ -69,7 +76,7 @@ "learn_more": "了解更多", "leave": "离开", "leave_room": "离开房间", - "logout": "登出", + "logout": "注销", "manage": "管理", "maximise": "最大化", "mention": "提及", @@ -77,53 +84,56 @@ "new_room": "新建房间", "new_video_room": "新视频房间", "next": "下一个", - "no": "不", + "no": "否", "ok": "确定", "open": "打开", - "pin": "别针", + "pin": "置顶", "proceed": "继续", - "quote": "引述", + "quote": "引用", "react": "回应", "refresh": "刷新", "register": "注册", - "reload": "重加载", + "reload": "重新载入", "remove": "移除", "rename": "重命名", "reply": "回复", "reply_in_thread": "在消息列中回复", "report_content": "举报内容", + "report_room": "举报房间", "resend": "重新发送", "reset": "重置", "resume": "恢复", "retry": "重试", - "review": "开始验证", - "revoke": "撤销", + "review": "审阅", + "revoke": "撤消", "save": "保存", "search": "搜索", - "send_report": "发送报告", + "send_report": "发送举报", + "set_avatar": "设置个人资料图像", "share": "分享", "show": "显示", - "show_advanced": "显示高级", + "show_advanced": "显示高级选项", "show_all": "显示全部", "sign_in": "登录", - "sign_out": "注销", + "sign_out": "移除此设备", "skip": "跳过", "start": "开始", "start_chat": "开始聊天", - "start_new_chat": "开始新的聊天", + "start_new_chat": "开始新聊天", "stop": "停止", "submit": "提交", "subscribe": "订阅", "transfer": "传输", "trust": "信任", "try_again": "重试", - "unban": "解除封禁", - "unignore": "取消忽略", + "unban": "解封", + "unignore": "解除忽略", "unpin": "取消置顶", - "unsubscribe": "取消订阅", + "unsubscribe": "退订", "update": "更新", - "upgrade": "升级加密", + "upgrade": "升级", "upload": "上传", + "upload_file": "上传文件", "verify": "验证", "view": "查看", "view_all": "查看全部", @@ -131,334 +141,398 @@ "view_message": "查看消息", "view_source": "查看源码", "yes": "是", + "yes_dismiss": "是,忽略", "zoom_in": "放大", "zoom_out": "缩小" }, "analytics": { - "accept_button": "没问题", - "bullet_1": "我们不会记录或配置任何账户数据", - "bullet_2": "我们不会与第三方共享信息", - "consent_migration": "你之前同意与我们分享匿名使用数据。我们正在更新其工作方式。", - "disable_prompt": "您可以随时在设置中关闭此功能", + "accept_button": "良好", + "bullet_1": "我们记录或分析任何个人数据", + "bullet_2": "我们分享数据给第三方", + "consent_migration": "你已于之前同意我们分享匿名使用情况数据。我们正在更新其运作方式。", + "disable_prompt": "你可以随时在设置中关闭此选项。", "enable_prompt": "帮助改进 %(analyticsOwner)s", - "learn_more": "共享匿名数据帮助我们发现问题。无个人数据。 没有第三方。了解更多", - "privacy_policy": "你可以在此处阅读我们所有的条款", - "pseudonymous_usage_data": "通过共享匿名使用数据,帮助我们发现问题并改进%(analyticsOwner)s。为了了解人们如何使用多台设备,我们将生成一个由您的设备共享的随机标识符。", - "shared_data_heading": "以下数据之一可能被分享:" + "learn_more": "分享匿名数据已帮助我们识别问题。不涉及个人隐私及第三方。了解更多", + "privacy_policy": "你可以阅读我们的所有条款 点击此处", + "pseudonymous_usage_data": "通过分享匿名使用数据,帮助我们发现问题并改进 %(analyticsOwner)s。为了解用户如何使用多台设备,我们将生成一个由你的设备共享的随机标识符。", + "shared_data_heading": "以下任何数据都可能被分享:" }, "auth": { - "3pid_in_use": "该电子邮件地址或电话号码已被使用。", - "account_clash": "你的新账户(%(newAccountId)s)已注册,但你已经登录了一个不同的账户(%(loggedInUserId)s)。", - "account_clash_previous_account": "用之前的账户继续", + "3pid_in_use": "该邮件地址或电话号码已被使用。", + "account_clash": "你的新账户 (%(newAccountId)s) 已注册,但你已登录到其它账户 (%(loggedInUserId)s)。", + "account_clash_previous_account": "使用上一个账户继续", "account_deactivated": "此账户已被停用。", - "autodiscovery_generic_failure": "从服务器获取自动发现配置时失败", - "autodiscovery_hs_incompatible": "您的服务器版本太旧,不支持所需的最低API版本。请与服务器所有者联系,或者升级您的服务器。", - "autodiscovery_invalid": "无效的家服务器搜索响应", - "autodiscovery_invalid_hs": "家服务器链接不像是有效的 Matrix 家服务器", - "autodiscovery_invalid_hs_base_url": "m.homeserver 的 base_url 无效", - "autodiscovery_invalid_is": "身份服务器链接不像是有效的身份服务器", - "autodiscovery_invalid_is_base_url": "m.identity_server 的 base_url 无效", - "autodiscovery_invalid_is_response": "无效的身份服务器搜索响应", - "autodiscovery_invalid_json": "无效的 JSON", - "autodiscovery_no_well_known": "找不到.well-known JSON文件", - "autodiscovery_unexpected_error_hs": "解析家服务器配置时发生未知错误", - "autodiscovery_unexpected_error_is": "解析身份服务器配置时发生未知错误", - "captcha_description": "此家服务器想要确认你不是机器人。", - "change_password_action": "修改密码", - "change_password_confirm_invalid": "密码不匹配", + "autodiscovery_generic_failure": "从服务器获取自动发现配置失败", + "autodiscovery_hs_incompatible": "你的主服务器版本太旧,不支持所需的最低 API 版本。请联系你的服务器所有者或升级你的服务器。", + "autodiscovery_invalid": "主服务器发现响应无效", + "autodiscovery_invalid_hs": "主服务器 URL 似乎不是有效的 Matrix 主服务器", + "autodiscovery_invalid_hs_base_url": "“m.homeserver”中的“base_url”无效", + "autodiscovery_invalid_is": "身份服务器 URL 似乎不是有效的身份服务器", + "autodiscovery_invalid_is_base_url": "“m.identity_server”中的“base_url”无效", + "autodiscovery_invalid_is_response": "身份服务器发现响应无效", + "autodiscovery_invalid_json": "JSON 无效", + "autodiscovery_no_well_known": ".well-known JSON 文件未找到", + "autodiscovery_unexpected_error_hs": "解析主服务器配置时出现未知错误", + "autodiscovery_unexpected_error_is": "解析身份服务器配置时出现未知错误", + "captcha_description": "此主服务器需要确认你是否为机器人。", + "change_password_action": "更改密码", + "change_password_confirm_invalid": "两次输入的密码不匹配", "change_password_confirm_label": "确认密码", "change_password_current_label": "当前密码", "change_password_empty": "密码不能为空", "change_password_error": "更改密码时出错:%(error)s", - "change_password_mismatch": "两次输入的新密码不符", - "change_password_new_label": "新密码", - "check_email_explainer": "按已发到%(email)s的说明操作", - "check_email_resend_prompt": "没收到?", - "check_email_resend_tooltip": "验证链接电子邮件已重新发送!", - "check_email_wrong_email_button": "重新输入电子邮件地址", - "check_email_wrong_email_prompt": "电子邮件地址错误?", + "change_password_mismatch": "新密码不匹配", + "change_password_new_label": "新密码不匹配", + "check_email_explainer": "按说明发送到 %(email)s", + "check_email_resend_prompt": "未收到?", + "check_email_resend_tooltip": "验证链接邮件已重新发送!", + "check_email_wrong_email_button": "重新输入邮件地址", + "check_email_wrong_email_prompt": "邮件地址有误?", "continue_with_idp": "使用 %(provider)s 继续", "continue_with_sso": "使用 %(ssoButtons)s 继续", - "country_dropdown": "国家下拉菜单", + "country_dropdown": "国家与地区下拉菜单", "create_account_prompt": "新来的?创建账户", "create_account_title": "创建账户", - "email_discovery_text": "使用电子邮箱以选择性地被现有联系人搜索。", - "email_field_label": "电子邮箱", - "email_field_label_invalid": "看起来不像有效的邮件地址", - "email_field_label_required": "输入邮箱地址", - "email_help_text": "添加电子邮箱以重置你的密码。", - "email_phone_discovery_text": "使用电子邮箱或电话以选择性地被现有联系人搜索。", - "enter_email_explainer": "%(homeserver)s 会向您发送一个验证链接,让您重置密码。", - "enter_email_heading": "输入您的邮箱以重置您的密码", - "failed_connect_identity_server": "无法连接到身份服务器", - "failed_connect_identity_server_other": "你可以登录,但部分功能在身份服务器重新上线之前不可用。如果持续看到此警告,请检查配置或联系服务器管理员。", - "failed_connect_identity_server_register": "你可以注册,但部分功能在身份服务器重新上线之前不可用。如果持续看到此警告,请检查配置或联系服务器管理员。", - "failed_connect_identity_server_reset_password": "你可以重置密码,但部分功能在身份服务器重新上线之前不可用。如果持续看到此警告,请检查配置或联系服务器管理员。", - "failed_homeserver_discovery": "无法执行家服务器搜索", + "email_discovery_text": "使用邮件可选择让现有联系人发现。", + "email_field_label": "邮件", + "email_field_label_invalid": "似乎不是有效的邮件地址", + "email_field_label_required": "输入邮件地址", + "email_help_text": "添加邮件地址以用于重置密码", + "email_phone_discovery_text": "使用邮件地址或电话号码可选择让现有联系人发现。", + "enter_email_explainer": "%(homeserver)s 将向你发送验证链接以重置密码。", + "enter_email_heading": "输入邮件地址以重置密码", + "failed_connect_identity_server": "无法连接身份服务器", + "failed_connect_identity_server_other": "你可以登录,但某些功能在身份服务器恢复正常之前将不可用。如果你持续看到此警告,请检查你的配置或联系服务器管理员。", + "failed_connect_identity_server_register": "你可以注册,但某些功能在身份服务器恢复正常之前将不可用。如果你持续看到此警告,请检查你的配置或联系服务器管理员。", + "failed_connect_identity_server_reset_password": "你可以重置密码,但某些功能在身份服务器恢复正常之前将不可用。如果你持续看到此警告,请检查你的配置或联系服务器管理员。", + "failed_homeserver_discovery": "无法执行主服务器发现", "failed_query_registration_methods": "无法查询支持的注册方法。", "failed_soft_logout_auth": "重新认证失败", - "failed_soft_logout_homeserver": "由于家服务器的问题,重新认证失败", - "forgot_password_email_invalid": "电子邮件地址似乎无效。", - "forgot_password_email_required": "必须输入和你账户关联的邮箱地址。", - "forgot_password_prompt": "忘记你的密码了吗?", - "forgot_password_send_email": "发送重置连接", - "identifier_label": "登录方式", + "failed_soft_logout_homeserver": "由于服务器的问题,重新认证失败", + "forgot_password_email_invalid": "邮件地址似乎无效。", + "forgot_password_email_required": "必须输入与你的账户关联的邮件地址", + "forgot_password_prompt": "忘记密码?", + "forgot_password_send_email": "发送邮件", + "identifier_label": "登录选项", "incorrect_credentials": "用户名或密码错误。", - "incorrect_credentials_detail": "请注意,你正在登录 %(hs)s,而非 matrix.org。", - "incorrect_password": "密码错误", + "incorrect_credentials_detail": "请留意你登录的服务器为 %(hs)s 而不是 matrix.org", + "incorrect_password": "密码不正确", "log_in_new_account": "登录到你的新账户。", "logout_dialog": { - "description": "你确定要登出吗?", + "description": "你确定要注销吗?", "megolm_export": "手动导出密钥", - "setup_key_backup_title": "你将失去你的加密消息的访问权", - "setup_secure_backup_description_1": "加密消息已使用端到端加密保护。只有你和拥有密钥的收件人可以阅读这些消息。", - "setup_secure_backup_description_2": "当你登出时,这些密钥会从此设备删除。这意味着你将无法查阅已加密消息,除非你在其他设备上有那些消息的密钥,或者已将其备份到服务器。", - "skip_key_backup": "我不想要我的加密消息" + "setup_key_backup_title": "你将失去加密消息的访问权", + "setup_secure_backup_description_1": "加密消息采用端到端加密技术确保安全。只有你与收件人拥有读取这些消息的密钥。", + "setup_secure_backup_description_2": "当你移除此设备时,这些密钥将从此设备中删除,这意味着你将无法读取加密消息,除非你在其他设备上拥有这些密钥,或者将它们备份到服务器。", + "skip_key_backup": "我要丢弃加密消息" }, - "misconfigured_body": "跟你的%(brand)s管理员确认你的配置不正确或重复的条目。", - "misconfigured_title": "你的 %(brand)s 配置有错误", - "msisdn_field_description": "别的用户可以使用你的联系人详情邀请你加入房间", + "misconfigured_body": "请 %(brand)s 的管理员检查你的配置中是否存在错误或重复的条目。", + "misconfigured_title": "你的 %(brand)s 配置错误", + "mobile_create_account_title": "你即将在 %(hsName)s 创建账户", + "msisdn_field_description": "其他用户可以使用你的联系方式邀请你进入房间", "msisdn_field_label": "电话", - "msisdn_field_number_invalid": "电话号码看起来不太对,请检查并重试", + "msisdn_field_number_invalid": "电话号码看起来不正确,请检查并重试", "msisdn_field_required_invalid": "输入电话号码", - "no_hs_url_provided": "未输入家服务器链接", + "no_hs_url_provided": "未提供主服务器 URL", "oidc": { - "error_title": "我们无法使你登入", - "generic_auth_error": "验证时出了问题。前往登录页面并重试。", - "missing_or_invalid_stored_state": "我们已要求浏览器记住你使用的家服务器,但不幸的是你的浏览器已忘记。请前往登录页面重试。" + "error_title": "我们无法让你登录", + "generic_auth_error": "身份验证期间出现问题。请转到登录页面并重试。", + "missing_or_invalid_stored_state": "我们要求浏览器记住你用于登录的主服务器,但很遗憾,你的浏览器忘记了它。请转到登录页面并重试。" }, - "password_field_keep_going_prompt": "继续前进……", + "password_field_keep_going_prompt": "保持…", "password_field_label": "输入密码", - "password_field_strong_label": "不错,是个强密码!", - "password_field_weak_label": "密码允许但不安全", + "password_field_strong_label": "很好,高强度密码!", + "password_field_weak_label": "允许使用密码,但不安全", "phone_label": "电话", "phone_optional_label": "电话号码(可选)", "qr_code_login": { - "completing_setup": "完成新设备的设置" + "check_code_explainer": "这将验证与其它设备的关联是否安全。", + "check_code_heading": "输入其它设备上显示的数字", + "check_code_input_label": "2 位数代码", + "check_code_mismatch": "数字不匹配", + "completing_setup": "完成新设备的设置", + "error_etag_missing": "发生意外错误。这可能是由于浏览器扩展、代理服务器或服务器配置错误造成的。", + "error_expired": "登录已过期。请重试。", + "error_expired_title": "登录未及时完成", + "error_insecure_channel_detected": "无法与新设备建立安全连接。你的现有设备仍然安全,无需担心。", + "error_insecure_channel_detected_instructions": "现在怎么办?", + "error_insecure_channel_detected_instructions_1": "如果是网络问题,请尝试使用二维码再次登录到其它设备。", + "error_insecure_channel_detected_instructions_2": "如果你遇到同样的问题,请尝试其它 Wi-Fi 网络或使用移动数据流量。", + "error_insecure_channel_detected_instructions_3": "如果未能生效,请手动登录", + "error_insecure_channel_detected_title": "不安全连接", + "error_other_device_already_signed_in": "你不需要做其他任何事情。", + "error_other_device_already_signed_in_title": "你的其它设备已登录", + "error_rate_limited": "短时间内尝试次数过多。请稍后再试。", + "error_unexpected": "发生意外错误。连接你其它设备的请求已被取消。", + "error_unsupported_protocol": "此设备不支持使用二维码登录其它设备。", + "error_unsupported_protocol_title": "其它设备不兼容", + "error_user_cancelled": "已在另一设备上取消登录。", + "error_user_cancelled_title": "登录请求已取消", + "error_user_declined": "你或账户提供者拒绝了登录请求。", + "error_user_declined_title": "登录被拒绝", + "follow_remaining_instructions": "按剩余说明操作", + "open_element_other_device": "在你的其它设备上打开 %(brand)s", + "point_the_camera": "扫描此处显示的二维码", + "scan_code_instruction": "使用其它设备扫描二维码", + "scan_qr_code": "使用二维码登录", + "security_code": "安全代码", + "security_code_prompt": "如有要求,请在其它设备上输入以下代码。", + "select_qr_code": "选择“%(scanQRCode)s”", + "unsupported_explainer": "你的账户提供者不支持通过二维码登录到新设备。", + "unsupported_heading": "二维码不受支持", + "waiting_for_device": "等待设备登录" }, "register_action": "创建账户", "registration": { - "continue_without_email_description": "请注意,如果你不添加电子邮箱并且忘记密码,你将永远失去对你账户的访问权。", - "continue_without_email_field_label": "电子邮箱(可选)", - "continue_without_email_title": "不使用电子邮箱并继续" + "continue_without_email_description": "请注意,如果你未添加邮件地址并忘记了密码,你可能会永久失去账户访问权。", + "continue_without_email_field_label": "邮件地址(可选)", + "continue_without_email_title": "在不使用邮件的情况下继续" }, - "registration_disabled": "此家服务器已禁止注册。", - "registration_msisdn_field_required_invalid": "输入电话号码(此家服务器上必须)", + "registration_disabled": "此主服务器已禁用注册。", + "registration_msisdn_field_required_invalid": "输入电话号码(在此主服务器为必需)", "registration_successful": "注册成功", - "registration_username_in_use": "该名称已被占用。 尝试另一个,或者如果是您,请在下面登录。", - "registration_username_unable_check": "无法检查用户名是否已被使用。稍后再试。", - "registration_username_validation": "仅使用小写字母,数字,横杠和下划线", + "registration_username_in_use": "该用户名已被使用。请尝试其它用户名,如果是你本人,请在下方登录。", + "registration_username_unable_check": "无法检查用户名是否被占用。稍后再试。", + "registration_username_validation": "只能使用小写字母、数字、破折号与下划线", "reset_password": { - "devices_logout_success": "你已登出全部设备,并将不再收到推送通知。要重新启用通知,请在每台设备上再次登入。", - "other_devices_logout_warning_1": "登出你的设备会删除存储在其上的消息加密密钥,使加密的聊天历史不可读。", - "other_devices_logout_warning_2": "若想保留对加密房间的聊天历史的访问权,请设置密钥备份或从其他设备导出消息密钥,然后再继续。", + "confirm_new_password": "确认新密码", + "devices_logout_success": "你已移除所有设备,将不再接收推送通知。要重新启用通知,请在每台设备上重新登录。", + "other_devices_logout_warning_1": "移除设备将删除存储在其中的消息加密密钥,从而使加密的聊天历史无法读取。", + "other_devices_logout_warning_2": "如果你想保留对加密房间中聊天记录的访问权限,请获取恢复密钥或从其它设备导出消息密钥,然后再继续。", "password_not_entered": "必须输入新密码。", - "passwords_mismatch": "新密码必须互相匹配。", - "reset_successful": "你的密码已重置。", - "return_to_login": "返回登录页面" + "passwords_mismatch": "两次输入的新密码必须匹配。", + "rate_limit_error": "短时间内尝试次数过多。请稍后再试。", + "rate_limit_error_with_time": "短时间内尝试次数过多。请于 %(timeout)s 秒后重试。", + "reset_successful": "密码已被重置。", + "return_to_login": "回到登录屏幕", + "sign_out_other_devices": "移除其它设备" }, + "reset_password_action": "重置密码", "reset_password_button": "忘记密码?", - "reset_password_email_field_description": "使用邮件地址恢复你的账户", - "reset_password_email_field_required_invalid": "输入邮件地址(此家服务器上必须)", - "reset_password_email_not_associated": "你的电子邮件地址似乎未与服务器上的Matrix ID关联。", - "reset_password_email_not_found_title": "未找到此邮箱地址", - "server_picker_custom": "其他自定义服务器", - "server_picker_description": "你可以使用自定义服务器选项来指定不同的家服务器URL以登录其他Matrix服务器。这让你能把%(brand)s和不同家服务器上的已有Matrix账户搭配使用。", - "server_picker_description_matrix.org": "免费加入最大的公共服务器,成为数百万用户中的一员", - "server_picker_dialog_title": "决定账户托管位置", - "server_picker_explainer": "使用你的Matrix服务器,或自己架设一个。", - "server_picker_failed_validate_homeserver": "无法验证家服务器", - "server_picker_intro": "我们将您可以托管账户的地方称为“服务器组”。", - "server_picker_invalid_url": "URL 无效", - "server_picker_learn_more": "关于家服务器", - "server_picker_matrix.org": "Matrix.org 是世界上最大的公共家服务器,因此对许多人来说是一个好地方。", - "server_picker_required": "指定家服务器", - "server_picker_title": "登录你的家服务器", + "reset_password_email_field_description": "使用邮件地址以恢复账户", + "reset_password_email_field_required_invalid": "输入邮件地址(在该主服务器为必需)", + "reset_password_email_not_associated": "你的邮件地址似乎未与此主服务器上的 Matrix ID 关联。", + "reset_password_email_not_found_title": "此邮件地址未找到", + "reset_password_title": "重置密码", + "server_picker_custom": "其它主服务器", + "server_picker_description": "你可以使用自定义服务器选项,通过指定不同的主服务器网址登录其它 Matrix 服务器。这样你就可以通过现有的 Matrix 账户在其它主服务器上使用 %(brand)s。", + "server_picker_description_matrix.org": "免费加入数百万人规模,最大的公共服务器", + "server_picker_dialog_title": "决定你的账户托管在哪里", + "server_picker_explainer": "如果有首选的 Matrix 主服务器,请使用它,或托管自己的服务器。", + "server_picker_failed_validate_homeserver": "无法验证主服务器", + "server_picker_intro": "我们将可以托管账户的地方称为 “主服务器”。", + "server_picker_invalid_url": "无效 URL", + "server_picker_learn_more": "关于主服务器", + "server_picker_matrix.org": "Matrix.org 是世界上最大的公共主服务器,因此对许多人来说都是一个不错的选择。", + "server_picker_required": "指定主服务器", + "server_picker_title": "登录到主服务器", "server_picker_title_default": "服务器选项", "server_picker_title_registration": "账户托管于", - "session_logged_out_description": "出于安全考虑,此会话已被注销。请重新登录。", - "session_logged_out_title": "已退出登录", + "session_logged_out_description": "为安全起见,此设备已被移除。请重新登录。", + "session_logged_out_title": "会话已移除", "set_email": { - "description": "这将允许你重置你的密码和接收通知。", - "verification_pending_description": "请检查你的电子邮箱并点击里面包含的链接。完成时请点击继续。", - "verification_pending_title": "验证等待中" + "description": "这将允许你重置密码并接收通知。", + "verification_pending_description": "请查看邮件并点击其中的链接。完成后点击“继续”。", + "verification_pending_title": "等待验证" }, - "set_email_prompt": "你想要设置一个邮箱地址吗?", - "sign_in_description": "使用你的账户继续。", - "sign_in_instead": "跳转到登录", - "sign_in_instead_prompt": "已有账户?在此登录", + "set_email_prompt": "你要设置邮件地址吗?", + "sign_in_description": "使用你的账户以继续。", + "sign_in_instead": "其它登录方式", + "sign_in_instead_prompt": "已有账户?登录", "sign_in_or_register": "登录或创建账户", - "sign_in_or_register_description": "使用已有账户或创建一个新账户。", - "sign_in_prompt": "有账户了?登录", - "sign_in_with_sso": "使用单点登录", + "sign_in_or_register_description": "使用你的现有账户或创建一个新账户继续。", + "sign_in_prompt": "已有账户?登录", + "sign_in_with_sso": "以单点模式登录", + "signing_in": "正在登录…", "soft_logout": { "clear_data_button": "清除所有数据", - "clear_data_description": "清除此设备中的所有数据是永久的。加密消息会丢失,除非其密钥已被备份。", - "clear_data_title": "是否清除此设备中的所有数据?" + "clear_data_description": "清除此会话中的所有数据是永久性的。除非备份了密钥,否则加密消息将会丢失。", + "clear_data_title": "清除此会话中的所有数据?" }, - "soft_logout_heading": "你已登出", - "soft_logout_intro_password": "输入你的密码以登录并重新获取访问你账户的权限。", - "soft_logout_intro_sso": "请登录以重新获取访问你账户的权限。", - "soft_logout_intro_unsupported_auth": "你不能登录到你的账户。请联系你的家服务器管理员以获取更多信息。", + "soft_logout_heading": "你已注销", + "soft_logout_intro_password": "输入密码登录并重新访问你的账户。", + "soft_logout_intro_sso": "登录并重新获得对账户的访问权。", + "soft_logout_intro_unsupported_auth": "你无法登录账户。请联系主服务器管理员了解更多信息。", "soft_logout_subheading": "清除个人数据", + "soft_logout_warning": "警告:你的个人数据(包括加密密钥)仍存储在此会话中。如果你结束使用此会话或想要登录其它账户,请清除它。", "sso": "单点登录", - "sso_complete_in_browser_dialog_title": "转到您的浏览器以完成登录", - "sso_failed_missing_storage": "我们已请求浏览器记住你使用的服务器,但是你的浏览器貌似已经忘记了。请前往登录页面重试。", + "sso_complete_in_browser_dialog_title": "转到浏览器以完成登录", + "sso_failed_missing_storage": "我们要求浏览器记住你用于登录的主服务器,但很遗憾,你的浏览器忘记了它。请转到登录页面并重试。", "sso_or_username_password": "%(ssoButtons)s 或 %(usernamePassword)s", - "sync_footer_subtitle": "如果你加入了很多房间,可能会消耗一些时间", + "sync_footer_subtitle": "如果你加入了很多房间,这可能需要一段时间。", + "syncing": "正在同步…", "uia": { "code": "代码", - "email": "要创建账户,请打开我们刚刚发送到%(emailAddress)s的电子邮件里的链接。", - "email_auth_header": "检查你的电子邮件以继续", - "email_resend_prompt": "没收到吗?重新发送", - "email_resent": "已重新发送!", + "email": "要创建你的账户,请打开我们刚刚发送至 %(emailAddress)s 的邮件中的链接。", + "email_auth_header": "查看邮箱以继续", + "email_resend_prompt": "没有收到?重新发送", + "email_resent": "重新发送!", "fallback_button": "开始认证", - "msisdn": "一封短信已发送到 %(msisdn)s", - "msisdn_token_incorrect": "令牌错误", - "msisdn_token_prompt": "请输入其包含的代码:", - "password_prompt": "在下方输入账户密码以确认你的身份。", - "recaptcha_missing_params": "在服务器配置中缺少验证码公钥。请将此问题报告给你的服务器管理员。", - "registration_token_prompt": "输入由服务器管理员所提供的注册密钥。", - "sso_body": "使用单一登入证明你的身份,以确认添加此电子邮件地址。", - "sso_failed": "确认你的身份时出了一点问题。取消并重试。", - "sso_postauth_body": "点击下方按钮确认你的身份。", + "mas_cross_signing_reset_cta": "在账户中继续", + "mas_cross_signing_reset_description": "你即将前往 %(serverName)s 账户重置数字身份。完成账户重置后,请返回此处并点击“重试”。", + "mas_cross_signing_reset_title": "转到你的账户以重置数字身份", + "msisdn": "已向 %(msisdn)s 发送文本消息", + "msisdn_token_incorrect": "Token 不正确", + "msisdn_token_prompt": "请输入其中包含的代码:", + "password_prompt": "请在下面输入账户密码以确认身份。", + "recaptcha_missing_params": "主服务器配置中缺少 CAPTCHA 公钥。请将此问题报告给主服务器管理员。", + "registration_token_label": "注册 Token", + "registration_token_prompt": "输入主服务器管理员提供的注册 Token。", + "sso_body": "请确认使用单点登录添加此邮件地址以证明身份。", + "sso_failed": "确认身份时出现问题。取消并重试。", + "sso_postauth_body": "点击以下按钮确认身份。", "sso_postauth_title": "确认以继续", - "sso_preauth_body": "要继续,请使用单点登录证明你的身份。", + "sso_preauth_body": "要继续,请使用单点登录以证明身份。", "sso_title": "使用单点登录继续", - "terms": "请阅读并接受此服务器的政策:", - "terms_invalid": "请阅读并接受此服务器的所有政策" + "terms": "请阅读并接受主服务器的政策:", + "terms_invalid": "请阅读并接受主服务器的所有政策" }, - "unsupported_auth": "此服务器未提供客户端支持的任何登录流程。", - "unsupported_auth_email": "此家服务器不支持使用电子邮箱地址登录。", - "unsupported_auth_msisdn": "此服务器不支持使用电话号码认证。", + "unsupported_auth": "此主服务器不提供此客户端支持的任何登录流程。", + "unsupported_auth_email": "此主服务器不支持使用邮件地址登录。", + "unsupported_auth_msisdn": "此服务器不支持使用电话号码进行身份验证。", "username_field_required_invalid": "输入用户名", - "username_in_use": "用户名已被占用,请尝试使用其他用户名。" + "username_in_use": "此用户名已被使用,请更换。", + "verify_email_explainer": "在重置密码之前,我们需要确认你的身份。请点击我们刚刚发送到 %(email)s 中的链接。", + "verify_email_heading": "验证邮件地址以继续" }, "bug_reporting": { - "additional_context": "如果有额外的上下文可以帮助我们分析问题,比如你当时在做什么、房间 ID、用户 ID 等等,请将其列于此处。", - "before_submitting": "在提交日志之前,你必须创建一个GitHub issue 来描述你的问题。", - "collecting_information": "正在收集应用版本信息", + "additional_context": "如果有其它有助于分析问题的上下文(例如你当时在做什么、房间 ID、用户 ID 等),请在此处包含这些内容。", + "before_submitting": "建议你创建 GitHub Issue,以确保你的报告被审核。", + "collecting_information": "正在收集 App 版本信息", "collecting_logs": "正在收集日志", - "create_new_issue": "请在 GitHub 上创建一个新 issue 以便我们调查此错误。", - "description": "调试日志包含应用使用数据,其中包括你的用户名、你访问过的房间的别名或ID、你上次与哪些UI元素互动、还有其它用户的用户名。但不包含消息。", + "create_new_issue": "请在 GitHub 上创建新 issue,以便我们调查此错误。", + "description": "调试日志包含 App 使用数据、你的用户名、访问过的房间 ID 或其别名、上次与之交互的 UI 元素以及其他用户的用户名。它们不包含消息。", "download_logs": "下载日志", "downloading_logs": "正在下载日志", - "error_empty": "请告诉我们哪里出错了,或最好创建一个 GitHub issue 来描述此问题。", + "error_empty": "请告诉我们出了什么问题,或者更好的做法是创建一个 GitHub issue 来描述问题。", + "failed_download_logs": "调试日志下载失败: ", + "failed_send_logs_causes": { + "disallowed_app": "你的 Bug 报告已被拒绝。Rageshake 服务器不支持此应用程序。", + "rejected_generic": "你的 Bug 报告已被拒绝。Rageshake 服务器由于策略原因拒绝了报告内容。", + "rejected_recovery_key": "你的 Bug 报告由于安全原因被拒绝,因为它包含恢复密钥。", + "rejected_version": "你的 Bug 报告被拒绝,因为你正在运行的版本太旧。", + "server_unknown_error": "Rageshake 服务器遇到未知错误,无法处理报告。", + "unknown_error": "日志发送失败。" + }, "github_issue": "GitHub 上的 issue", - "introduction": "若你通过GitHub提交bug,则调试日志能帮助我们追踪问题。 ", - "log_request": "要帮助我们防止其以后发生,请给我们发送日志。", - "logs_sent": "日志已发送", - "matrix_security_issue": "要报告 Matrix 相关的安全问题,请阅读 Matrix.org 的安全公开策略。", - "preparing_download": "正在准备下载日志", + "introduction": "如果你通过 GitHub 提交 bug,调试日志可以帮助我们跟踪问题。", + "log_request": "为帮助我们今后避免这种情况,请向我们发送日志。", + "logs_sent": "发送日志", + "matrix_security_issue": "要报告与 Matrix 相关的安全问题,请阅读 Matrix.org 安全披露政策。", + "preparing_download": "准备下载日志", "preparing_logs": "正在准备发送日志", "send_logs": "发送日志", "submit_debug_logs": "提交调试日志", - "textarea_label": "提示", + "textarea_label": "内容", "thank_you": "谢谢!", - "title": "错误上报", - "unsupported_browser": "提醒:你的浏览器不被支持,所以你的体验可能不可预料。", + "title": "Bug 报告", + "unsupported_browser": "提醒:你的浏览器不受支持,因此你的使用体验可能无法预测。", "uploading_logs": "正在上传日志", - "waiting_for_server": "正在等待服务器响应" + "waiting_for_server": "等待服务器响应" }, - "cannot_invite_without_identity_server": "无法在未设置身份服务器时邀请用户,你可以在“设置”里连接一个。", - "cannot_reach_homeserver": "无法连接到家服务器", - "cannot_reach_homeserver_detail": "确保你的网络连接稳定,或与服务器管理员联系", - "cant_load_page": "无法加载页面", - "chat_card_back_action_label": "返回聊天", + "cannot_invite_without_identity_server": "如果没有身份服务器,则无法通过邮件邀请用户。您可以连接到“设置”下的服务器。", + "cannot_reach_homeserver": "无法连接主服务器", + "cannot_reach_homeserver_detail": "确保具有良好的 Internet 连接,或联系服务器管理员", + "cant_load_page": "无法载入页面", + "chat_card_back_action_label": "回到聊天", "chat_effects": { - "confetti_description": "附加五彩纸屑发送", - "confetti_message": "发送五彩纸屑", - "fireworks_description": "附加烟火发送", - "fireworks_message": "发送烟火", - "hearts_description": "与爱心一起发送给定的消息", - "hearts_message": "发送爱心", - "rainfall_description": "附加降雨发送给定的消息", - "rainfall_message": "发送降雨", - "snowfall_description": "发送附加雪球的给定信息", - "snowfall_message": "发送雪球", - "spaceinvaders_description": "此消息带有空间主题化效果", - "spaceinvaders_message": "发送空间入侵者" + "confetti_description": "向指定消息发送“五彩纸屑”", + "confetti_message": "发送了“五彩纸屑”", + "fireworks_description": "向指定消息发送“烟花”", + "fireworks_message": "发送了“烟花”", + "hearts_description": "向指定消息发送“爱心”", + "hearts_message": "发送了“爱心”", + "rainfall_description": "向指定消息发送“下雨”", + "rainfall_message": "发送了“下雨”", + "snowfall_description": "向指定消息发送“下雪”", + "snowfall_message": "发送了“下雪”", + "spaceinvaders_description": "向指定消息发送具有“太空主题”的特效", + "spaceinvaders_message": "发送了“太空侵略者”" }, "common": { - "access_token": "访问令牌", - "accessibility": "无障碍功能", + "access_token": "访问 Token", + "accessibility": "易用性", "advanced": "高级", - "analytics": "统计分析服务", + "all_chats": "所有聊天", + "analytics": "分析", "and_n_others": { - "other": "和其他%(count)s个人……", - "one": "和其它一个..." + "one": "与另一个…", + "other": "以及剩余 %(count)s 个…" }, "appearance": "外观", "application": "应用", - "are_you_sure": "你确定吗?", + "are_you_sure": "是否确定?", "attachment": "附件", "authentication": "认证", "avatar": "头像", "beta": "beta", - "camera": "摄像头", + "camera": "相机", "cameras": "相机", - "capabilities": "功能", + "cancel": "取消", + "capabilities": "能力", "copied": "已复制!", - "credits": "感谢", + "credits": "鸣谢", "dark": "深色", "description": "描述", "deselect_all": "取消全选", "device": "设备", "edited": "已编辑", - "email_address": "邮箱地址", + "email_address": "邮件地址", "emoji": "表情符号", "encrypted": "已加密", "error": "错误", "faq": "常见问答集", - "favourites": "收藏夹", + "favourites": "收藏", "feedback": "反馈", - "filter_results": "过滤结果", + "filter_results": "筛选结果", "forward_message": "转发消息", "general": "通用", - "go_to_settings": "打开设置", - "guest": "游客", + "go_to_settings": "转到设置", + "guest": "访客", "help": "帮助", "historical": "历史", "home": "主页", - "homeserver": "家服务器", + "homeserver": "主服务器", "identity_server": "身份服务器", - "image": "图片", + "image": "图像", "integration_manager": "集成管理器", "joined": "已加入", "labs": "实验室", - "legal": "法律信息", + "legal": "法律", "light": "浅色", - "loading": "加载中...", + "loading": "正在载入…", "location": "位置", "low_priority": "低优先级", "matrix": "Matrix", "message": "消息", "message_layout": "消息布局", + "message_timestamp_invalid": "无效时间戳", "microphone": "麦克风", "model": "模型", + "moderation_and_safety": "尺度与安全", "modern": "现代", - "mute": "静音", + "mute": "静默", "n_members": { "one": "%(count)s 位成员", - "other": "%(count)s 位成员" + "other": "%(count)s 个成员" }, "n_rooms": { "one": "%(count)s 个房间", "other": "%(count)s 个房间" }, "name": "名称", - "no_results": "没有更多结果", - "no_results_found": "找不到结果", - "not_trusted": "不受信任的", - "off": "关闭", + "no_results": "没有结果", + "no_results_found": "未找到结果", + "not_trusted": "未被信任", + "off": "关", "offline": "离线", - "on": "打开", + "on": "开", "options": "选项", - "orphan_rooms": "其他房间", + "orphan_rooms": "其它房间", "password": "密码", - "people": "联系人", + "people": "人员", "preferences": "偏好", - "presence": "在线", + "presence": "线上状态", "preview_message": "嘿。你是最棒的!", "privacy": "隐私", "private": "私有", @@ -467,67 +541,74 @@ "profile": "个人资料", "public": "公共", "public_room": "公共房间", - "public_space": "公开空间", + "public_space": "公共空间", "qr_code": "二维码", "random": "随机", "reactions": "反应", - "report_a_bug": "反馈问题", + "recommended": "推荐", + "report_a_bug": "报告 Bug", "room": "房间", "room_name": "房间名称", "rooms": "房间", + "save": "保存", + "saved": "已保存", + "saving": "正在保存…", "secure_backup": "安全备份", "select_all": "全选", "server": "服务器", "settings": "设置", - "setup_secure_messages": "设置安全消息", + "setup_secure_messages": "设置安全消息传递", "show_more": "显示更多", - "someone": "某位用户", - "space": "空格", + "someone": "某人", + "space": "空间", "spaces": "空间", "sticker": "贴纸", "stickerpack": "贴纸包", "success": "成功", "suggestions": "建议", "support": "支持", - "system_alerts": "系统警告", + "system_alerts": "系统警报", "theme": "主题", "thread": "消息列", "threads": "消息列", "timeline": "时间线", + "unavailable": "不可用", "unencrypted": "未加密", - "unmute": "取消静音", - "unnamed_room": "未命名的房间", + "unmute": "取消静默", + "unnamed_room": "未命名房间", "unnamed_space": "未命名空间", "unverified": "未验证", + "updating": "正在更新…", "user": "用户", - "user_avatar": "头像", + "user_avatar": "个人资料图像", "username": "用户名", "verified": "已验证", "version": "版本", "video": "视频", "video_room": "视频房间", "view_message": "查看消息", + "voice": "语音", "warning": "警告" }, "composer": { "autocomplete": { "@room_description": "通知房间全体成员", "command_a11y": "命令自动补全", - "command_description": "命令", - "emoji_a11y": "表情符号自动补全", + "command_description": "指令", + "emoji_a11y": "Emoji 自动补全", "notification_a11y": "通知自动补全", "notification_description": "房间通知", "room_a11y": "房间自动补全", - "space_a11y": "空间自动完成", + "space_a11y": "空间自动补全", "user_a11y": "用户自动补全", "user_description": "用户" }, "close_sticker_picker": "隐藏贴纸", "edit_composer_label": "编辑消息", - "format_bold": "粗体", + "format_bold": "加粗", "format_code_block": "代码块", "format_decrease_indent": "减少缩进", - "format_increase_indent": "添加缩进", + "format_increase_indent": "增加缩进", "format_inline_code": "代码", "format_insert_link": "插入链接", "format_italic": "斜体", @@ -536,181 +617,319 @@ "format_ordered_list": "有序列表", "format_strikethrough": "删除线", "format_underline": "下划线", - "format_unordered_list": "无序列表", + "format_unordered_list": "项目符号列表", + "formatting_toolbar_label": "格式化", "link_modal": { - "link_field_label": "链接" + "link_field_label": "链接", + "text_field_label": "文本", + "title_create": "创建链接", + "title_edit": "编辑链接" }, - "no_perms_notice": "你没有在此房间发送消息的权限", + "mode_plain": "隐藏文字格式化选项", + "mode_rich_text": "显示文字格式化选项", + "no_perms_notice": "你无权在此房间发送消息", "placeholder": "发送消息…", - "placeholder_encrypted": "发送加密消息……", + "placeholder_encrypted": "发送加密消息…", "placeholder_reply": "发送回复…", "placeholder_reply_encrypted": "发送加密回复…", - "placeholder_thread": "回复消息列……", - "placeholder_thread_encrypted": "回复加密的消息列……", + "placeholder_thread": "在消息列中回复…", + "placeholder_thread_encrypted": "回复加密消息列…", "poll_button": "投票", "poll_button_no_perms_description": "你无权在此房间启动投票。", "poll_button_no_perms_title": "需要权限", "replying_title": "正在回复", - "room_upgraded_link": "对话在这里继续。", - "room_upgraded_notice": "此房间已被取代,且不再活跃。", + "room_unencrypted": "位于此房间的消息非端到端加密", + "room_upgraded_link": "对话在此处继续。", + "room_upgraded_notice": "此房间已被取代,不再活跃。", "send_button_title": "发送消息", "send_button_voice_message": "发送语音消息", "send_voice_message": "发送语音消息", - "stop_voice_message": "停止录制", + "stop_voice_message": "停止录音", "voice_message_button": "语音消息" }, - "console_dev_note": "若你知道你正在做什么,Element是开源的,请务必看看我们的GitHub(https://github.com/vector-im/element-web/)并贡献!", - "console_scam_warning": "若某人告诉你在这里复制/粘贴某物,那你极有可能正被欺骗!", - "console_wait": "等等!", + "console_dev_note": "如果你知道你在做什么,Element 是开源的,请务必查看我们的 GitHub(https://github.com/vector-im/element-web/)并做出贡献!", + "console_scam_warning": "如果有人告诉你在这里复制或粘贴某些内容,那么你很有可能被诈骗!", + "console_wait": "请三思!", "create_room": { "action_create_room": "创建房间", "action_create_video_room": "创建视频房间", - "encrypted_video_room_warning": "你以后无法停用。房间将会加密但是嵌入的通话不会。", - "encrypted_warning": "之后你无法停用。桥接和大多数机器人也不能工作。", - "encryption_forced": "你的服务器要求私有房间得启用加密。", + "encrypted_video_room_warning": "你以后无法禁用此功能。房间将被加密,但嵌入式通话不会加密。", + "encrypted_warning": "此项之后无法禁用。会导致桥接器与大多数机器人暂不可用。", + "encryption_forced": "服务器要求在私有房间中启用加密。", "encryption_label": "启用端到端加密", - "error_title": "创建房间失败", - "generic_error": "当前服务器可能处于不可用或过载状态,或者你遇到了一个 bug。", - "join_rule_change_notice": "你可以随时从房间设置中更改此设置。", + "error_title": "房间创建失败", + "generic_error": "服务器可能不可用、超载或遇到错误。", + "join_rule_change_notice": "你可以随时在房间设置中更改此项。", "join_rule_invite": "私有房间(仅邀请)", - "join_rule_invite_label": "只有被邀请的人才能找到并加入这个房间。", - "join_rule_public_label": "任何人都可以找到并加入这个房间。", - "join_rule_public_parent_space_label": "任何人都可以找到并加入这个房间,而不仅仅是 的成员。", - "join_rule_restricted": "对空间成员可见", - "join_rule_restricted_label": " 中的每个人都可以找到并加入这个房间。", - "name_validation_required": "请输入房间名称", - "room_visibility_label": "房间可见度", - "title_private_room": "创建一个私人房间", - "title_public_room": "创建公开房间", + "join_rule_invite_label": "只有受邀人员才能找到并加入此房间。", + "join_rule_knock_label": "任何人都可以申请加入,但管理员或协管员需要授予访问权限。你可以稍后更改此设置。", + "join_rule_public_label": "任何人都可以找到并加入此房间。", + "join_rule_public_parent_space_label": "任何人都可以找到并加入此房间,而不仅限于 中的成员。", + "join_rule_restricted": "标准", + "join_rule_restricted_label": "任何位于 中的人都可以加入。", + "name_validation_required": "为房间输入名称", + "room_visibility_label": "房间可见性", + "state_encrypted_warning": "启用加密状态事件的实验性支持,此功能会对服务器隐藏房间名称与主题等元数据。这些元数据对之后加入房间的用户与不支持 MSC4362 的客户端隐藏。", + "state_encryption_label": "加密状态事件", + "title_private_room": "创建私有房间", + "title_public_room": "创建公共房间", "title_video_room": "创建视频房间", - "topic_label": "话题(可选)", - "unfederated": "阻住任何不属于 %(serverName)s 的人加入此房间。", - "unfederated_label_default_off": "你可以启用此选项如果此房间将仅用于你的家服务器上的内部团队协作。此选项之后无法更改。", - "unfederated_label_default_on": "若房间将用于与拥有自己的家服务器的外部团队协作,则你可禁用此功能。这无法在以后更改。", + "topic_label": "主题(可选)", + "unfederated": "阻止任何不属于 %(serverName)s 的人员加入此房间。", + "unfederated_label_default_off": "如果房间仅用于与主服务器上的内部团队协作,则可以启用此项。一旦启用就无法更改。", + "unfederated_label_default_on": "如果此房间将用于与拥有自己主服务器的外部团队协作,你可以禁用此功能。此设置以后无法更改。", "unsupported_version": "服务器不支持指定的房间版本。" }, "create_space": { - "add_details_prompt": "添加一些细节,以便人们辨识你的社群。", - "add_details_prompt_2": "你随时可以更改它们。", - "add_existing_rooms_description": "选择要添加的房间或对话。这是专属于你的空间,不会有人被通知。你稍后可以再增加更多。", - "add_existing_rooms_heading": "你想要组织什么?", + "add_details_prompt": "添加一些信息以便人们识别。", + "add_details_prompt_2": "你可以随时更改。", + "add_existing_rooms_description": "选择要添加的房间或对话。这只是一个供你使用的空间,不会通知任何人。你可以稍后添加更多空间。", + "add_existing_rooms_heading": "你想组织哪些内容?", "address_label": "地址", "address_placeholder": "例如:my-space", - "done_action": "前往我的空间", - "done_action_first_room": "前往我的第一个房间", - "explainer": "空间是将房间和人分组的一种新方式。你想创建什么类型的空间?你可以在以后更改。", - "failed_create_initial_rooms": "创建初始空间房间失败", - "failed_invite_users": "邀请以下用户加入你的空间失败:%(csvUsers)s", - "invite_teammates_by_username": "按照用户名邀请", - "invite_teammates_description": "确保对的人可以访问。稍后你可以邀请更多人。", - "invite_teammates_heading": "邀请你的伙伴", + "creating": "正在创建…", + "creating_rooms": "正在创建房间…", + "done_action": "转到我的空间", + "done_action_first_room": "转到我的首个房间", + "explainer": "空间是发现并组织房间的一种方式。你想创建什么样的空间?", + "failed_create_initial_rooms": "创建初始空间失败", + "failed_invite_users": "邀请以下用户到空间失败:%(csvUsers)s", + "invite_teammates_by_username": "通过用户名邀请", + "invite_teammates_description": "确保认识的人可以访问。你可以稍后再邀请其他人。", + "invite_teammates_heading": "邀请同事", + "inviting_users": "正在邀请…", "label": "创建空间", "name_required": "请输入空间名称", - "personal_space": "仅有我", - "personal_space_description": "用于整理你房间的私有空间", - "private_description": "仅邀请,适合你自己或团队", + "personal_space": "只有我", + "personal_space_description": "一个用于组织你的房间的私有空间", + "private_description": "仅限邀请,适用于个人或团队", "private_heading": "你的私有空间", - "private_personal_description": "确保对的人有权访问 %(name)s", - "private_personal_heading": "你与谁一同工作?", - "private_space": "我和我的伙伴", - "private_space_description": "供你和你的伙伴使用的私有空间", - "public_description": "适合每一个人的开放空间,社群的理想选择", + "private_only_heading": "你的空间", + "private_personal_description": "确保受你期许的人员可以访问 %(name)s", + "private_personal_heading": "你与谁合作?", + "private_space": "我与我的同事", + "private_space_description": "你与同事的私有空间", + "public_description": "任何人都可以加入,适用于社区", "public_heading": "你的公共空间", - "setup_rooms_community_description": "让我们为每个主题都创建一个房间吧。", - "setup_rooms_community_heading": "你想在 %(spaceName)s 中讨论什么?", - "setup_rooms_description": "稍后你可以添加更多房间,包括现有的。", - "setup_rooms_private_heading": "你的团队正在进行什么项目?", - "share_description": "当前仅有你一人,与人同道而行会更好。", + "search_public_button": "在公共空间中搜索", + "setup_rooms_community_description": "创建一些房间以入门。", + "setup_rooms_community_heading": "你想在 %(spaceName)s 中讨论哪些内容?", + "setup_rooms_description": "你可以稍后添加更多房间到空间,包括现有房间。", + "setup_rooms_private_description": "创建一些房间以入门。", + "setup_rooms_private_heading": "你的团队正在进行哪些项目?", + "share_description": "目前只有你一人,如果有其他人,效果会更好。", "share_heading": "分享 %(name)s", "skip_action": "暂时跳过", - "subspace_beta_notice": "向你管理的空间添加空间。", + "subspace_adding": "正在添加…", + "subspace_beta_notice": "在受你管理的空间中添加子空间。", "subspace_dropdown_title": "创建空间", "subspace_existing_space_prompt": "想要添加现有空间?", - "subspace_join_rule_invite_description": "只有受邀者才能找到并加入此空间。", + "subspace_join_rule_invite_description": "只有受邀人员才能找到并加入此空间。", "subspace_join_rule_invite_only": "私有空间(仅邀请)", - "subspace_join_rule_label": "空间可见度", - "subspace_join_rule_public_description": "任何人都可以找到并加入这个空间,而不仅仅是 的成员。", - "subspace_join_rule_restricted_description": " 中的任何人都可以找到并加入。" + "subspace_join_rule_label": "空间可见性", + "subspace_join_rule_public_description": "任何人都可以找到并加入此空间,而不仅是 的成员。", + "subspace_join_rule_restricted_description": "位于 中的任何人都可以加入。" }, "credits": { - "default_cover_photo": "默认封面照片 ©Jesús Roncero 根据CC-BY-SA 4.0 条款使用。", - "twemoji": "Twemoji emoji art ©Twitter, Inc 和其他贡献者 根据CC-BY 4.0 条款使用。", - "twemoji_colr": "twemoji-colr 字体 ©Mozilla Foundation 根据Apache 2.0 条款使用。" + "default_cover_photo": "默认封面照片 © Jesús Roncero,根据 CC-BY-SA 4.0 的条款使用。", + "twemoji": "Twemoji Emoji 艺术 © Twitter, Inc 及其他贡献者 根据 CC-BY 4.0 的条款使用。", + "twemoji_colr": "twemoji-colr 样式 © Mozilla Foundation,根据 Apache 2.0 的条款使用。" + }, + "decline_invitation_dialog": { + "confirm": "你确定要拒绝 %(roomName)s 的加入邀请?", + "ignore_user_help": "你将看不到来自该用户的任何消息或房间邀请。", + "reason_description": "描述举报房间的理由。", + "report_room_description": "向账户提供者举报此房间。", + "title": "拒绝邀请" }, "desktop_default_device_name": "%(brand)s桌面版:%(platformName)s", "devtools": { - "active_widgets": "已启用的挂件", - "category_other": "其他", + "active_widgets": "激活小部件", + "category_other": "其它", "category_room": "房间", "caution_colon": "警告:", + "checking_sticky_events_support": "正在检查是否支持黏着事件…", "client_versions": "客户端版本", + "crypto": { + "4s_public_key_in_account_data": "在账户数据中", + "4s_public_key_not_in_account_data": "未找到", + "4s_public_key_status": "秘密存储公钥:", + "backup_key_cached": "本地缓存", + "backup_key_cached_status": "密钥缓存:", + "backup_key_not_stored": "未存储", + "backup_key_stored": "在秘密存储中", + "backup_key_stored_status": "密钥存储:", + "backup_key_unexpected_type": "意外类型", + "backup_key_well_formed": "良好", + "cross_signing": "交叉签名", + "cross_signing_cached": "本地缓存", + "cross_signing_not_ready": "交叉签名未设置", + "cross_signing_private_keys_in_storage": "在秘密存储中", + "cross_signing_private_keys_in_storage_status": "交叉签名私钥:", + "cross_signing_private_keys_not_in_storage": "存储中未找到", + "cross_signing_public_keys_on_device": "在内存中", + "cross_signing_public_keys_on_device_status": "交叉签名公钥:", + "cross_signing_ready": "交叉签名已就绪。", + "cross_signing_status": "交叉签名状态:", + "cross_signing_untrusted": "你的账户在秘密存储中拥有数字身份,但尚未被此会话信任。", + "crypto_not_available": "密码学模块不可用", + "device_id": "设备 ID", + "key_backup_active_version": "活跃的备份版本:", + "key_backup_active_version_none": "无", + "key_backup_inactive_warning": "你的密钥尚未在此会话中备份。", + "key_backup_latest_version": "服务器上的最新备份版本:", + "key_storage": "密钥存储", + "master_private_key_cached_status": "主私钥:", + "not_found": "未找到", + "not_found_locally": "本地未找到", + "secret_storage_not_ready": "未就绪", + "secret_storage_ready": "就绪", + "secret_storage_status": "秘密存储:", + "self_signing_private_key_cached_status": "自签名私钥:", + "session": "会话", + "session_fingerprint": "指纹(会话密钥)", + "title": "端到端加密", + "user_signing_private_key_cached_status": "用户签名私钥:" + }, "developer_mode": "开发者模式", "developer_tools": "开发者工具", + "device_dehydrated_no": "脱水:否", + "device_dehydrated_yes": "脱水:是", + "device_id": "设备 ID:%(deviceId)s", + "device_keys": "设备密钥", + "device_verification_status": { + "signed_by_owner": "验证状态: 经所有者签名", + "unknown": "验证状态:未知", + "unverified": "验证状态: 未经所有者签名", + "verified": "验证状态: 经交叉签名验证" + }, + "devices": "%(count)s 个密码学设备", "edit_setting": "编辑设置", "edit_values": "编辑值", "empty_string": "<空字符串>", + "error_sticky_duration_must_be_a_number": "stickyDuration 必须是数字", + "error_sticky_duration_out_of_range": "stickyDuration 取值范围必须在 0 ~ 36000 毫秒(1 小时)", "event_content": "事件内容", - "event_id": "事件ID:%(eventId)s", + "event_id": "事件 ID:%(eventId)s", "event_sent": "事件已发送!", "event_type": "事件类型", - "explore_room_state": "查找房间状态", - "failed_to_find_widget": "查找此挂件时出现错误。", + "expired": "已过期", + "expires_in": "剩余", + "explore_account_data": "浏览账户数据", + "explore_room_account_data": "浏览房间内账户数据", + "explore_room_state": "浏览房间状态", + "explore_sticky_state": "浏览黏着状态", + "failed_to_find_widget": "查找此小部件时出错。", "failed_to_load": "载入失败。", - "failed_to_save": "保存设置失败。", - "failed_to_send": "发送事件失败!", - "invalid_json": "看起来不像有效的JSON。", + "failed_to_save": "设置保存失败。", + "failed_to_send": "事件发送失败!", + "id": "ID:", + "invalid_device_key_id": "设备密钥 ID 无效", + "invalid_json": "JSON 似乎无效", "level": "层级", - "low_bandwidth_mode": "低带宽模式", - "low_bandwidth_mode_description": "需要兼容的家服务器。", - "number_of_users": "用户数", - "original_event_source": "原始事件源码", - "room_id": "房间ID: %(roomId)s", + "low_bandwidth_mode": "禁用对带宽有刚需的功能", + "low_bandwidth_mode_description": "禁用加密、状态、已读回执与键入通知", + "main_timeline": "主要时间线", + "manual_device_verification": "手动设备验证", + "no_receipt_found": "未找到回执", + "no_sticky_events": "此房间暂无黏着事件。", + "notification_state": "通知状态为 %(notificationState)s", + "notifications_debug": "通知调试", + "number_of_users": "用户数量", + "only_joined_members": "仅限已加入的用户", + "original_event_source": "原始事件源代码", + "restore_from_backup": "从备份恢复", + "room_encrypted": "房间已加密 ✅", + "room_id": "房间 ID:%(roomId)s", + "room_not_encrypted": "此房间未加密🚨", + "room_notifications_dot": "圆点:", + "room_notifications_highlight": "高亮:", + "room_notifications_last_event": "最新事件:", + "room_notifications_sender": "发送者:", + "room_notifications_thread_id": "消息列 ID:", + "room_notifications_total": "总数:", + "room_notifications_type": "类型:", + "room_status": "房间状态", + "room_unread_status_count": { + "one": "房间未读状态:%(status)s,数量:%(count)s", + "other": "房间未读状态:%(status)s,数量:%(count)s" + }, "save_setting_values": "保存设置值", + "see_history": "查看历史", "send_custom_account_data_event": "发送自定义账户数据事件", - "send_custom_room_account_data_event": "发送自定义房间账户资料事件", + "send_custom_room_account_data_event": "发送自定义房间账户数据事件", "send_custom_state_event": "发送自定义状态事件", + "send_custom_sticky_event": "发送自定义黏着事件", "send_custom_timeline_event": "发送自定义时间线事件", "server_info": "服务器信息", "server_versions": "服务器版本", - "settable_global": "全局可设置性", - "settable_room": "房间可设置性", + "settable_global": "全局可设置", + "settable_room": "可在房间内设置", "setting_colon": "设置:", "setting_definition": "设置定义:", "setting_id": "设置 ID", - "show_hidden_events": "显示时间线中的隐藏事件", + "settings": { + "elementCallUrl": "Element Call URL" + }, + "settings_explorer": "设置浏览器", + "show_empty_content_events": "以空白内容显示事件", + "show_hidden_events": "在时间线显示隐藏事件", "spaces": { - "one": "<空间>", - "other": "<%(count)s个空间>" + "one": "", + "other": "<%(count)s 个空间>" }, "state_key": "状态键(State Key)", + "sticky_duration": "黏着持续时间(毫秒)", + "sticky_events_not_supported": "你的主服务器不支持黏着事件。", + "thread_root_id": "消息列根 ID:%(threadRootId)s", + "threads_timeline": "消息列时间线", "title": "开发者工具", "toggle_event": "切换事件", "toolbox": "工具箱", - "use_at_own_risk": "此界面不会检查值的类型。使用风险自负。", + "use_at_own_risk": "此 UI 不检查值的数据类型。需自行承担使用风险。", + "user_avatar": "头像:%(avatar)s", + "user_displayname": "显示名称:%(displayname)s", + "user_id": "用户 ID:%(userId)s", + "user_no_avatar": "头像:", + "user_no_displayname": "显示名称:", + "user_read_up_to": "用户阅读到:", + "user_read_up_to_ignore_synthetic": "用户阅读到(ignoreSynthetic): ", + "user_read_up_to_private": "用户阅读到(m.read.private): ", + "user_read_up_to_private_ignore_synthetic": "用户阅读到(m.read.private;ignoreSynthetic):", + "user_room_membership": "成员资格", + "user_verification_status": { + "identity_changed": "验证状态: 未验证且已更改数字身份", + "unverified": "验证状态: 未验证", + "verified": "验证状态: 已验证", + "was_verified": "验证状态: 已验证但已更改数字身份" + }, + "users": "用户", "value": "值", "value_colon": "值:", - "value_in_this_room": "此房间中的值", + "value_in_this_room": "在此房间的值", "value_this_room_colon": "此房间中的值:", - "values_explicit": "各层级的值", - "values_explicit_colon": "各层级的值:", - "values_explicit_room": "此房间中各层级的值", - "values_explicit_this_room_colon": "此房间中各层级的值:", - "view_source_decrypted_event_source": "解密的事件源码", - "widget_screenshots": "对支持的挂件启用挂件截图" + "values_explicit": "显式值", + "values_explicit_colon": "显式值:", + "values_explicit_room": "此房间中的显式值", + "values_explicit_this_room_colon": "此房间中的显式值:", + "view_servers_in_room": "在房间中查看服务器", + "view_source_decrypted_event_source": "解密的事件源代码", + "view_source_decrypted_event_source_unavailable": "解密源代码不可用", + "widget_screenshots": "为支持的小部件启用“小部件屏幕截图”" }, "dialog_close_label": "关闭对话框", "download_completed": "下载完成", "emoji": { "categories": "类别", - "category_activities": "活动", - "category_animals_nature": "动物和自然", - "category_flags": "旗", - "category_food_drink": "食物和饮料", + "category_activities": "节假日", + "category_animals_nature": "动物与自然", + "category_flags": "旗帜", + "category_food_drink": "饮食", "category_frequently_used": "经常使用", - "category_objects": "物体", - "category_smileys_people": "表情和人", + "category_objects": "日常物品", + "category_smileys_people": "人与表情", "category_symbols": "符号", - "category_travel_places": "旅行和地点", + "category_travel_places": "文旅景点", "quick_reactions": "快速反应" }, "emoji_picker": { @@ -720,383 +939,498 @@ "empty_room_was_name": "空房间(曾是%(oldName)s)", "encryption": { "access_secret_storage_dialog": { + "alternatives": "如果你有安全密钥或安全口令,这也会起作用。", "key_validation_text": { - "wrong_security_key": "安全密钥错误" + "wrong_security_key": "你输入的恢复密钥不正确。" }, + "privacy_warning": "确保此时无人窥视此屏幕!", "restoring": "从备份恢复密钥", - "security_key_title": "安全密钥" + "security_key_label": "恢复密钥", + "security_key_title": "输入恢复密钥" }, "bootstrap_title": "设置密钥", - "confirm_encryption_setup_body": "点击下方按钮以确认设置加密。", + "confirm_encryption_setup_body": "点击以下按钮确认加密设置。", "confirm_encryption_setup_title": "确认加密设置", - "cross_signing_room_normal": "此房间是端到端加密的", - "cross_signing_room_verified": "房间中所有人都已被验证", - "cross_signing_room_warning": "有人在使用未知会话", - "cross_signing_user_normal": "你没有验证此用户。", - "cross_signing_user_verified": "你验证了此用户。此用户已验证了其全部会话。", - "cross_signing_user_warning": "此用户没有验证其全部会话。", - "event_shield_reason_authenticity_not_guaranteed": "此加密消息的真实性无法在此设备上保证。", + "continue_with_reset": "继续重置", + "cross_signing_room_normal": "此房间已端到端加密", + "cross_signing_room_verified": "此房间中的每个成员都已验证", + "cross_signing_room_warning": "有人正在使用未知会话", + "cross_signing_user_normal": "你尚未验证此用户。", + "cross_signing_user_verified": "你已验证此用户。此用户已验证其所有会话。", + "cross_signing_user_warning": "该用户尚未验证其所有会话。", + "enter_recovery_key": "输入恢复密钥", + "event_shield_reason_authenticity_not_guaranteed": "此设备无法保证此加密消息的真实性。", + "event_shield_reason_mismatched_sender": "事件的发送者与发送该事件的设备的所有者不匹配。", "event_shield_reason_mismatched_sender_key": "由未验证的会话加密", + "event_shield_reason_unknown_device": "由未知或已被删除的设备加密。", + "event_shield_reason_unsigned_device": "由未经所有者验证的设备加密。", + "event_shield_reason_unverified_identity": "由未经验证的用户加密。", "export_unsupported": "你的浏览器不支持所需的密码学扩展", + "forgot_recovery_key": "忘记恢复密钥?", + "identity_needs_reset_description": "你必须重置数字身份才能确保访问消息历史", "import_invalid_keyfile": "不是有效的 %(brand)s 密钥文件", - "import_invalid_passphrase": "身份验证失败:密码错误?", + "import_invalid_passphrase": "身份验证检查失败:密码不正确?", + "key_storage_out_of_sync": "你的密钥存储不同步。", + "key_storage_out_of_sync_description": "请确认恢复密钥,以保持对密钥存储与消息历史记录的访问权。", + "message_shared_by": "由于此消息在发送时你不位于此房间,%(displayName)s(%(userId)s)共享了此消息。", "messages_not_secure": { - "cause_1": "你的家服务器", - "cause_2": "你正在验证的用户所连接的家服务器", - "cause_3": "你或其他用户的互联网连接", + "cause_1": "你的主服务器版本太旧,不支持所需的最低 API 版本。请联系你的服务器所有者或升级你的服务器。", + "cause_2": "你正在验证的用户所连接的主服务器", + "cause_3": "你或其他用户的 Internet 连接", "cause_4": "你或其他用户的会话", - "heading": "以下之一可能被损害:", - "title": "你的消息不安全" + "heading": "下列中的某项可能已泄露:", + "title": "你的消息传递不安全" }, "new_recovery_method_detected": { - "description_1": "检测到新的安全短语和安全消息密钥。", - "description_2": "此会话正在使用新的恢复方法加密历史。", - "title": "新恢复方式", - "warning": "如果你没有设置新恢复方式,可能有攻击者正试图侵入你的账户。请立即更改你的账户密码并在设置中设定一个新恢复方式。" + "description_1": "已检测到用于安全信息的新安全口令与密钥。", + "description_2": "此会话正在使用新的恢复方法加密历史记录。", + "title": "新的恢复方法", + "warning": "如果你未设置新的恢复方法,攻击者可能正在尝试访问你的账户。请立即在“设置”中更改你的账户密码并设置新的恢复方法。" }, + "pinned_identity_changed": "%(displayName)s (%(userId)s) 的数字身份已重置。了解更多", + "pinned_identity_changed_no_displayname": "%(userId)s的数字身份已重置。了解更多", "recovery_method_removed": { - "description_1": "此会话已检测到你的安全短语和安全消息密钥被移除。", - "description_2": "如果你出于意外这样做了,你可以在此会话上设置安全消息,以使用新的加密方式重新加密此会话的消息历史。", - "title": "恢复方式已移除", - "warning": "如果你没有移除此恢复方式,可能有攻击者正试图侵入你的账户。请立即更改你的账户密码并在设置中设定一个新的恢复方式。" + "description_1": "此会话检测到你的安全口令与安全消息密钥已被移除。", + "description_2": "如果不慎执行了此操作,你可以为此会话设置安全消息传递,它将使用新的恢复方法重新加密此会话的消息历史。", + "title": "恢复方法已移除", + "warning": "如果你未移除恢复方法,攻击者可能正在尝试访问你的账户。请立即在“设置”中更改你的账户密码并设置新的恢复方法。" }, + "set_up_recovery": "备份聊天", + "set_up_recovery_toast_description": "你的聊天已被端到端加密自动备份。如果你无法访问所有设备,则需要使用恢复密钥并保留数字身份。", "set_up_toast_title": "设置安全备份", "setup_secure_backup": { - "explainer": "在登出之前请备份密钥以免丢失。" + "explainer": "移除此设备前备份密钥以防止丢失。" }, + "turn_on_key_storage": "启用密钥存储", + "turn_on_key_storage_description": "这将允许你在新设备上查看聊天历史, 这是备份聊天与数字身份所必需的。", "udd": { - "interactive_verification_button": "用表情符号交互式验证", - "other_ask_verify_text": "要求此用户验证其会话,或在下面手动进行验证。", - "other_new_session_text": "%(name)s(%(userId)s)登录到未验证的新会话:", - "own_ask_verify_text": "使用以下选项之一验证你的其他会话。", - "own_new_session_text": "你登录了未经过验证的新会话:", - "title": "不可信任" + "interactive_verification_button": "使用 Emoji 交互式验证", + "other_ask_verify_text": "请该用户验证其会话,或在下面手动验证。", + "other_new_session_text": "%(name)s(%(userId)s)在未验证的情况下登录了一个新会话:", + "own_ask_verify_text": "请使用以下选项之一验证你的其它会话。", + "own_new_session_text": "你在未验证的情况下登录了新会话:", + "title": "未被信任" }, "unable_to_setup_keys_error": "无法设置密钥", "verification": { - "accepting": "正在接受……", + "accepting": "正在接受…", "after_new_login": { "device_verified": "设备已验证", "skip_verification": "暂时跳过验证", "verify_this_device": "验证此设备" }, - "cancelling": "正在取消……", - "complete_action": "收到", + "cancelled_verification": "请求超时、被拒绝或验证结果不匹配。", + "cancelling": "正在取消…", + "cant_confirm": "无法确认?", + "complete_action": "明白", "complete_description": "你已成功验证此用户。", "complete_title": "已验证!", - "explainer": "此用户的安全消息是端到端加密的,不能被第三方读取。", - "in_person": "为了安全,请当面完成或使用信任的方法交流。", - "incoming_sas_device_dialog_text_1": "验证此设备以将其标记为已信任。在收发端到端加密消息时,信任设备可让你与其他用户更加放心。", - "incoming_sas_device_dialog_text_2": "验证此设备会将其标记为已信任,与此同时,其他验证了你的用户也会信任此设备。", - "incoming_sas_dialog_title": "收到验证请求", - "incoming_sas_user_dialog_text_1": "验证此用户并将其标记为已信任。在收发端到端加密消息时,信任用户可让你更加放心。", - "incoming_sas_user_dialog_text_2": "验证此用户会将其会话标记为已信任,与此同时,你的会话也会被此用户标记为已信任。", - "no_support_qr_emoji": "你正在尝试验证的设备不支持扫码QR码或表情符号验证,这是%(brand)s所支持的。用不同的客户端试试。", - "other_party_cancelled": "另一方取消了验证。", - "prompt_encrypted": "验证房间中所有用户以确保其安全。", - "prompt_unencrypted": "在加密房间中,验证所有用户以确保其安全。", - "qr_or_sas": "%(qrCode)s或%(emojiCompare)s", + "confirm_identity_description": "验证此设备以设置安全消息传递", + "confirm_identity_title": "确认你的数字身份", + "confirm_the_emojis": "确认以下 Emoji 与你其它设备上显示的相符。", + "error_starting_description": "我们无法与其他用户开始聊天。", + "error_starting_title": "开始验证时出错", + "explainer": "与此用户的安全消息已进行端到端加密,第三方无法读取。", + "in_person": "为了安全起见,请亲自执行此操作或使用受信任的通信方式。", + "incoming_sas_device_dialog_text_1": "验证此设备并将其标记为受信任。信任此设备可让你和其它用户在使用端到端加密消息时更加安心。", + "incoming_sas_device_dialog_text_2": "验证此设备会将其标记为受信任,并且已与你验证过的用户将信任此设备。", + "incoming_sas_dialog_title": "验证请求传入", + "incoming_sas_dialog_waiting": "等待对方确认…", + "incoming_sas_user_dialog_text_1": "验证此用户并将其标记为受信任的用户。其可以让你在使用端到端加密消息时更加安心。", + "incoming_sas_user_dialog_text_2": "验证此用户会将他们的会话标记为受信任,也会将你的会话标记为对他们受信任。", + "manual": { + "already_verified": "此设备已经过验证", + "already_verified_and_wrong_fingerprint": "提供的指纹不匹配,但设备已验证!", + "device_id": "设备 ID", + "failure_description": "“%(deviceId)s”验证失败:%(error)s", + "failure_title": "验证失败", + "fingerprint": "指纹(会话密钥)", + "no_crypto": "无法验证设备:加密组件未启用", + "no_device": "无法验证设备:设备“%(deviceId)s”未找到", + "no_userid": "无法验证设备:无法找到用户 ID", + "success_description": "此设备(%(deviceId)s)已交叉签名", + "success_title": "验证成功", + "text": "请提供你自己的一台设备的 ID 和指纹进行验证。请注意,这将允许其它设备以你的身份发送和接收消息。如果有人告诉你在此处粘贴内容,你很可能被诈骗!", + "wrong_fingerprint": "无法验证设备“%(deviceId)s”:提供的指纹“%(fingerprint)s”与设备指纹“%(fprint)s”不匹配。" + }, + "no_support_qr_emoji": "你尝试验证的设备不支持扫描二维码或 Emoji 验证,而 %(brand)s 支持这些功能。请尝试使用其它客户端。", + "now_you_can": "现在你可以安全地读取或发送消息,并且与你聊天的任何人也可以信任此设备。", + "once_accepted_can_continue": "一旦被接受,你将可以继续验证。", + "other_party_cancelled": "对方已取消验证。", + "prompt_encrypted": "验证房间中的所有用户以确保安全。", + "prompt_unencrypted": "在加密房间中验证所有用户以确保安全。", + "qr_or_sas": "%(qrCode)s 或 %(emojiCompare)s", "qr_prompt": "扫描此唯一代码", - "qr_reciprocate_same_shield_user": "快完成了!%(displayName)s 显示了同样的盾牌吗?", + "qr_reciprocate_check_again_device": "再次在你的其它设备上检查以完成验证。", + "qr_reciprocate_no": "否,我没有看到绿色盾牌", + "qr_reciprocate_same_shield_user": "即将完成!%(displayName)s 显示的是相同的盾牌吗?", + "qr_reciprocate_yes": "是,我看到了绿色盾牌", + "request_toast_accept_user": "验证用户", + "request_toast_decline_counter": "忽略(剩余 %(counter)s 秒)", "request_toast_detail": "来自 %(ip)s 的 %(deviceId)s", - "sas_caption_self": "确认屏幕上出现以下数字,以验证设备。", - "sas_caption_user": "通过在其屏幕上显示以下数字来验证此用户。", - "sas_description": "若你在两个设备上都没有相机,比较唯一一组表情符号", - "sas_emoji_caption_user": "通过在其屏幕上显示以下表情符号来验证此用户。", - "sas_match": "它们匹配", - "sas_no_match": "它们不匹配", - "sas_prompt": "比较唯一表情符号", - "scan_qr": "扫码验证", - "scan_qr_explainer": "请 %(displayName)s 扫描你的代码:", + "request_toast_start_verification": "开始验证", + "sas_caption_self": "通过确认以下数字出现在设备屏幕上以验证此设备。", + "sas_caption_user": "通过确认以下数字出现在用户的屏幕上已验证此用户。", + "sas_description": "如果两台设备都没有摄像头,请比较一组唯一的 Emoji", + "sas_emoji_caption_user": "通过确认以下 Emoji 是否出现在对方的屏幕上从而验证此用户。", + "sas_match": "匹配", + "sas_no_match": "不匹配", + "sas_prompt": "比较唯一 Emoji", + "scan_qr": "通过扫描验证", + "scan_qr_explainer": "请求 %(displayName)s 扫描代码:", "start_button": "开始验证", - "successful_user": "你成功验证了 %(displayName)s!", - "unsupported_method": "无法找到支持的验证方法。", - "unverified_session_toast_title": "现在登录。请问是你本人吗?", - "unverified_sessions_toast_description": "检查以确保你的账户是安全的", - "unverified_sessions_toast_reject": "稍后再说", + "successful_user": "你已成功验证 %(displayName)s!", + "unsupported_method": "无法找到受支持的验证方法。", + "unverified_session_toast_accept": "是我", + "unverified_session_toast_title": "有新登录。是否为本人?", + "unverified_sessions_toast_description": "审阅以确保你的账户安全", + "unverified_sessions_toast_reject": "稍后", "unverified_sessions_toast_title": "你有未验证的会话", - "verification_dialog_title_device": "验证其他设备", + "use_another_device": "使用另一设备", + "use_recovery_key": "使用恢复密钥", + "verification_dialog_title_choose": "选择验证方式", + "verification_dialog_title_compare_emojis": "比较 Emoji", + "verification_dialog_title_confirm_green_shield": "确认你在其它设备上看到了绿色盾牌。", + "verification_dialog_title_device": "验证其它设备", + "verification_dialog_title_failed": "验证失败", + "verification_dialog_title_start_on_other_device": "在其它设备上开始验证", "verification_dialog_title_user": "验证请求", - "verification_skip_warning": "如果不进行验证,您将无法访问您的所有消息,并且在其他人看来可能不受信任。", - "verification_success_with_backup": "你的新设备已通过验证。它现在可以访问你的加密消息,并且其它用户会将其视为受信任的。", - "verification_success_without_backup": "你的新设备现已验证。其他用户将会视其为受信任的。", - "verify_emoji": "通过表情符号验证", - "verify_emoji_prompt": "通过比较唯一的表情符号来验证。", - "verify_emoji_prompt_qr": "如果你不能扫描以上代码,请通过比较唯一的表情符号来验证。", - "verify_later": "我稍后进行验证", - "waiting_for_user_accept": "正在等待%(displayName)s接受……", - "waiting_other_device": "正等待你在其它设备上验证……", - "waiting_other_device_details": "正等待你在其它设备上验证,%(deviceName)s(%(deviceId)s)……", - "waiting_other_user": "正在等待%(displayName)s进行验证……" + "verification_dialog_title_verified": "设备已验证", + "verification_skip_warning": "在没有验证的情况下你将无法访问所有消息,并且无法被其他人信任。", + "verification_success_with_backup": "你的新设备现已验证。它可以访问你的加密消息,其他用户将视其为受信任的设备。", + "verification_success_without_backup": "你的新设备已通过验证。其他用户将看到它是受信任的。", + "verify_by_completing_one_of": "完成以下任一方式进行验证:", + "verify_emoji": "使用 Emoji 验证", + "verify_emoji_prompt": "通过比较唯一的 Emoji 进行验证。", + "verify_emoji_prompt_qr": "如果无法扫描上述二维码,可通过比较唯一的 Emoji 验证。", + "verify_later": "稍后验证", + "waiting_for_user_accept": "正在等待 %(displayName)s 接受…", + "waiting_other_device": "正在等待其它设备验证…", + "waiting_other_device_details": "等待你在另一台设备上验证,%(deviceName)s(%(deviceId)s)…", + "waiting_other_user": "正在等待 %(displayName)s 验证…" }, "verification_requested_toast_title": "已请求验证", - "verify_toast_description": "其他用户可能不信任它", - "verify_toast_title": "验证此会话" + "verified_identity_changed": "%(displayName)s (%(userId)s) 的数字身份已重置。了解更多", + "verified_identity_changed_no_displayname": "%(userId)s的数字身份已重置。了解更多", + "verify_toast_description": "可能不受其他用户信任", + "verify_toast_title": "验证此设备", + "withdraw_verification_action": "撤消验证" }, "error": { - "admin_contact": "请 联系你的服务管理员 以继续使用本服务。", - "admin_contact_short": "请联系你的服务器管理员。", - "app_launch_unexpected_error": "准备软件时出现意外错误,详细信息请查看控制台。", - "cannot_load_config": "无法加载配置文件:请刷新页面以重试。", - "connection": "与家服务器通讯时出现问题,请稍后再试。", - "dialog_description_default": "发生了一个错误。", - "edit_history_unsupported": "你的家服务器似乎不支持此功能。", + "admin_contact": "请联系服务管理员以继续使用此服务。", + "admin_contact_short": "联系你的服务器管理员。", + "app_launch_unexpected_error": "准备 App 时发生意外错误。详情请查看控制台。", + "cannot_load_config": "无法加载配置文件:请刷新页面重试。", + "connection": "与主服务器通信时出现问题,请稍后重试。", + "dialog_description_default": "发生错误。", + "download_media": "下载源媒体失败,未找到源 URL", + "edit_history_unsupported": "你的主服务器似乎不支持此功能。", "failed_copy": "复制失败", - "hs_blocked": "此 homeserver 已被其管理员屏蔽。", - "invalid_configuration_mixed_server": "配置无效:无法与 default_server_name 或 default_server_config 一起指定 default_hs_url", - "invalid_configuration_no_server": "配置无效:没有指定默认服务器。", - "invalid_json": "Element 配置文件中包含无效的 JSON。请改正错误并重新加载页面。", + "hs_blocked": "此主服务器已被其管理员屏蔽。", + "invalid_configuration_mixed_server": "配置无效:“default_hs_url”不能与“default_server_name”或“default_server_config”同时指定。", + "invalid_configuration_no_server": "配置无效:未指定默认服务器。", + "invalid_json": "你的 Element 配置包含无效的 JSON。请修正此问题并重载页面。", "invalid_json_detail": "来自解析器的消息:%(message)s", - "invalid_json_generic": "无效的 JSON", - "mau": "此家服务器已达到其每月活跃用户限制。", + "invalid_json_generic": "无效 JSON", + "mau": "此主服务器已达到每月活跃用户数量限制。", "misconfigured": "Element 配置错误", - "mixed_content": "当浏览器地址栏里有 HTTPS 的 URL 时,不能使用 HTTP 连接家服务器。请使用 HTTPS 或者允许不安全的脚本。", - "non_urgent_echo_failure_toast": "你的服务器没有响应一些请求。", - "resource_limits": "本服务器已达到其使用量限制之一。", + "mixed_content": "当浏览器地址栏中显示 HTTPS 网址时,无法通过 HTTP 连接到主服务器。请使用 HTTPS 或启用不安全的脚本。", + "non_urgent_echo_failure_toast": "你的服务器不响应某些请求。", + "resource_limits": "该主服务器已超出其资源限制。", "session_restore": { - "clear_storage_button": "清除存储并登出", - "clear_storage_description": "登出并删除加密密钥?", - "description_1": "我们在尝试恢复你先前的会话时遇到了错误。", - "description_2": "如果你之前使用过较新版本的 %(brand)s,则你的会话可能与当前版本不兼容。请关闭此窗口并使用最新版本。", - "description_3": "清除本页储存在你浏览器上的数据或许能修复此问题,但也会导致你退出登录并无法读取任何已加密的聊天记录。", + "clear_storage_button": "移除此设备", + "clear_storage_description": "移除此设备的同时包括其加密密钥?", + "description_1": "尝试恢复之前的会话时出错。", + "description_2": "如果你之前使用过较新版本的 %(brand)s,你的会话可能与此版本不兼容。请关闭此窗口并返回到较新的版本。", + "description_3": "清除浏览器的存储空间或许可以解决问题,但会将此设备移除,并导致所有加密的聊天历史无法读取。", "title": "无法恢复会话" }, - "something_went_wrong": "出了点问题!", - "storage_evicted_description_1": "一些会话数据,包括加密消息密钥,已缺失。要修复此问题,登出并重新登录,然后从备份恢复密钥。", - "storage_evicted_description_2": "你的浏览器可能在磁盘空间不足时删除了此数据。", - "storage_evicted_title": "缺失会话数据", - "sync": "服务器连接失败,正在重试……", - "tls": "无法连接家服务器 - 请检查网络连接,确保你的家服务器 SSL 证书被信任,且没有浏览器插件拦截请求。", + "something_went_wrong": "出现问题!", + "storage_evicted_description_1": "包括加密消息密钥在内的部分会话数据丢失。请注销并重新登录以修复此问题,并从备份中恢复密钥。", + "storage_evicted_description_2": "你的浏览器可能在磁盘空间不足时移除了这些数据。", + "storage_evicted_title": "会话数据缺失", + "sync": "无法连接到主服务器,正在重试…", + "tls": "无法连接到主服务器,请检查网络连接,确保主服务器的 SSL 证书受信任,并且浏览器扩展程序未阻止请求。", "unknown": "未知错误", "unknown_error_code": "未知错误代码", - "update_power_level": "权力级别修改失败" + "update_history_visibility": "历史可见性更改失败", + "update_power_level": "权力值更改失败" }, - "error_app_opened_in_another_window": "%(brand)s已在另一个窗口中打开。单击“%(label)s”以在此处使用%(brand)s并断开其他窗口的连接。", - "error_database_closed_title": "数据库意外关闭", + "error_app_open_in_another_tab": "切换到其它标签页以连接到 %(brand)s。现在可以关闭此标签页。", + "error_app_open_in_another_tab_title": "%(brand)s 已在其它标签页中打开", + "error_app_opened_in_another_window": "%(brand)s 已在另一个窗口中打开。点击“%(label)s”即可在此处使用 %(brand)s 并断开与另一个窗口的连接。", + "error_database_closed_description": { + "for_desktop": "你的磁盘可能已满。请清理空间并重新加载。", + "for_web": "如果你清除了浏览数据,则预计会出现此消息。%(brand)s 可能也在另一个标签页中打开,或者你的磁盘已满。请清理一些空间并重载。" + }, + "error_database_closed_title": "%(brand)s 已停止工作", "error_dialog": { "copy_room_link_failed": { - "description": "无法将房间的链接复制到剪贴板。", + "description": "无法复制房间链接到剪贴板。", "title": "无法复制房间链接" }, - "error_loading_user_profile": "无法加载用户资料", - "forget_room_failed": "忘记房间失败,错误代码: %(errCode)s" + "error_loading_user_profile": "无法载入用户资料", + "forget_room_failed": "忘记房间 %(errCode)s 失败" }, "error_user_not_logged_in": "用户未登录", "event_preview": { "m.call.answer": { - "dm": "通话中", - "user": "%(senderName)s加入通话", - "you": "你加入通话" + "dm": "通话进行中", + "user": "%(senderName)s 已加入通话", + "you": "你已加入通话" }, "m.call.hangup": { "user": "%(senderName)s 结束了通话", "you": "你结束了通话" }, "m.call.invite": { - "dm_receive": "%(senderName)s正在通话", - "dm_send": "正在等待接听", - "user": "%(senderName)s开始了通话", + "dm_receive": "%(senderName)s 正在通话", + "dm_send": "等待接听", + "user": "%(senderName)s 开始了通话", "you": "你开始了通话" - } + }, + "m.emote": "* %(senderName)s %(emote)s", + "m.reaction": { + "user": "%(sender)s 使用 %(reaction)s 对 %(message)s 作出反应", + "you": "你使用了 %(reaction)s 对 %(message)s 作出反应" + }, + "m.sticker": "%(senderName)s:%(stickerName)s", + "m.text": "%(senderName)s:%(message)s", + "prefix": { + "audio": "音频", + "file": "文件", + "image": "图像", + "poll": "投票", + "video": "视频" + }, + "preview": "%(prefix)s:%(preview)s" }, "export_chat": { - "cancelled": "导出已取消", - "cancelled_detail": "成功取消了导出", - "confirm_stop": "您确定要停止导出数据吗?如果你这样做了,你需要重新开始。", - "creating_html": "正在创建 HTML...", - "creating_output": "正在创建输出...", + "cancelled": "已取消导出", + "cancelled_detail": "导出已被取消", + "confirm_stop": "你确定要停止导出数据?继而导致需要重新开始。", + "creating_html": "正在创建 HTML…", + "creating_output": "正在创建输出…", "creator_summary": "%(creatorName)s 创建了此房间。", "current_timeline": "当前时间线", - "enter_number_between_min_max": "输入一个 %(min)s 和 %(max)s 之间的数字", - "error_fetching_file": "获取文件出错", - "export_info": "这是 导出的开始。导出人 ,导出日期 %(exportDate)s。", - "export_successful": "成功导出!", + "enter_number_between_min_max": "输入 %(min)s 到 %(max)s 之间的数字", + "error_fetching_file": "获取文件时出错", + "export_info": "这是 导出的开始。由 导出于 %(exportDate)s。", + "export_successful": "导出成功!", "exported_n_events_in_time": { "one": "在 %(seconds)s 秒内导出了 %(count)s 个事件", "other": "在 %(seconds)s 秒内导出了 %(count)s 个事件" }, "exporting_your_data": "导出你的数据", "fetched_n_events": { - "one": "迄今获取了 %(count)s 事件", - "other": "迄今获取了 %(count)s 事件" + "one": "已获取距今 %(count)s 个事件", + "other": "已获取 %(count)s 个事件" }, "fetched_n_events_in_time": { - "one": "%(seconds)s 秒内获取了 %(count)s 个事件", - "other": "%(seconds)s 秒内获取了 %(count)s 个事件" + "one": "于 %(seconds)ss 内已获取 %(count)s 个事件", + "other": "于 %(seconds)ss 内已获取 %(count)s 个事件" }, "fetched_n_events_with_total": { - "one": "已获取总共 %(total)s 事件中的 %(count)s 个", - "other": "已获取 %(total)s 事件中的 %(count)s 个" + "one": "已获取 %(total)s 个事件中的 %(count)s 个", + "other": "已获取 %(total)s 个事件中的 %(count)s 个" }, - "fetching_events": "正在获取事件...", - "file_attached": "已附加文件", + "fetching_events": "正在获取事件…", + "file_attached": "附加文件", "format": "格式", "from_the_beginning": "从开头", - "generating_zip": "生成 ZIP", + "generating_zip": "生成 ZIP 压缩文件", "html": "HTML", - "html_title": "导出的数据", - "include_attachments": "包括附件", + "html_title": "已导出的数据", + "include_attachments": "包含附件", "json": "JSON", - "media_omitted": "省略了媒体文件", - "media_omitted_file_size": "省略了媒体文件 - 超出了文件大小限制", + "media_omitted": "媒体已被省略", + "media_omitted_file_size": "媒体已被省略,文件大小超过限制", "messages": "消息", "next_page": "下一组消息", - "num_messages": "消息数", - "num_messages_min_max": "消息数只能是一个介于 %(min)s 和 %(max)s 之间的整数", - "number_of_messages": "指定消息数", - "previous_page": "上一组信息", - "processing_event_n": "正在处理总共 %(total)s 事件中的事件 %(number)s", - "select_option": "从下面的选项中选择以从时间线导出聊天", + "num_messages": "消息数量", + "num_messages_min_max": "消息数量只能是 %(min)s 到 %(max)s 之间的数字", + "number_of_messages": "指定消息数量", + "previous_page": "上一组消息", + "processing": "正在处理…", + "processing_event_n": "处理 %(total)s 个事件中的 %(number)s 个", + "select_option": "选择以下选项以导出时间线中的聊天历史", "size_limit": "大小限制", - "size_limit_min_max": "大小只能是 %(min)sMB 和 %(max)sMB 之间的一个数字", - "starting_export": "正在导出...", - "successful": "成功导出", - "successful_detail": "导出成功了。你可以在下载文件夹中找到导出文件。", + "size_limit_min_max": "大小只能是介于 %(min)s MB 到 %(max)s MB 之间的数字", + "size_limit_postfix": "MB", + "starting_export": "正在开始导出…", + "successful": "导出成功", + "successful_detail": "导出成功。可以在“下载”文件夹中找到。", "text": "纯文本", "title": "导出聊天", - "topic": "话题:%(topic)s", - "unload_confirm": "你确定要在导出过程中退出吗?" + "topic": "主题:%(topic)s", + "unload_confirm": "你确定要在导出期间退出?" }, - "failed_load_async_component": "无法加载!请检查你的网络连接并重试。", + "failed_load_async_component": "无法加载!请检查网络连接并重试。", "feedback": { - "can_contact_label": "如果你有任何后续问题,可以联系我", - "comment_label": "备注", - "existing_issue_link": "请先查找一下 Github 上已有的问题,以免重复。找不到重复问题?发起一个吧。", - "may_contact_label": "如果您想跟进或让我测试即将到来的想法,您可以与我联系", - "platform_username": "我们将会记录你的平台及用户名,以帮助我们尽我们所能地使用你的反馈。", - "pro_type": "专业建议:如果你要发起新问题,请一并提交调试日志,以便我们找出问题根源。", + "can_contact_label": "后续有任何问题时可以联系我", + "comment_label": "评论", + "existing_issue_link": "请先查找 Github Issue 是否有与你同样的问题,如果不存在可以创建一个。", + "may_contact_label": "若要跟进或让我测试新推出的想法时可以联系我", + "platform_username": "我们会记录你的平台与用户名,以帮助我们尽可能多地利用你的反馈。", + "pro_type": "专业建议:若要开始提交 Bug,请同时附上调试日志以便我们确定问题的根源。", "send_feedback_action": "发送反馈", - "sent": "反馈已发送" + "sent": "反馈已发送!谢谢,我们已经收到!" }, "file_panel": { - "empty_description": "从聊天中附加文件或将文件拖放到房间的任何地方。", - "empty_heading": "此房间中没有文件可见", - "guest_note": "你必须 注册 以使用此功能", - "peek_note": "你必须加入房间以看到它的文件" + "empty_description": "在聊天中附加文件或要拖放文件到此房间的任意位置。", + "empty_heading": "此房间暂无可见的文件", + "guest_note": "你必须注册才能使用此功能", + "peek_note": "你必须加入此房间才能其中的文件" }, "forward": { - "filter_placeholder": "搜索房间或用户", + "filter_placeholder": "搜索房间或人员", "message_preview_heading": "消息预览", - "no_perms_title": "你无权执行此操作", + "no_perms_title": "你没有权限执行此操作。", "open_room": "打开房间", "send_label": "发送", "sending": "正在发送", - "sent": "已发送" + "sent": "发送" }, "identity_server": { "change": "更改身份服务器", - "change_prompt": "从身份服务器断开连接而连接到吗?", - "change_server_prompt": "如果你不想使用 以发现你认识的现存联系人并被其发现,请在下方输入另一个身份服务器。", - "checking": "检查服务器", - "description_connected": "你正在使用来发现你认识的现存联系人,同时也让他们可以发现你。你可以在下方更改你的身份服务器。", - "description_disconnected": "你现在没有使用身份服务器。若想发现你认识的现存联系人并被其发现,请在下方添加一个身份服务器。", - "description_optional": "使用身份服务器是可选的。如果你选择不使用身份服务器,你将不能被别的用户发现,也不能用邮箱或电话邀请别人。", - "disconnect": "断开身份服务器连接", - "disconnect_anyway": "仍然断开连接", - "disconnect_offline_warning": "断开连接前,你应从身份服务器删除你的个人数据。不幸的是,身份服务器当前处于离线状态或无法访问。", - "disconnect_personal_data_warning_1": "你仍然在身份服务器 共享你的个人数据。", - "disconnect_personal_data_warning_2": "我们推荐你在断开连接前从身份服务器上删除你的邮箱地址和电话号码。", - "disconnect_server": "从身份服务器 断开连接吗?", - "disconnect_warning": "断开身份服务器连接意味着你将无法被其他用户发现,同时你也将无法使用电子邮件或电话邀请别人。", + "change_prompt": "断开连接身份服务器 并连接到 ?", + "change_server_prompt": "如果你不想使用 来发现你认识的现有联系人,并使其无法被发现,请在下方输入另一个身份服务器。", + "changed": "身份服务器已更改", + "checking": "正在检查服务器", + "description_connected": "你目前正在使用 来发现你认识的现有联系人,并使其能够被你发现。你可以在下面更改你的身份服务器。", + "description_disconnected": "你尚未使用身份服务器。要发现联系人并使其可以被发现,请在下面添加。", + "description_optional": "使用身份服务器是可选的。如果你选择不使用身份服务器,其他用户将无法发现你,也无法通过邮件地址或电话号码邀请其他人。", + "disconnect": "断开身份服务器", + "disconnect_anyway": "强制断开", + "disconnect_offline_warning": "断开连接前你应该从身份服务器 移除个人数据。很遗憾,身份服务器 当前处于离线状态或无法访问。", + "disconnect_personal_data_warning_1": "你仍在身份服务器 分享个人数据。", + "disconnect_personal_data_warning_2": "我们建议你在断开连接之前,从身份服务器中删除邮件地址与电话号码。", + "disconnect_server": "断开身份服务器 ?", + "disconnect_warning": "断开连接身份服务器意味着其他用户将无法发现你,并且你将无法通过邮件地址或电话号码邀请其他人。", "do_not_use": "不使用身份服务器", - "error_connection": "无法连接到身份服务器", - "error_invalid": "身份服务器无效(状态码 %(code)s)", - "error_invalid_or_terms": "服务协议未同意或身份服务器无效。", - "no_terms": "你选择的身份服务器没有服务协议。", - "suggestions": "你应该:", - "suggestions_1": "检查你的浏览器是否安装有可能屏蔽身份服务器的插件(例如 Privacy Badger)", + "error_connection": "无法连接身份服务器", + "error_invalid": "不是有效的身份服务器(状态码 %(code)s)", + "error_invalid_or_terms": "未接受服务条款或身份服务器无效。", + "no_terms": "你选择的身份服务器没有任何服务条款。", + "suggestions": "你应:", + "suggestions_1": "检查你的浏览器扩展是否有任何可能阻止身份服务器(例如 Privacy Badger)。", "suggestions_2": "联系身份服务器 的管理员", - "suggestions_3": "等待并稍后重试", + "suggestions_3": "稍后再试", "url": "身份服务器(%(server)s)", - "url_field_label": "输入一个新的身份服务器", - "url_not_https": "身份服务器URL必须是HTTPS" + "url_field_label": "输入新的身份服务器", + "url_not_https": "身份服务器 URL 必须为 HTTPS" }, - "in_space": "在 %(spaceName)s 空间。", - "in_space1_and_space2": "在 %(space1Name)s 和 %(space2Name)s 空间。", + "in_space": "位于 %(spaceName)s。", + "in_space1_and_space2": "位于空间 %(space1Name)s 与 %(space2Name)s。", "in_space_and_n_other_spaces": { - "one": "在 %(spaceName)s 和其他 %(count)s 个空间。", - "other": "在 %(spaceName)s 和其他 %(count)s 个空间。" + "one": "%(spaceName)s 与剩余 1 个空间。", + "other": "位于 %(spaceName)s 与其余 %(count)s 个空间。" }, "incompatible_browser": { - "title": "不支持的浏览器" + "continue": "强制继续", + "description": "%(brand)s 使用了一些当前浏览器中不可用的浏览器功能。%(detail)s", + "detail_can_continue": "如果你继续操作,某些功能可能会停止运行,并且未来可能会丢失数据。", + "detail_no_continue": "如果你使用的不是最新版本,请尝试更新浏览器然后重试。", + "learn_more": "了解更多", + "linux": "Linux", + "macos": "Mac", + "supported_browsers": "为了获得最佳体验,请使用 ChromeFirefoxEdgeSafari。", + "title": "%(brand)s 不支持此浏览器", + "use_desktop_heading": "请改用 %(brand)s 桌面应用", + "use_mobile_heading": "在手机上换用 %(brand)s", + "use_mobile_heading_after_desktop": "或使用移动 App", + "windows_64bit": "Windows (64 位)", + "windows_arm_64bit": "Windows (ARM 64 位)" }, "info_tooltip_title": "信息", "integration_manager": { - "error_connecting": "此集成管理器为离线状态或者其不能访问你的家服务器。", - "error_connecting_heading": "不能连接到集成管理器", - "explainer": "集成管理器接收配置数据,并可以以你的名义修改挂件、发送房间邀请及设置权力级别。", + "connecting": "正在连接到集成管理器…", + "error_connecting": "集成管理器处于离线状态或其无法连接到你的主服务器。", + "error_connecting_heading": "无法连接到集成管理器", + "explainer": "集成管理器可接收配置数据, 并可以代表你修改小部件、发送房间邀请与设置权力值。", "manage_title": "管理集成", - "use_im": "使用集成管理器管理机器人、挂件和贴纸包。", - "use_im_default": "使用集成管理器(%(serverName)s)管理机器人、挂件和贴纸包。" + "toggle_label": "启用集成管理器", + "use_im": "使用集成管理器管理机器人、小部件与贴纸包。", + "use_im_default": "使用集成管理器 (%(serverName)s) 管理机器人、小部件与贴纸包。" }, "integrations": { - "disabled_dialog_title": "集成已禁用", - "impossible_dialog_description": "你的 %(brand)s 不允许你使用集成管理器来完成此操作,请联系管理员。", - "impossible_dialog_title": "集成未被允许" + "disabled_dialog_description": "在设置中启用“%(manageIntegrations)s”以执行此操作。", + "disabled_dialog_title": "集成已被禁用", + "impossible_dialog_description": "%(brand)s 不允许使用集成管理器执行此操作。请联系管理员。", + "impossible_dialog_title": "不允许集成" }, "invite": { - "email_caption": "通过邮箱邀请", - "email_use_default_is": "使用一个身份服务器以通过邮箱邀请。使用默认(%(defaultIdentityServerName)s)或在设置中管理。", - "email_use_is": "使用一个身份服务器以通过邮箱邀请。在设置中管理。", - "error_already_invited_room": "用户已被邀请至房间", - "error_already_invited_space": "用户已被邀请至空间", - "error_already_joined_room": "用户已在房间中", - "error_already_joined_space": "用户已在空间中", - "error_bad_state": "用户必须先解封才能被邀请。", - "error_dm": "我们无法创建你的私聊。", - "error_find_room": "尝试邀请用户时出错。", - "error_find_user_description": "下列用户可能不存在或无效,因此不能被邀请:%(csvNames)s", - "error_find_user_title": "寻找以下用户失败", - "error_invite": "我们不能邀请这些用户。请检查你想邀请的用户并重试。", - "error_permissions_room": "你没有权限将其他用户邀请至本房间。", - "error_permissions_space": "你无权邀请他人加入此空间。", - "error_profile_undisclosed": "用户可能存在页可能不存在", - "error_transfer_multiple_target": "通话只能转移到单个用户。", + "email_caption": "通过邮件邀请", + "email_limit_one": "通过邮件发送邀请一次只能发送一个", + "email_use_default_is": "使用身份服务器通过邮件地址邀请。使用默认的 (%(defaultIdentityServerName)s) 或在 设置 中进行管理。", + "email_use_is": "使用身份服务器通过邮件地址邀请。请在 “设置” 中进行管理。", + "error_already_invited_room": "用户已被邀请到房间", + "error_already_invited_space": "用户已被邀请到空间", + "error_already_joined_room": "用户已位于此房间", + "error_already_joined_space": "用户已位于此空间", + "error_bad_state": "该用户必须先被解封才能被邀请。", + "error_dm": "无法创建私聊。", + "error_find_room": "在邀请用户时出现问题。", + "error_find_user_description": "以下用户可能不存在或无效,因此无法被邀请:%(csvNames)s", + "error_find_user_title": "以下用户查找失败", + "error_invite": "我们无法邀请这些用户。请检查你要邀请的用户,然后重试。", + "error_permissions_room": "你无权邀请人员进入此房间。", + "error_permissions_space": "你无权邀请人员访问此空间。", + "error_profile_undisclosed": "用户既有可能存在,也有可能不存在", + "error_transfer_multiple_target": "通话只能转接到单个用户。", + "error_unfederated_room": "此房间为非联合房间。你不能从外部服务器邀请人员。", + "error_unfederated_space": "此空间未联合。你无法邀请人员到外部服务器。", "error_unknown": "未知服务器错误", "error_user_not_found": "用户不存在", - "error_version_unsupported_room": "用户的家服务器不支持此房间版本。", - "error_version_unsupported_space": "用户的家服务器版本不支持空间。", + "error_version_unsupported_room": "用户的主服务器不支持此房间的版本。", + "error_version_unsupported_space": "此主服务器不支持使用电话号码进行身份验证。", "failed_generic": "操作失败", "failed_title": "邀请失败", - "invalid_address": "无法识别地址", - "name_email_mxid_share_room": "使用名字、电子邮件地址、用户名(如)邀请某人或分享此房间。", - "name_email_mxid_share_space": "使用某人的名字、电子邮箱地址或用户名(如 )邀请他们,或分享此空间。", - "name_mxid_share_room": "使用某人的名字、用户名(如 )或分享此房间来邀请他们。", - "name_mxid_share_space": "使用某人的名字、用户名(如 )邀请他们,或分享此空间。", + "invalid_address": "未识别的地址", + "name_email_mxid_share_room": "使用对方的名称、邮件地址、用户名(例如 )或分享此房间。", + "name_email_mxid_share_space": "使用对方的名称、邮件地址、用户名(例如 )邀请,或分享此房间。", + "name_mxid_share_room": "请使用名称、用户名(例如 )邀请他人,或分享此房间。", + "name_mxid_share_space": "请使用名称、用户名(例如 )邀请他人,或分享此空间。", + "progress": { + "dont_close": "在完成前请勿退出 app。", + "preparing": "正在准备邀请…" + }, "recents_section": "最近对话", - "room_failed_partial": "我们已向其他人发送邀请,但无法邀请以下人员至", + "room_failed_partial": "我们已邀请其他人加入,但以下人员无法受邀加入 。", "room_failed_partial_title": "部分邀请无法发送", - "room_failed_title": "未能邀请用户加入 %(roomName)s", + "room_failed_title": "无法邀请用户到 %(roomName)s", "send_link_prompt": "或发送邀请链接", - "start_conversation_name_email_mxid_prompt": "使用某人的名称、电子邮箱地址或用户名来与其开始对话(如 )。", - "start_conversation_name_mxid_prompt": "使用某人的名字或用户名(如 )开始与其进行对话。", - "suggestions_disclaimer": "出于隐私考虑,部分建议可能会被隐藏。", - "suggestions_disclaimer_prompt": "如果您看不到您要找的人,请将您的邀请链接发送给他们。", - "suggestions_section": "最近私聊", - "to_room": "邀请至 %(roomName)s", - "to_space": "邀请至 %(spaceName)s", + "start_conversation_name_email_mxid_prompt": "要与某人开始对话,请使用其名称、邮件地址或用户名(例如 )。", + "start_conversation_name_mxid_prompt": "使用名称或用户名(例如 )与他人开始对话。", + "suggestions_disclaimer": "由于隐私原因,某些建议可能被隐藏。", + "suggestions_disclaimer_prompt": "如果你看不到要找的人员,请复制并向其发送以下邀请链接。", + "suggestions_section": "最近的私聊", + "to_room": "邀请到 %(roomName)s", + "to_space": "邀请到 %(spaceName)s", "transfer_dial_pad_tab": "拨号盘", - "transfer_user_directory_tab": "用户目录", - "unable_find_profiles_description_default": "找不到下列 Matrix ID 的用户资料,你还是要邀请吗?", - "unable_find_profiles_invite_label_default": "还是邀请", - "unable_find_profiles_invite_never_warn_label_default": "还是邀请,不用再提醒我", - "unable_find_profiles_title": "以下用户可能不存在" + "transfer_user_directory_tab": "用户名册", + "unable_find_profiles_description_default": "无法找到下列 Matrix ID 的个人资料,你是否仍然要邀请?", + "unable_find_profiles_invite_label_default": "强制邀请", + "unable_find_profiles_invite_never_warn_label_default": "强制邀请并不再警告", + "unable_find_profiles_title": "以下用户可能不存在", + "unban_first_title": "除非被解封否则用户无法被邀请" }, - "inviting_user1_and_user2": "正在邀请 %(user1)s 与 %(user2)s", + "inviting_user1_and_user2": "邀请 %(user1)s 与 %(user2)s", "inviting_user_and_n_others": { - "one": "正在邀请%(user)s和另外1个人", - "other": "正在邀请%(user)s和其他%(count)s人" + "one": "邀请 %(user)s 及剩余 1 个", + "other": "邀请了 %(user)s 与剩余 %(count)s 个" }, "items_and_n_others": { - "other": " 和其他 %(count)s 人", - "one": " 与另一个人" + "one": " 以及剩余 1 个", + "other": " 以及剩余 %(count)s 个" }, "keyboard": { - "activate_button": "激活选中的按钮", + "activate_button": "激活选择的按钮", + "alt": "Alt", "autocomplete_cancel": "取消自动补全", "autocomplete_force": "强制完成", - "autocomplete_navigate_next": "下个自动完成建议", - "autocomplete_navigate_prev": "上个自动完成建议", + "autocomplete_navigate_next": "下一个自动补全建议", + "autocomplete_navigate_prev": "上一个自动补全建议", "backspace": "", "cancel_reply": "取消回复消息", "category_autocomplete": "自动补全", @@ -1104,393 +1438,510 @@ "category_navigation": "导航", "category_room_list": "房间列表", "close_dialog_menu": "关闭对话框或上下文菜单", - "composer_jump_end": "跳至编辑器尾部", - "composer_jump_start": "跳至编辑器的开头", - "composer_navigate_next_history": "导航到编辑器历史里的下条消息", - "composer_navigate_prev_history": "导航到编辑器历史里的上条消息", - "composer_new_line": "换行", + "composer_jump_end": "跳转到编辑器末尾", + "composer_jump_start": "跳转到编辑器开头", + "composer_navigate_next_history": "导航到下一个消息编辑器历史", + "composer_navigate_prev_history": "导航到上一个消息编辑器历史", + "composer_new_line": "另起一行", "composer_redo": "重做编辑", "composer_toggle_bold": "切换粗体", "composer_toggle_code_block": "切换代码块", "composer_toggle_italics": "切换斜体", "composer_toggle_link": "切换链接", "composer_toggle_quote": "切换引用", - "composer_undo": "撤销编辑", + "composer_undo": "撤消编辑", + "control": "Ctrl", "dismiss_read_marker_and_jump_bottom": "忽略已读标记并跳转到底部", - "enter": "回车", - "go_home_view": "转到主视图", - "home": "主页", - "jump_first_message": "跳转至第一条消息", - "jump_last_message": "跳转至最后一条消息", + "end": "结束", + "enter": "", + "escape": "Esc", + "go_home_view": "转到主页视图", + "home": "", + "jump_first_message": "跳转到首个消息", + "jump_last_message": "跳转到最新消息", "jump_room_search": "跳转到房间搜索", "jump_to_read_marker": "跳转到最旧的未读消息", - "keyboard_shortcuts_tab": "打开此设置标签页", - "navigate_next_history": "下个最近访问过的房间或空间", - "navigate_next_message_edit": "导航到下条要编辑的消息", - "navigate_prev_history": "上个最近访问过的房间或空间", - "navigate_prev_message_edit": "导航到上条要编辑的消息", - "next_room": "下个房间或私聊", - "next_unread_room": "下个未读房间或私聊", + "keyboard_shortcuts_tab": "打开此设置页", + "navigate_next_history": "下一个最近访问的房间或空间", + "navigate_next_message_edit": "导航到下一条要编辑的消息", + "navigate_prev_history": "上一个最近访问的房间或空间", + "navigate_prev_message_edit": "导航到上一条要编辑的消息", + "next_landmark": "转到下一个焦点", + "next_room": "下一个房间或私聊", + "next_unread_room": "下一个未读房间或私聊", + "number": "[数字]", "open_user_settings": "打开用户设置", "page_down": "", "page_up": "", - "prev_room": "上个房间或私聊", - "prev_unread_room": "上个未读房间或私聊", - "room_list_collapse_section": "折叠房间列表段", - "room_list_expand_section": "展开房间列表段", - "room_list_navigate_down": "在房间列表中向下导航", - "room_list_navigate_up": "在房间列表中向上导航", - "room_list_select_room": "从房间列表选择房间", - "scroll_down_timeline": "在时间线里向下滚动", - "scroll_up_timeline": "在时间线里向上滚动", - "search": "搜索(必须启用)", + "prev_landmark": "转到上一个焦点", + "prev_room": "上一个房间或私聊", + "prev_unread_room": "上一个未读房间或私聊", + "room_list_collapse_section": "折叠房间列表部分", + "room_list_expand_section": "展开房间列表部分", + "room_list_navigate_down": "向下导航到房间列表", + "room_list_navigate_up": "向上导航到房间列表", + "room_list_select_room": "从房间列表中选择房间", + "save": "保存", + "scroll_down_timeline": "向下滚动时间线", + "scroll_up_timeline": "向上滚动时间线", + "search": "搜索(要使其生效必须启用相关功能)", "send_sticker": "发送贴纸", + "shift": "Shift", "space": "空格", - "switch_to_space": "按数字切换到空间", - "toggle_hidden_events": "切换隐藏事件可见性", + "switch_to_space": "使用数字切换空间", + "toggle_hidden_events": "切换隐藏事件的可见性", "toggle_microphone_mute": "切换麦克风静音", "toggle_right_panel": "切换右侧面板", - "toggle_space_panel": "切换空间仪表盘", - "toggle_top_left_menu": "切换左上方的菜单", - "toggle_webcam_mute": "切换网络相机开/关", + "toggle_space_panel": "切换空间面板", + "toggle_top_left_menu": "切换左上角菜单", + "toggle_webcam_mute": "切换摄像头开/关", "upload_file": "上传文件" }, "labs": { - "ask_to_join": "启用 “需要验证加入请求”", - "beta_description": "%(brand)s的下一步是什么?实验室是早期获得东西、测试新功能和在它们发布前帮助塑造的最好方式。", - "beta_feature": "这是beta功能", - "beta_feedback_leave_button": "要离开beta,请访问你的设置。", - "beta_feedback_title": "%(featureName)sBeta反馈", - "beta_section": "即将到来的功能", - "bridge_state": "在房间设置中显示桥接信息", + "ask_to_join": "启用申请加入", + "beta_description": "%(brand)s 的下一步是什么?实验室是提前获取信息、测试新功能并在其正式推出之前帮助其完善的最佳途径。", + "beta_feature": "此为 Beta 功能", + "beta_feedback_leave_button": "要退出 Beta 测试,请访问设置。", + "beta_feedback_title": "%(featureName)s Beta 功能反馈", + "beta_section": "即将推出的功能", + "bridge_state": "在房间设置中显示桥接器信息", "bridge_state_channel": "频道:", - "bridge_state_creator": "此桥曾由提供。", - "bridge_state_manager": "此桥接由 管理。", - "bridge_state_workspace": "工作空间:", - "click_for_info": "点击获取更多信息", - "currently_experimental": "目前是实验性的。", + "bridge_state_creator": "此桥接器由 提供。", + "bridge_state_manager": "此桥接器由 管理。", + "bridge_state_workspace": "工作区: ", + "click_for_info": "点击显示更多信息", + "currently_experimental": "当前为实验性。", "custom_themes": "支持添加自定义主题", - "element_call_video_rooms": "Element通话视频房间", - "experimental_description": "想要做点实验?试试我们开发中的最新点子。这些功能尚未确定;它们可能不稳定,可能会变动,也可能被完全丢弃。了解更多。", + "dynamic_room_predecessors": "动态房间前身", + "dynamic_room_predecessors_description": "需要启用 MSC3946 以支持“迟到房间归档”。", + "element_call_video_rooms": "Element Call 视频房间", + "encrypted_state_events": "已加密的状态事件", + "encrypted_state_events_description": "启用加密状态事件的实验性支持,此功能会对服务器隐藏房间名称与主题等元数据。这些元数据对之后加入房间的用户与不支持 MSC4362 的客户端隐藏。", + "exclude_insecure_devices": "发送或接收消息时排除不安全的设备", + "exclude_insecure_devices_description": "此模式启用后,加密消息将不会分享给未验证的设备,并且来自未验证设备的消息将显示为“错误”。注意:如果启用此模式,则可能无法与尚未验证其设备的用户通信。", + "experimental_description": "感觉很有实验性?试试我们正在开发的最新创意。这些功能尚未最终确定;它们可能不稳定,可能会改变,也可能被完全放弃。了解更多。", "experimental_section": "早期预览", - "feature_wysiwyg_composer_description": "在消息编辑器中使用富文本代替 Markdown。", - "group_calls": "新的群通话体验", + "extended_profiles_msc_support": "需要服务器支持 MSC4133", + "feature_disable_call_per_sender_encryption": "为 Element Call 禁用“按每个发送者加密”", + "feature_wysiwyg_composer_description": "在消息编辑器中使用富文本取代 Markdown。", + "group_calls": "新的群呼体验", "group_developer": "开发者", "group_encryption": "加密", "group_experimental": "实验性", "group_messaging": "消息传递", - "group_moderation": "审核", + "group_moderation": "管理", "group_profile": "个人资料", "group_rooms": "房间", "group_spaces": "空间", "group_themes": "主题", - "group_voip": "语音和视频", - "group_widgets": "挂件", - "hidebold": "隐藏通知的点标记(仅显示计数标记)", - "html_topic": "显示房间话题的HTML表现形式", - "join_beta": "加入beta", - "join_beta_reload": "加入beta会重载%(brand)s。", - "jump_to_date": "跳至日期(新增 /jumptodate 并跳至日期标头)", - "jump_to_date_msc_support": "需要您的服务器支持 MSC3030", - "latex_maths": "在消息中渲染LaTeX数学", - "leave_beta": "离开beta", - "leave_beta_reload": "离开beta会重载%(brand)s。", - "location_share_live": "实时位置共享", - "location_share_live_description": "临时的实现。位置在房间历史中持续保留。", - "mjolnir": "忽略他人的新方式", - "msc3531_hide_messages_pending_moderation": "让协管员隐藏等待审核的消息。", - "notification_settings": "通知设置", - "notification_settings_beta_caption": "引入一种更简单的方式来更改通知设置。以你喜欢的方式定制%(brand)s。", + "group_threads": "消息列", + "group_ui": "用户界面", + "group_voip": "语音与视频", + "group_widgets": "小部件", + "hidebold": "隐藏通知圆点(仅显示计数器徽章)", + "html_topic": "以 HTML 格式显示房间主题", + "join_beta": "参与 Beta 测试", + "join_beta_reload": "参与 Beta 测试将重载 %(brand)s。", + "jump_to_date": "跳转到日期(在编辑器中输入指令“/jumptodate”可以跳转到当天的消息开头)", + "jump_to_date_msc_support": "需要服务器支持 MSC3030", + "latex_maths": "在消息中渲染 LaTeX 数学公式", + "leave_beta": "退出 Beta 测试", + "leave_beta_reload": "退出 Beta 测试将重载 %(brand)s。", + "location_share_live": "分享实时位置", + "location_share_live_description": "临时实现。位置将持续保留于房间历史。", + "mjolnir": "忽略人员的新方式", + "msc3531_hide_messages_pending_moderation": "允许协管员隐藏等待审核的消息", + "new_room_list": "启用新的房间列表", + "notification_settings": "新的通知设置", + "notification_settings_beta_caption": "介绍一种更简单的方法来更改通知设置。就像你喜欢的那样定制 %(brand)s。", "notification_settings_beta_title": "通知设置", - "report_to_moderators": "报告给协管员", - "report_to_moderators_description": "在支持审核的房间中,“报告”按钮将让你向房间协管员举报滥用行为。", + "notifications": "在房间标题处启用通知面板", + "render_reaction_images": "在反应中渲染自定义图像", + "render_reaction_images_description": "有时被称为“自定义 Emoji”。", + "report_to_moderators": "向协管员举报", + "report_to_moderators_description": "在支持审核的房间中,“举报”按钮可以让你向房间协管员举报滥用行为。", + "room_list_sections": "房间列表区域", + "share_history_on_invite": "与新成员分享加密历史", + "share_history_on_invite_description": "当邀请用户进入历史可见性设为“共享”的加密房间时与该用户共享加密的历史消息,同时你被邀请加入此类房间时接收加密的历史消息。", + "share_history_on_invite_warning": "此功能目前处于实验阶段,并且尚未实践所有安全预防措施。请勿为生产账户启用。", "sliding_sync": "滑动同步模式", - "sliding_sync_description": "正在积极开发中,不能禁用。", - "sliding_sync_server_no_support": "你的服务器缺少原生支持", - "under_active_development": "积极开发中。", + "sliding_sync_description": "正在处于开发中,并且一旦启用就无法禁用。当前与 Element Call 不兼容。", + "sliding_sync_disabled_notice": "重新登录以禁用", + "sliding_sync_server_no_support": "服务器缺少支持", + "under_active_development": "正在处于开发中。", + "unrealiable_e2e": "在加密房间中不可靠", "video_rooms": "视频房间", - "video_rooms_a_new_way_to_chat": "在 %(brand)s 中使用语音和视频的新方式。", - "video_rooms_always_on_voip_channels": "视频房间是嵌入在%(brand)s房间内的总是开启的VoIP频道。", - "video_rooms_beta": "视频房间是beta功能", - "video_rooms_faq1_answer": "使用左侧面板房间部分的“+”按钮。", - "video_rooms_faq1_question": "我如何创建视频房间?", - "video_rooms_faq2_answer": "是的,聊天时间线显示在视频旁。", - "video_rooms_faq2_question": "我能在视频通话的同时使用文字聊天吗?", + "video_rooms_a_new_way_to_chat": "在 %(brand)s 中语音与视频聊天的新方式。", + "video_rooms_always_on_voip_channels": "视频房间是嵌入到 %(brand)s 并始终在线的 VoIP 渠道。", + "video_rooms_beta": "视频房间是 Beta 功能", + "video_rooms_faq1_answer": "在左侧面板的“房间”部分点击“+”按钮。", + "video_rooms_faq1_question": "如何创建视频房间?", + "video_rooms_faq2_answer": "可以,时间线会显示在视频房间的一侧。", + "video_rooms_faq2_question": "我是否可以在视频房间中使用文字聊天?", + "video_rooms_feedbackSubheading": "感谢你试用 Beta 功能,请尽可能详细地描述以便我们改进它。", "wysiwyg_composer": "富文本编辑器" }, "labs_mjolnir": { - "advanced_warning": "⚠ 这些设置是为高级用户准备的。", - "ban_reason": "已忽略/已屏蔽", - "error_adding_ignore": "添加已忽略的用户/服务器时出现错误", + "advanced_warning": "⚠ 以下设置面向高级用户。", + "ban_reason": "已忽略或已屏蔽", + "error_adding_ignore": "添加忽略的用户/服务器时出错", "error_adding_list_description": "请验证房间 ID 或地址并重试。", - "error_adding_list_title": "订阅列表时出现错误", - "error_removing_ignore": "移除已忽略用户/服务器时出现错误", - "error_removing_list_description": "请重试或查看你的终端以获得提示。", - "error_removing_list_title": "取消订阅列表时出现错误", - "explainer_1": "在此处添加你想忽略的用户和服务器。使用星号以使%(brand)s匹配任何字符。例如,@bot:*会忽略全部在任何服务器上以“bot”为名的用户。", - "explainer_2": "忽略人是通过含有封禁规则的封禁列表来完成的。订阅一个封禁列表意味着被此列表阻止的用户/服务器将会对你隐藏。", - "lists": "你正在订阅:", - "lists_description_1": "订阅一个封禁列表会使你加入它!", - "lists_description_2": "如果这不是你想要的,请使用别的的工具来忽略用户。", - "lists_heading": "订阅的列表", - "lists_new_label": "封禁列表的房间 ID 或地址", + "error_adding_list_title": "订阅列表时出错", + "error_removing_ignore": "移除已忽略的用户/服务器时出错", + "error_removing_list_description": "请重试或查看控制台中的提示。", + "error_removing_list_title": "退订列表时出错", + "explainer_1": "在此处添加要忽略的用户与服务器。使用“*”可让 %(brand)s 匹配任何字符。例如 @bot:* 将忽略任何服务器上名称为“bot”的所有用户。", + "explainer_2": "忽略人员是通过“封禁列表”完成的,封禁列表包含谁被封禁的规则。订阅封禁列表意味着位于该列表的用户/服务器将对你隐藏。", + "lists": "你当前已订阅:", + "lists_description_1": "你订阅封禁列表意味着此列表将作为房间加入!", + "lists_description_2": "如果这不是你想要的,请使用其它工具忽略用户。", + "lists_heading": "已订阅的列表", + "lists_new_label": "封禁列表中的房间 ID 或地址", "no_lists": "你没有订阅任何列表", - "personal_empty": "你没有忽略任何人。", - "personal_heading": "个人封禁列表", - "personal_new_label": "要忽略的服务器或用户 ID", - "personal_new_placeholder": "例如: @bot:* 或 example.org", - "personal_section": "你正在忽略:", + "personal_description": "个性化封禁列表包含你不想看到其消息的所有用户/服务器。忽略第一个用户/服务器后,你的房间列表中将出现一个名为“%(myBanList)s”的新房间。请留在该房间确保封禁列表持续生效。", + "personal_empty": "你尚未忽略任何人。", + "personal_heading": "个性化封禁列表", + "personal_new_label": "要忽略的用户 ID 或服务器", + "personal_new_placeholder": "例如 @bot:* 或 example.org", + "personal_section": "你当前忽略:", "room_name": "我的封禁列表", - "room_topic": "这是你屏蔽的用户/服务器的列表——不要离开此房间!", + "room_topic": "此房间包含你已屏蔽的用户与服务器。要使封禁列表生效,请勿离开房间!", "rules_empty": "无", "rules_server": "服务器规则", - "rules_title": "封禁列表规则 - %(roomName)s", + "rules_title": "封禁列表规则:%(roomName)s", "rules_user": "用户规则", - "something_went_wrong": "出现问题。请重试或查看你的终端以获得提示。", + "something_went_wrong": "出现问题。请重试或查看控制台提示。", "title": "已忽略的用户", "view_rules": "查看规则" }, "language_dropdown_label": "语言下拉菜单", "leave_room_dialog": { - "last_person_warning": "你是这里唯一的人。如果你离开了,以后包括你在内任何人都将无法加入。", - "leave_room_question": "你确定要离开房间 “%(roomName)s” 吗?", - "leave_space_question": "你确定要离开空间「%(spaceName)s」吗?", - "room_rejoin_warning": "此房间不是公开房间。如果没有成员邀请,你将无法重新加入。", - "space_rejoin_warning": "此空间并不公开。在没有得到邀请的情况下,你将无法重新加入。" + "last_person_warning": "你是此处唯一的用户。如果你离开,以后将没有人能加入,包括你自己。", + "leave_room_question": "你确定要离开房间“%(roomName)s”?", + "leave_space_question": "你确定要离开空间“%(spaceName)s”?", + "room_leave_admin_warning": "你是此房间的唯一管理员。如果你离开,任何人都将无法更改房间设置或执行其它重要操作。", + "room_leave_mod_warning": "你是此房间的唯一管理员。如果你离开,任何人都将无法更改房间设置或执行其它重要操作。", + "room_rejoin_warning": "此房间非公共房间。你无法在未被邀请的情况下重新加入。", + "space_rejoin_warning": "此空间非公开。未经邀请,你将无法重新加入。" }, "left_panel": { "open_dial_pad": "打开拨号键盘" }, "lightbox": { "rotate_left": "向左旋转", - "rotate_right": "向右旋转" + "rotate_right": "向右旋转", + "title": "图像查看" }, "location_sharing": { - "MapStyleUrlNotConfigured": "此家服务器未配置显示地图。", - "MapStyleUrlNotReachable": "此家服务器未正确配置,故无法显示地图,亦或所配置的地图服务器无法使用。", - "click_drop_pin": "点击以放置图钉", - "click_move_pin": "点击以移动图钉", - "close_sidebar": "关闭侧边栏", + "MapStyleUrlNotConfigured": "此主服务器未配置地图显示。", + "MapStyleUrlNotReachable": "此主服务器的配置不正确,无法显示地图,或者配置的地图服务器可能无法访问。", + "WebGLNotEnabled": "显示地图需要 WebGL,请在浏览器设置中启用。", + "click_drop_pin": "点击以放置锚点", + "click_move_pin": "点击以移动锚点", + "close_sidebar": "关闭边栏", "error_fetch_location": "无法获取位置", - "error_no_perms_description": "你需要拥有正确的权限才能在此房间中共享位置。", - "error_no_perms_title": "你没有权限分享位置", - "error_send_description": "%(brand)s无法发送你的位置。请稍后再试。", + "error_no_perms_description": "你需要拥有合适的权限才能在此房间中分享位置。", + "error_no_perms_title": "你无权分享位置", + "error_send_description": "%(brand)s 无法发送你的位置。请稍后再试。", "error_send_title": "我们无法发送你的位置", "error_sharing_live_location": "分享实时位置时出错", "error_stopping_live_location": "停止实时位置时出错", "expand_map": "展开地图", - "failed_generic": "获取你的位置失败。请之后再试。", - "failed_load_map": "无法加载地图", - "failed_permission": "%(brand)s was denied permission to fetch your location. 请在你的浏览器中允许位置访问。", - "failed_timeout": "尝试获取你的位置超时。请之后再试。", - "failed_unknown": "获取位置时发生错误。请之后再试。", + "failed_generic": "无法获取你的位置。请稍后再试。", + "failed_load_map": "无法载入地图", + "failed_permission": "%(brand)s 被拒绝获取位置信息。请在浏览器设置中允许访问位置信息。", + "failed_timeout": "尝试获取位置超时。请稍后再试。", + "failed_unknown": "获取位置时出现未知错误。请稍后再试。", "find_my_location": "查找我的位置", - "live_description": "%(displayName)s的实时位置", - "live_enable_description": "请注意:这是使用临时实现的实验室功能。这意味着你无法删除你的位置历史,并且甚至在你停止与此房间分享实时位置后,高级用户将仍能查看你的位置历史。", - "live_enable_heading": "实时位置分享", - "live_location_active": "你正在分享你的实时位置", + "live_description": "%(displayName)s 的事实位置", + "live_enable_description": "请注意:这是一项使用临时实施方案的实验室功能。这意味着你将无法删除你的位置记录,即使你停止与此房间共享实时位置,高级用户仍然可以看到你的位置记录。", + "live_enable_heading": "分享实时位置", + "live_location_active": "你正在分享实时位置", "live_location_enabled": "实时位置已启用", "live_location_ended": "实时位置已结束", - "live_location_error": "实时位置错误", + "live_location_error": "实时位置出错", "live_locations_empty": "无实时位置", - "live_share_button": "分享%(duration)s", + "live_share_button": "分享 %(duration)s", "live_toggle_label": "启用实时位置分享", - "live_until": "实时分享直至%(expiryTime)s", + "live_until": "直播直到 %(expiryTime)s", + "live_update_time": "已更新 %(humanizedUpdateTime)s", + "loading_live_location": "正在加载实时位置…", "location_not_available": "位置不可用", "map_feedback": "地图反馈", "mapbox_logo": "Mapbox 图标", - "reset_bearing": "重置为向北方位", - "share_button": "共享位置", + "reset_bearing": "重置方向为正北", + "share_button": "分享位置", "share_type_live": "我的实时位置", - "share_type_own": "我当前的位置", - "share_type_pin": "放置图钉", - "share_type_prompt": "你想分享什么位置类型?", + "share_type_own": "我的当前位置", + "share_type_pin": "放置锚点", + "share_type_prompt": "你想分享的位置的类型?", "toggle_attribution": "切换属性" }, "member_list": { - "filter_placeholder": "过滤房间成员" + "count": { + "one": "%(count)s 位成员", + "other": "%(count)s 个成员" + }, + "filter_placeholder": "搜索房间内成员", + "invite_button_no_perms_tooltip": "你无权邀请用户", + "invited_label": "已邀请", + "list_title": "成员列表", + "no_matches": "不匹配" }, "member_list_back_action_label": "房间成员", - "message_edit_dialog_title": "消息编辑历史", + "message_edit_dialog_title": "消息编辑", + "migrating_crypto": "请稍候。我们正在更新 %(brand)s,以使加密更快、更可靠。", "mobile_guide": { - "toast_accept": "使用 app", - "toast_description": "在移动网页浏览器中 %(brand)s 是实验性功能。为了获取更好的体验和最新功能,请使用我们的免费原生应用。", - "toast_title": "使用 app 以获得更好的体验" + "toast_accept": "使用 App", + "toast_description": "%(brand)s 在移动 Web 浏览器上处于实验阶段。为了获得更好的体验和最新功能,请使用我们的免费原生应用。", + "toast_title": "使用 App 以获得更好的体验" }, - "name_and_id": "%(name)s%(userId)s", + "name_and_id": "%(name)s(%(userId)s)", "no_more_results": "没有更多结果", "notif_panel": { - "empty_description": "你没有可见的通知。", - "empty_heading": "一切完毕" + "empty_description": "你没有可见通知。", + "empty_heading": "你已阅读所有消息" }, "notifications": { "all_messages": "全部消息", - "all_messages_description": "获得每条消息的通知", + "all_messages_description": "接收每个消息的通知", "class_global": "全局", - "class_other": "其他", + "class_other": "其它", "default": "默认", - "enable_prompt_toast_description": "开启桌面通知", + "email_pusher_app_display_name": "邮件通知", + "enable_prompt_toast_description": "启用桌面通知", "enable_prompt_toast_title": "通知", "enable_prompt_toast_title_from_message_send": "不要错过任何回复", - "error_change_title": "修改通知设置", + "error_change_title": "更改通知设置", "keyword": "关键词", "keyword_new": "新的关键词", - "mark_all_read": "标记所有为已读", - "mentions_and_keywords": "@提及和关键词", - "mentions_and_keywords_description": "如设置中设定的那样仅通知提及和关键词", - "mentions_keywords": "提及&关键词", - "message_didnt_send": "消息没有发送。点击查看信息。", + "level_activity": "活动", + "level_highlight": "高亮", + "level_muted": "已静默", + "level_none": "无", + "level_notification": "通知", + "level_unsent": "未发送", + "mark_all_read": "全部设为已读", + "mentions_and_keywords": "提及与关键词", + "mentions_and_keywords_description": "仅按设置中的配置获取提及与关键词", + "mentions_keywords": "提及与关键词", + "message_didnt_send": "消息未发送。点击以获取信息。", "mute_description": "你不会收到任何通知" }, "notifier": { "m.key.verification.request": "%(name)s 正在请求验证" }, "onboarding": { - "create_room": "创建一个群聊", - "explore_rooms": "探索公共房间", - "has_avatar_label": "很好,这样大家就知道是你了", - "intro_byline": "拥有您的对话。", - "intro_welcome": "欢迎来到 %(appName)s", - "no_avatar_label": "添加照片,让人们知道这是你。", + "create_room": "创建群聊", + "explore_rooms": "浏览公共房间", + "has_avatar_label": "很好,这将有助于人们确认是你", + "intro_byline": "掌控你的对话。", + "intro_welcome": "欢迎使用 %(appName)s", + "no_avatar_label": "添加照片让别人知道这是你。", "send_dm": "发送私聊", - "welcome_detail": "现在,让我们协助你开始", + "welcome_detail": "协助你开始", "welcome_user": "欢迎 %(name)s" }, + "pill": { + "permalink_other_room": "一个位于 %(room)s 的消息", + "permalink_this_room": "来自 %(user)s 的消息" + }, "poll": { "create_poll_action": "创建投票", "create_poll_title": "创建投票", - "disclosed_notes": "投票者一投完票就能看到结果", + "disclosed_notes": "结果将在参与者投票后立即可见", "edit_poll_title": "编辑投票", - "end_description": "您确定要结束此投票吗? 这将显示投票的最终结果并阻止人们投票。", - "end_message": "投票已经结束。 得票最多答案:%(topAnswer)s", - "end_message_no_votes": "投票已经结束。 没有投票。", + "end_description": "你确定要结束此投票?此操作将显示最终结果并阻止人员投票。", + "end_message": "投票已结束。热门答案:%(topAnswer)s", + "end_message_no_votes": "投票已结束。无人投票。", "end_title": "结束投票", - "error_ending_description": "抱歉,投票没有结束。 请再试一次。", + "ended_poll_label": "投票已结束", + "error_ending_description": "抱歉,投票尚未结束。请重试。", "error_ending_title": "结束投票失败", - "error_voting_description": "抱歉,你的投票未登记。请重试。", - "error_voting_title": "投票未登记", - "failed_send_poll_description": "抱歉,您尝试创建的投票未被发布。", + "error_voting_description": "抱歉,你的投票尚未注册。请重试。", + "error_voting_title": "投票未注册", + "failed_send_poll_description": "抱歉,你尝试创建的投票未能发布。", "failed_send_poll_title": "发布投票失败", - "notes": "结果仅在你结束投票后展示", + "notes": "结果仅在结束投票时显示", + "option_label": "选项 %(number)s,%(answer)s", + "option_label_winning_with_total": { + "one": "选项 %(number),%(answer)s,领先,%(count)s 票", + "other": "选项 %(number),%(answer)s,领先,%(count)s 票" + }, + "option_label_with_total": { + "one": "选项 %(number),%(answer)s,%(count)s 票", + "other": "选项 %(number),%(answer)s,%(count)s 票" + }, "options_add_button": "添加选项", "options_heading": "创建选项", "options_label": "选项 %(number)s", - "options_placeholder": "写个选项", - "topic_heading": "你的投票问题或主题是什么?", - "topic_label": "问题或主题", + "options_placeholder": "撰写选项", + "poll_label": "投票", + "topic_heading": "投票中的提问或主题?", + "topic_label": "提问或主题", + "topic_placeholder": "撰写内容…", + "total_decryption_errors": "由于解密错误可能无法统计某些投票", "total_n_votes": { - "one": "票数已达 %(count)s 票。要查看结果请亲自投票", - "other": "票数已达 %(count)s 票。要查看结果请亲自投票" + "one": "已有 %(count)s 个投票。投票以查看结果", + "other": "已有 %(count)s 个投票。投票以查看结果" }, "total_n_votes_voted": { - "one": "基于 %(count)s 票", - "other": "基于 %(count)s 票" + "one": "基于 %(count)s 个投票", + "other": "基于 %(count)s 个投票" }, - "total_no_votes": "尚无投票", - "total_not_ended": "结果将在投票结束时可见", + "total_no_votes": "暂无人投票", + "total_not_ended": "结果将于投票结束后可见。", "type_closed": "封闭式投票", "type_heading": "投票类型", "type_open": "开放式投票", - "unable_edit_description": "抱歉,你无法在有人投票后编辑投票。", + "unable_edit_description": "抱歉,已有投票的情况下你无法再编辑投票。", "unable_edit_title": "无法编辑投票" }, "power_level": { "admin": "管理员", + "creator": "所有者", "custom": "自定义(%(level)s)", - "custom_level": "自定义级别", + "custom_level": "自定义权力值", "default": "默认", - "label": "权力级别", + "label": "权力值", "moderator": "协管员", - "restricted": "受限" + "restricted": "已受限" }, - "powered_by_matrix": "由 Matrix 驱动", - "powered_by_matrix_with_logo": "去中心化、加密的聊天与协作,由 $matrixLogo 驱动", + "powered_by_matrix": "由 Matrix 提供底层支持", + "powered_by_matrix_with_logo": "由 $matrixLogo 驱动的去中心化、加密聊天与协作", "presence": { "away": "离开", - "busy": "忙", + "busy": "忙碌", "idle": "空闲", - "idle_for": "已闲置 %(duration)s", + "idle_for": "已空闲 %(duration)s", "offline": "离线", - "offline_for": "已离线 %(duration)s", + "offline_for": "离线 %(duration)s", "online": "在线", - "online_for": "已上线 %(duration)s", - "unknown": "未知的", - "unknown_for": "未知状态已持续 %(duration)s" + "online_for": "已在线 %(duration)s", + "unknown": "未知", + "unknown_for": "未知 %(duration)s", + "unreachable": "无法访问用户的服务器" }, "quick_settings": { "all_settings": "所有设置", - "metaspace_section": "固定到侧边栏", + "metaspace_section": "钉在边栏", "sidebar_settings": "更多选项", "title": "快速设置" }, "quit_warning": { - "call_in_progress": "你似乎正在通话,确定要退出吗?", - "file_upload_in_progress": "你似乎正在上传文件,确定要退出吗?" + "call_in_progress": "你似乎正在通话,你确定要退出?", + "file_upload_in_progress": "你似乎正在上传文件,你确定要退出?" }, "redact": { "confirm_button": "确认移除", - "error": "你无法删除这条消息。(%(code)s)", + "confirm_description": "你确定要移除(删除)此事件?", + "confirm_description_state": "请注意,如此移除房间更改可能会使更改失效。", + "error": "你无法删除此消息。(%(code)s)", "ongoing": "正在移除…", "reason_label": "理由(可选)" }, "report_content": { - "description": "举报此消息会将其唯一的“事件ID”发送给你的家服务器的管理员。如果此房间中的消息是加密的,则你的家服务器管理员将无法阅读消息文本,也无法查看任何文件或图片。", + "description": "举报此消息会将其唯一的“事件 ID”发送给服务器管理员。如果此房间中的消息已加密,则服务器管理员将无法查看消息文本、任何文件或图像。", "disagree": "不同意", - "hide_messages_from_user": "若想隐藏来自此用户的全部当前和未来的消息,请打勾。", + "error_create_room_moderation_bot": "无法使用协管机器人创建房间", + "hide_messages_from_user": "如果你想隐藏此用户当前及未来的所有消息,请选中此项。", "ignore_user": "忽略用户", - "illegal_content": "违法内容", - "missing_reason": "请填写你为何做此报告。", - "nature": "请选择性质并描述为什么此消息是滥用。", - "nature_disagreement": "此用户所写的是错误内容。\n这将会报告给房间协管员。", - "nature_illegal": "此用户正在做出违法行为,如对他人施暴,或威胁使用暴力。\n这将报告给房间协管员,他们可能会将其报告给执法部门。", - "nature_nonstandard_admin": "该房间专用于非法或不良内容,或者版主未能对非法或不良内容进行管理。\n这将报告给%(homeserver)s 的管理员。", - "nature_nonstandard_admin_encrypted": "该房间专用于非法或不良内容,或者版主未能对非法或不良内容进行管理。\n这将报告给 %(homeserver)s 的管理员。管理员无法读取此房间的加密内容。", - "nature_other": "任何其他原因。请描述问题。\n这将报告给房间协管员。", - "nature_spam": "此用户正在房间中滥发广告、广告链接或宣传。\n这将报告给房间协管员。", - "other_label": "其他", - "report_content_to_homeserver": "向你的家服务器管理员举报内容", - "report_entire_room": "报告整个房间", - "spam_or_propaganda": "垃圾信息或宣传", - "toxic_behaviour": "不良行为" + "illegal_content": "非法内容", + "missing_reason": "请填写举报理由。", + "nature": "请选择一种性质,并描述该消息为何具有滥用性。", + "nature_disagreement": "此用户输入的内容有误。\n此问题将报告给房间协管员。", + "nature_illegal": "此用户存在违法行为,例如人肉搜索或威胁使用暴力。\n我们会将此情况报告给房间管理员,管理员可能会将此问题上报给法律部门。", + "nature_nonstandard_admin": "此房间专门用于非法或有害内容,或者管理员未能审核非法或有害内容。\n此问题将报告给 %(homeserver)s 的管理员。", + "nature_nonstandard_admin_encrypted": "此房间专门发布非法或有害内容,或者管理员未能审核非法或有害内容。\n此问题将报告给 %(homeserver)s 的管理员。管理员将无法阅读此房间的加密内容。", + "nature_other": "任何其它原因。请描述问题。\n这将报告给房间管理员。", + "nature_spam": "此用户正在向房间发送垃圾广告、广告链接或推广内容。\n我们将向房间管理员报告此问题。", + "nature_toxic": "此用户表现不良,例如辱骂其他用户、在家庭友好型房间分享仅限成人的内容或以其它方式违反此房间的规则。\n将向房间管理员举报。", + "other_label": "其它", + "report_content_to_homeserver": "向服务器管理员举报此内容", + "report_entire_room": "举报整个房间", + "spam_or_propaganda": "垃圾内容或广告", + "toxic_behaviour": "有害行为" + }, + "report_room": { + "description": "向服务器管理员举报此房间。如果消息已被加密,则管理员无法看到它们。", + "reason_label": "描述理由" }, "restore_key_backup_dialog": { "count_of_decryption_failures": "%(failedCount)s 个会话解密失败!", - "count_of_successfully_restored_keys": "成功恢复了 %(sessionCount)s 个密钥", - "enter_key_description": "通过输入你的安全密钥来访问你的安全消息历史记录并设置安全通信。", - "enter_key_title": "输入安全密钥", - "enter_phrase_description": "无法通过你的安全短语访问你的安全消息历史记录并设置安全通信。", - "enter_phrase_title": "输入安全短语", - "incorrect_security_phrase_dialog": "无法使用此安全短语解密备份:请确认你是否输入了正确的安全短语。", - "incorrect_security_phrase_title": "安全短语错误", - "key_backup_warning": "警告:你应此只在受信任的电脑上设置密钥备份。", - "key_forgotten_text": "如果你忘记了你的安全密钥,你可以", - "key_is_invalid": "安全密钥无效", - "key_is_valid": "看起来是有效的安全密钥!", - "keys_restored_title": "已恢复密钥", - "load_error_content": "无法获取备份状态", - "load_keys_progress": "%(total)s 个密钥中之 %(completed)s 个已恢复", - "no_backup_error": "找不到备份!", - "phrase_forgotten_text": "如果你忘记了你的安全短语,你可以使用你的安全密钥设置新的恢复选项", - "recovery_key_mismatch_description": "无法使用此安全密钥解密备份:请检查你输入的安全密钥是否正确。", - "recovery_key_mismatch_title": "安全密钥不符", - "restore_failed_error": "无法还原备份" + "count_of_successfully_restored_keys": "成功恢复 %(sessionCount)s 个密钥", + "enter_key_description": "访问你的安全消息历史记录并通过输入恢复密钥来设置安全消息传递。", + "enter_key_title": "输入恢复密钥", + "enter_phrase_description": "访问你的安全消息历史记录,并通过输入你的安全口令来设置安全消息传递。", + "enter_phrase_title": "输入安全口令", + "incorrect_security_phrase_dialog": "无法使用此安全口令解密备份:请确认你输入的安全口令是否正确。", + "incorrect_security_phrase_title": "安全口令不正确", + "key_backup_warning": "警告:你应仅从受信任的计算机设置密钥备份。", + "key_fetch_in_progress": "从服务器获取密钥", + "key_forgotten_text": "如果你忘记了恢复密钥,你可以。", + "key_is_invalid": "非有效的恢复密钥", + "key_is_valid": "似乎是有效的恢复密钥!", + "keys_restored_title": "密钥已恢复", + "load_error_content": "无法载入备份状态", + "load_keys_progress": "已恢复 %(total)s 个密钥中的 %(completed)s 个", + "no_backup_error": "未找到备份!", + "phrase_forgotten_text": "如果你忘记了安全口令,你可以使用恢复密钥设置新的恢复选项。", + "recovery_key_mismatch_description": "无法使用此恢复密钥解密备份:请确认输入了正确的恢复密钥。", + "recovery_key_mismatch_title": "恢复密钥不正确", + "restore_failed_error": "无法恢复备份" }, "right_panel": { - "add_integrations": "添加挂件、桥接和机器人", + "add_integrations": "添加扩展", + "add_topic": "添加主题", + "extensions_button": "扩展", + "extensions_empty_description": "选择“%(addIntegrations)s”以浏览并添加扩展到此房间。", + "extensions_empty_title": "使用更多工具、小部件与机器人提高生产力", "files_button": "文件", "pinned_messages": { + "empty_description": "选择一个消息并点击“%(pinAction)s”以包含在此处。", + "empty_title": "置顶重要的消息以便于发现", + "header": { + "one": "1 个已置顶的消息", + "other": "%(count)s 个已置顶的消息", + "zero": "已置顶的消息" + }, "limits": { - "other": "你仅能固定 %(count)s 个挂件" - } + "other": "你最多只能钉住 %(count)s 个小部件" + }, + "menu": "打开菜单", + "reply_thread": "回复 消息列中的消息", + "unpin_all": { + "button": "取消置顶所有消息", + "content": "确实要移除所有已置顶的消息?此操作无法撤消。", + "title": "取消置顶所有消息?" + }, + "view": "在时间线中查看" }, - "pinned_messages_button": "已固定", + "pinned_messages_button": "已置顶的消息", "poll": { + "active_heading": "进行中的投票", + "empty_active": "此房间暂无进行中的投票", + "empty_active_load_more": "暂无进行中的投票。加载更多投票以查看过去几个月的投票。", + "empty_active_load_more_n_days": { + "one": "过去 1 天暂无进行中的投票。载入更多投票以获取过去几个月的投票", + "other": "过去 %(count)s 天暂无进行中的投票。载入更多投票以获取过去几个月的投票" + }, + "empty_past": "此房间暂无过往投票", + "empty_past_load_more": "暂无过往投票。载入更多投票以获取过去几个月的投票", + "empty_past_load_more_n_days": { + "one": "过去 1 天没有过往投票。加载更多投票以查看过去几个月的投票。", + "other": "过去 %(count)s 天暂无过往的投票。载入更多投票以获取过去几个月的投票" + }, "final_result": { - "one": "基于 %(count)s 票数的最终结果", - "other": "基于 %(count)s 票数的最终结果" - } + "one": "基于 %(count)s 个投票的最终结果", + "other": "基于 %(count)s 个投票的最终结果" + }, + "load_more": "载入更多投票", + "loading": "正在载入投票", + "past_heading": "过往的投票", + "view_in_timeline": "在时间线中查看投票", + "view_poll": "查看投票" }, - "polls_button": "投票历史", + "polls_button": "投票", "room_summary_card": { "title": "房间信息" }, @@ -1502,754 +1953,1100 @@ } }, "room": { - "3pid_invite_email_not_found_account": "该邀请被发送到了与你的账户无关的 %(email)s", - "3pid_invite_email_not_found_account_room": "这个到 %(roomName)s 的邀请是发送给 %(email)s 的,而此邮箱没有关联你的账户", - "3pid_invite_error_description": "尝试验证你的邀请时返回错误(%(errcode)s)。你可以尝试把这个信息传给邀请你的人。", - "3pid_invite_error_invite_action": "仍然尝试加入", - "3pid_invite_error_invite_subtitle": "你只能通过有效邀请加入。", - "3pid_invite_error_public_subtitle": "你依旧可以加入这里。", - "3pid_invite_error_title": "你的邀请出了问题。", - "3pid_invite_error_title_room": "你到 %(roomName)s 的邀请出错", - "3pid_invite_no_is_subtitle": "要直接在 %(brand)s 中接收邀请,请在设置中使用一个身份服务器。", - "banned_by": "你被 %(memberName)s 封禁", - "banned_from_room_by": "你被 %(memberName)s 从 %(roomName)s 封禁了", + "3pid_invite_email_not_found_account": "此邀请已发送到 %(email)s,但该邮箱地址与你的账户无关。", + "3pid_invite_email_not_found_account_room": "%(roomName)s 的邀请已发送到 %(email)s,该邮箱与你的账户无关。", + "3pid_invite_error_description": "尝试验证邀请时出错(%(errcode)s)。你可以尝试将此信息转发给邀请你的人员。", + "3pid_invite_error_invite_action": "尝试强制加入", + "3pid_invite_error_invite_subtitle": "你只能使用有效的邀请加入。", + "3pid_invite_error_public_subtitle": "你仍然可以加入。", + "3pid_invite_error_title": "你的邀请出现问题。", + "3pid_invite_error_title_room": "你对 %(roomName)s 的邀请出现问题", + "3pid_invite_no_is_subtitle": "使用“设置”中的身份服务器,可直接在 %(brand)s 中接收邀请。", + "banned_by": "你已被 %(memberName)s 封禁", + "banned_from_room_by": "你已被 %(memberName)s 禁止进入 %(roomName)s", "context_menu": { "copy_link": "复制房间链接", "favourite": "收藏", "forget": "忘记房间", "low_priority": "低优先级", + "mark_read": "设为已读", + "mark_unread": "设为未读", + "notifications_default": "跟随系统设置", + "notifications_mute": "静默房间", "title": "房间选项", "unfavourite": "已收藏" }, - "creating_room_text": "正在创建房间%(names)s", + "creating_room_text": "正在以 %(names)s 为名称创建房间", "dm_invite_action": "开始聊天", - "dm_invite_subtitle": " 想聊天", - "dm_invite_title": "你想和 %(user)s 聊天吗?", - "drop_file_prompt": "把文件拖到这里以上传", - "edit_topic": "编辑话题", - "error_join_404_invite": "邀请你的人已经离开了,亦或是他们的家服务器离线了。", - "error_join_404_invite_same_hs": "邀请你的人已经离开了。", - "error_join_connection": "加入时发生错误。", - "error_join_incompatible_version_1": "抱歉,你的家服务器过旧,故无法参与其中。", - "error_join_incompatible_version_2": "请 联系你的家服务器管理员。", + "dm_invite_subtitle": " 想要聊天", + "dm_invite_title": "你想与 %(user)s 聊天吗?", + "drop_file_prompt": "拖放文件到此处以上传", + "edit_topic": "编辑主题", + "error_cancel_knock_title": "取消失败", + "error_join_403": "你需要被邀请才能访问此房间。", + "error_join_404_1": "你尝试使用房间 ID 加入,但未提供要加入的服务器列表。房间 ID 是内部标识符,如果没有其它信息,则无法用于加入房间。", + "error_join_404_2": "如果你知道房间地址,请尝试通过该地址加入。", + "error_join_404_invite": "邀请你的人已离开,或其服务器已离线。", + "error_join_404_invite_same_hs": "邀请你的人已离开。", + "error_join_connection": "加入时出错。", + "error_join_incompatible_version_1": "抱歉,你的主服务器太旧,无法在此处参与。", + "error_join_incompatible_version_2": "请联系主服务器管理员。", "error_join_title": "加入失败", - "error_jump_to_date_connection": "在尝试查找并跳转到给定日期时发生网络错误。你的服务器可能出现了故障,或者你的网络暂时出现了问题。请再试一次。如果依然发生这种情况,请联系您的服务器管理员", + "error_join_unknown": "发生未知错误。", + "error_jump_to_date": "服务器返回 %(statusCode)s,错误代码为 %(errorCode)s", + "error_jump_to_date_connection": "尝试查找并跳转到指定日期时出现网络错误。主服务器可能已关闭,或互联网连接只是暂时出现问题。请重试。如果此问题持续存在,请联系主服务器管理员。", + "error_jump_to_date_details": "错误详细信息", + "error_jump_to_date_not_found": "我们找不到从 %(dateString)s 开始的后续活动。请尝试选择更早的日期。", + "error_jump_to_date_send_logs_prompt": "请提交调试日志以帮助我们追踪问题。", + "error_jump_to_date_title": "无法找到该日期的事件", "face_pile_summary": { - "one": "已有你所认识的 %(count)s 个人加入", - "other": "已有你所认识的 %(count)s 个人加入" + "one": "你认识的 %(count)s 个人已加入", + "other": "%(count)s 个你认识的人已加入" }, "face_pile_tooltip_label": { - "one": "查看 1 位成员", - "other": "查看全部 %(count)s 位成员" + "one": "查看 1 个成员", + "other": "查看全部 %(count)s 个成员" }, "face_pile_tooltip_shortcut": "包括 %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "包括你,%(commaSeparatedMembers)s", - "failed_reject_invite": "拒绝邀请失败", + "failed_determine_user": "由于成员事件已更改,无法确定要忽略哪个用户。", + "failed_reject_invite": "邀请拒绝失败", "forget_room": "忘记此房间", "forget_space": "忘记此空间", "header": { - "room_is_public": "此房间为公共的" + "join_video_call": "加入视频通话", + "join_voice_call": "加入语音通话", + "n_people_asking_to_join": { + "one": "申请加入", + "other": "%(count)s 个人员申请加入" + }, + "room_is_public": "此为公共房间", + "shared_history_tooltip": "新成员可以看到历史", + "world_readable_history_tooltip": "任何人都可以看到历史" }, - "inaccessible": "这个房间或空间当前不可访问。", - "inaccessible_name": "%(roomName)s 此时无法访问。", - "inaccessible_subtitle_1": "等一会儿再试或联系管理员检查你是否拥有访问权限。", - "inaccessible_subtitle_2": "尝试访问房间或空间时返回%(errcode)s。若你认为你看到这条消息是有问题的,请提交bug报告。", + "header_avatar_open_settings_label": "打开房间设置", + "header_face_pile_tooltip": "人员", + "header_untrusted_label": "不受信任", + "inaccessible": "此房间或空间暂时无法访问。", + "inaccessible_name": "目前无法访问 %(roomName)s。", + "inaccessible_subtitle_1": "请稍后重试,或联系房间或空间管理员检查你是否有权访问。", + "inaccessible_subtitle_2": "尝试访问房间或空间时返回了 %(errcode)s。如果认为此消息有误,请提交 Bug 报告。", "intro": { - "dm_caption": "除非你们其中一个邀请了别人加入,否则将仅有你们两个人在此对话中。", + "display_topic": "主题:", + "dm_caption": "除非两者之一邀请他人加入,否则此处只有你们两个人的对话。", + "edit_topic": "主题:编辑)", "enable_encryption_prompt": "在设置中启用加密。", - "no_avatar_label": "添加图片,让人们一眼就能看到你的房间。", - "no_topic": "添加话题,让大家知道这里是讨论什么的。", - "private_unencrypted_warning": "你的私人消息通常是加密的,但此房间不是。这通常是因为使用了不受支持的设备或方法,例如电子邮件邀请。", - "room_invite": "仅邀请至此房间", - "send_message_start_dm": "发送你的第一条消息邀请来聊天", - "start_of_dm_history": "这是你与的私聊历史的开端。", - "start_of_room": "这里是 的开始。", - "unencrypted_warning": "未启用端到端加密", + "encrypted_3pid_dm_pending_join": "一旦所有人都加入,你就可以开始聊天", + "no_avatar_label": "添加一张照片,这样别人就能很容易地找到你的房间。", + "no_topic": "添加主题有助于人们了解此房间与哪些事物相关。", + "private_unencrypted_warning": "你的私有消息通常是加密的,但此房间不是。通常这是由于使用了不受支持的设备或方法,例如通过邮件地址邀请。", + "room_invite": "仅邀请到此房间", + "send_message_start_dm": "发送第一个消息以邀请 聊天", + "start_of_dm_history": "此处是你与 的私聊历史的开头。", + "start_of_room": "这是 的开始。", + "unencrypted_warning": "端到端加密未启用", "user_created": "%(displayName)s 创建了此房间。", "you_created": "你创建了此房间。" }, - "invite_email_mismatch_suggestion": "要在 %(brand)s 中直接接收邀请,请在设置中共享此邮箱。", - "invite_sent_to_email": "邀请已被发送到 %(email)s", - "invite_sent_to_email_room": "这个到 %(roomName)s 的邀请是发送给 %(email)s 的", - "invite_subtitle": " 邀请了你", + "invite_email_mismatch_suggestion": "在“设置 ”中分享此邮件,以便直接在 %(brand)s 中接收邀请。", + "invite_sent_to_email": "此邀请已发送至 %(email)s", + "invite_sent_to_email_room": "到 %(roomName)s 的邀请已发送到 %(email)s", + "invite_subtitle": "由 邀请", "invite_this_room": "邀请到此房间", "invite_title": "你想加入 %(roomName)s 吗?", - "inviter_unknown": "未知的", + "inviter_unknown": "未知", "invites_you_text": " 邀请了你", "join_button_account": "注册", - "join_failed_needs_invite": "你需要一个邀请来查看 %(roomName)s", + "join_failed_needs_invite": "要查看 %(roomName)s,你需要被邀请", "join_the_discussion": "加入讨论", "join_title": "加入房间以参与", - "join_title_account": "使用一个账户加入对话", - "joining": "加入中…", - "jump_read_marker": "跳到第一条未读消息。", + "join_title_account": "通过账户加入对话", + "joining": "正在加入…", + "joining_room": "正在加入房间…", + "joining_space": "加入空间", + "jump_read_marker": "跳转到首个未读消息。", "jump_to_bottom_button": "滚动到最近的消息", "kick_reason": "原因:%(reason)s", - "kicked_by": "%(memberName)s 将你移出了这里", - "kicked_from_room_by": "%(memberName)s 将你移出了 %(roomName)s", + "kicked_by": "你已被 %(memberName)s 移除", + "kicked_from_room_by": "你已被 %(memberName)s 从 %(roomName)s 移除", + "knock_cancel_action": "取消请求", + "knock_denied_subtitle": "由于你被拒绝访问,除非收到群组管理员或协管员的邀请,否则你无法重新加入。", + "knock_denied_title": "你已被拒绝访问", + "knock_message_field_placeholder": "消息(可选)", + "knock_prompt": "申请加入?", + "knock_prompt_name": "申请加入 %(roomName)s?", + "knock_send_action": "请求访问", + "knock_sent": "已发送加入申请", + "knock_sent_subtitle": "你的加入请求正在等待处理。", + "knock_subtitle": "你需要获得此房间的访问权限才能查看或参与对话。你可以在下方发送加入申请。", "leave_error_title": "离开房间时出错", - "leave_server_notices_description": "此房间是用于发布来自家服务器的重要讯息的,所以你不能退出它。", - "leave_server_notices_title": "无法退出服务器公告房间", - "leave_unexpected_error": "试图离开房间时发生意外服务器错误", - "link_email_to_receive_3pid_invite": "要在 %(brand)s 中直接接收邀请,请在设置中将你的账户连接到此邮箱。", - "loading_preview": "加载预览中", - "no_peek_join_prompt": "%(roomName)s 不能被预览。你想加入吗?", - "no_peek_no_name_join_prompt": "这里没有预览, 你是否要加入?", - "not_found_subtitle": "你确定你位于正确的地方?", - "not_found_title": "这个房间或空间不存在。", - "not_found_title_name": "%(roomName)s 不存在。", - "peek_join_prompt": "你正在预览 %(roomName)s。想加入吗?", - "read_topic": "点击阅读话题", - "rejoin_button": "重新加入", - "unread_notifications_predecessor": { - "other": "你在此房间的先前版本中有 %(count)s 条未读通知。", - "one": "你在此房间的先前版本中有 %(count)s 条未读通知。" + "leave_server_notices_description": "此房间用于存储来自主服务器的重要消息,因此你无法离开。", + "leave_server_notices_title": "无法离开“服务器通知”房间", + "leave_unexpected_error": "尝试离开房间时出现意外服务器错误", + "link_email_to_receive_3pid_invite": "请在“设置”中将此邮件地址与你的账户关联,以便直接在 %(brand)s 中接收邀请。", + "loading_preview": "正在载入预览", + "no_peek_join_prompt": "%(roomName)s 无法预览。是否加入?", + "no_peek_no_name_join_prompt": "暂无预览,是否加入?", + "not_found_subtitle": "你确定来对地方了吗?", + "not_found_title": "此房间或空间不存在。", + "not_found_title_name": "%(roomName)s 不存在。 ", + "peek_join_prompt": "你正在预览 %(roomName)s。是否加入?", + "pinned_message_banner": { + "button_close_list": "关闭列表", + "button_view_all": "查看全部", + "description": "此房间包含已置顶的消息。点击查看。", + "go_to_newest_message": "在此处查看时间线中的置顶消息和最新的置顶消息", + "go_to_next_message": "在此处查看时间线中的置顶消息和下一条最早的置顶消息", + "title": "第 %(index)s 个已置顶的消息,共 %(length)s 个" }, - "upgrade_error_description": "请再次检查你的服务器是否支持所选房间版本,然后再试一次。", - "upgrade_error_title": "升级房间时发生错误", - "upgrade_warning_bar": "升级此房间将会关闭房间的当前实例并创建一个具有相同名称的升级版房间。", - "upgrade_warning_bar_admins": "此警告仅房间管理员可见", - "upgrade_warning_bar_unstable": "此房间运行的房间版本是 ,此版本已被家服务器标记为 不稳定 。", - "upgrade_warning_bar_upgraded": "此房间已经被升级。", + "read_topic": "点击阅读主题", + "rejecting": "正在拒绝邀请…", + "rejoin_button": "重新加入", + "room_content": "房间内容", + "room_is_low_priority": "此为低优先级房间", + "search": { + "all_rooms_button": "搜索所有房间", + "placeholder": "搜索消息…", + "summary": { + "one": "为“”找到 1 个结果", + "other": "根据关键词“”找到 %(count)s 个结果" + }, + "this_room_button": "搜索此房间" + }, + "unknown_status_code_for_timeline_jump": "未知状态码", + "unread_notifications_predecessor": { + "one": "你的 %(count)s 个未读通知在此房间的上一版本中。", + "other": "你在此房间的上一版本中有 %(count)s 个未读通知。" + }, + "upgrade_error_description": "请仔细检查你的服务器是否支持所选的房间版本,然后重试。", + "upgrade_error_title": "升级房间时出错", + "upgrade_warning_bar": "升级此房间将关闭当前的房间实例,并创建一个同名的已升级房间。", + "upgrade_warning_bar_admins": "仅房间管理员可以看到此警告", + "upgrade_warning_bar_unstable": "此房间正在运行房间版本 ,此主服务器已将其标记为不稳定。", + "upgrade_warning_bar_upgraded": "此房间已升级过。", "upload": { "uploading_multiple_file": { - "other": "正在上传 %(filename)s 与其他 %(count)s 个文件", - "one": "正在上传 %(filename)s 与其他 %(count)s 个文件" + "one": "正在上传 %(filename)s 与剩余 %(count)s 个文件", + "other": "正在上传 %(filename)s 与剩余 %(count)s 个文件" }, "uploading_single_file": "正在上传 %(filename)s" - } + }, + "video_room": "此房间为视频房间", + "waiting_for_join_subtitle": "受邀用户加入 %(brand)s 后,你将能够聊天,并且房间将端到端加密。", + "waiting_for_join_title": "等待用户加入 %(brand)s" }, "room_list": { "add_room_label": "添加房间", "add_space_label": "添加空间", - "breadcrumbs_empty": "没有最近访问过的房间", + "breadcrumbs_empty": "暂无最近访问的房间", "breadcrumbs_label": "最近访问的房间", - "failed_add_tag": "无法为房间新增标签 %(tagName)s", - "failed_remove_tag": "移除房间标签 %(tagName)s 失败", - "failed_set_dm_tag": "设置私聊标签失败", + "failed_add_tag": "为房间添加标签 %(tagName)s 失败", + "failed_remove_tag": "从房间移除标签 %(tagName)s 失败", + "failed_set_dm_tag": "私聊标签设置失败", "home_menu_label": "主页选项", "join_public_room_label": "加入公共房间", "joining_rooms_status": { - "one": "目前正在加入 %(count)s 个房间", - "other": "目前正在加入 %(count)s 个房间" + "one": "当前正在加入 %(count)s 个房间", + "other": "当前加入了 %(count)s 个房间" + }, + "list_title": "房间列表", + "more_options": { + "leave_room": "离开房间" }, "notification_options": "通知选项", "redacting_messages_status": { - "one": "目前正在移除%(count)s个房间中的消息", - "other": "目前正在移除%(count)s个房间中的消息" + "one": "正在移除房间中的 %(count)s 个消息", + "other": "正在移除房间中的 %(count)s 个消息" + }, + "section": { + "chats": "聊天", + "favourites": "收藏", + "low_priority": "低优先级" }, "show_less": "显示更少", "show_n_more": { - "other": "多显示 %(count)s 个", - "one": "多显示 %(count)s 个" + "one": "显示剩余 %(count)s 个", + "other": "显示剩余 %(count)s 个" }, "show_previews": "显示消息预览", "sort_by": "排序", "sort_by_activity": "活动", "sort_by_alphabet": "字典顺序", - "sort_unread_first": "优先显示有未读消息的房间", - "space_menu_label": "%(spaceName)s菜单", + "sort_unread_first": "优先显示包含未读消息的房间", + "space_menu_label": "%(spaceName)s 菜单", "sublist_options": "列表选项", "suggested_rooms_heading": "建议的房间" }, "room_settings": { "access": { - "description_space": "决定谁可以查看和加入 %(spaceName)s。", + "description_space": "决定谁可以查看并加入 %(spaceName)s。", "title": "访问" }, "advanced": { - "error_upgrade_description": "房间可能没有完整地升级", + "error_upgrade_description": "无法完成升级此房间", "error_upgrade_title": "房间升级失败", "information_section_room": "房间信息", "information_section_space": "空间信息", - "room_id": "内部房间ID", - "room_predecessor": "查看%(roomName)s里更旧的消息。", - "room_upgrade_button": "升级此房间至推荐版本", + "room_id": "内部房间 ID", + "room_predecessor": "在 %(roomName)s 中查看更早的消息", + "room_upgrade_button": "将此房间升级到推荐的房间版本", + "room_upgrade_warning": "警告:升级房间不会自动将房间成员迁移到新版本房间。我们会在旧版本房间中发布新房间的链接 - 房间成员必须点击此链接才能加入新房间。", "room_version": "房间版本:", "room_version_section": "房间版本", - "space_predecessor": "查看%(spaceName)s的旧版本。", + "space_predecessor": "在 %(spaceName)s 中查看更旧的版本。", "space_upgrade_button": "将此空间升级到推荐的房间版本", - "unfederated": "此房间无法被远程 Matrix 服务器访问", - "upgrade_button": "升级此房间至版本 %(version)s", - "upgrade_dialog_description": "升级此房间需要关闭此房间的当前实例并创建一个新的房间代替它。为了给房间成员最好的体验,我们会:", - "upgrade_dialog_description_1": "创建一个拥有相同的名称、描述与头像的新房间", - "upgrade_dialog_description_2": "更新所有本地房间别名以使其指向新房间", - "upgrade_dialog_description_3": "阻止用户在旧房间中发言,并发送消息建议用户迁移至新房间", - "upgrade_dialog_description_4": "在新房间的开始处发送一条指回旧房间的链接,这样用户可以查看旧消息", - "upgrade_dialog_title": "更新房间版本", - "upgrade_dwarning_ialog_title_public": "更新公共房间", - "upgrade_warning_dialog_description": "更新房间是高级操作,通常建议在房间由于错误、缺失功能或安全漏洞而不稳定时使用。", - "upgrade_warning_dialog_explainer": "请注意升级将使这个房间有一个新版本。所有当前的消息都将保留在此存档房间中。", - "upgrade_warning_dialog_footer": "你将把此房间从 升级至 。", - "upgrade_warning_dialog_invite_label": "自动邀请该房间的成员加入新房间", - "upgrade_warning_dialog_report_bug_prompt": "这通常仅影响服务器如何处理房间。如果你的 %(brand)s 遇到问题,请回报错误。", - "upgrade_warning_dialog_report_bug_prompt_link": "通常这只影响房间在服务器上的处理方式。如果你对你的 %(brand)s 有问题,请报告一个错误。", - "upgrade_warning_dialog_title_private": "更新私人房间" + "unfederated": "远程 Matrix 服务器无法访问此房间", + "upgrade_button": "升级此房间到版本 %(version)s", + "upgrade_dialog_description": "升级此房间需要关闭当前的房间实例并创建一个新的房间来代替它。为了给房间成员提供最佳体验,我们将:", + "upgrade_dialog_description_1": "创建一个具有相同名称、描述与头像的新房间", + "upgrade_dialog_description_2": "更新任意本地房间别名以使其指向新房间", + "upgrade_dialog_description_3": "阻止用户在旧版本房间发言,并发送消息建议用户迁移到新房间。", + "upgrade_dialog_description_4": "在新房间的开头添加返回旧房间的链接,以便其他人可以查看旧消息。", + "upgrade_dialog_title": "升级房间版本", + "upgrade_dwarning_ialog_title_public": "升级公共房间", + "upgrade_warning_dialog_description": "升级房间是一项高级操作,通常建议在房间由于错误、功能缺失或安全漏洞而不稳定时执行此操作。", + "upgrade_warning_dialog_explainer": "请注意,升级将生成新版本的房间。所有当前消息都将保留在此已存档的房间中。", + "upgrade_warning_dialog_footer": "你将升级此房间的版本从 。", + "upgrade_warning_dialog_invite_label": "自动将此房间的成员邀请到新房间", + "upgrade_warning_dialog_report_bug_prompt": "这通常只会影响房间在服务器上的处理方式。如果你的 %(brand)s 出现问题,请报告 Bug。", + "upgrade_warning_dialog_report_bug_prompt_link": "这通常只会影响服务器上对房间的处理方式。如果你在使用 %(brand)s 时遇到问题,请报告 Bug。", + "upgrade_warning_dialog_title": "升级房间", + "upgrade_warning_dialog_title_private": "升级私有房间" }, "alias_not_specified": "未指定", "bridges": { - "description": "此房间正桥接消息到以下平台。了解更多。", - "empty": "这个房间不会将消息桥接到任何平台。了解更多", - "title": "桥接" + "description": "此房间正在将消息桥接到以下平台。了解详情", + "empty": "此房间不会将消息桥接到任何平台。了解更多", + "title": "桥接器" }, "delete_avatar_label": "删除头像", "general": { - "alias_field_has_domain_invalid": "缺少域分隔符,例子(:domain.org)", - "alias_field_has_localpart_invalid": "缺少房间名称或分隔符,例子(my-room:domain.org)", - "alias_field_matches_invalid": "此地址不指向此房间", - "alias_field_placeholder_default": "例如 my-room", + "alias_field_has_domain_invalid": "缺少域名分隔符。例如(:domain.org)", + "alias_field_has_localpart_invalid": "缺少房间名称或分隔符,例如(my-room:domain.org)", + "alias_field_matches_invalid": "此地址未指向此房间", + "alias_field_placeholder_default": "例如:my-room", "alias_field_required_invalid": "请提供地址", - "alias_field_safe_localpart_invalid": "不允许使用某些字符", - "alias_field_taken_invalid": "此地址的服务器无效或已被使用", + "alias_field_safe_localpart_invalid": "某些字符不被允许", + "alias_field_taken_invalid": "此地址指向的服务器无效或已被使用", "alias_field_taken_invalid_domain": "此地址已被使用", - "alias_field_taken_valid": "此地址可用", + "alias_field_taken_valid": "此地址可供使用", "alias_heading": "房间地址", - "aliases_items_label": "其他公布的地址:", - "aliases_no_items_label": "还没有其他公布的地址,在下方添加一个", + "aliases_items_label": "其它已发布地址:", + "aliases_no_items_label": "暂无其它已发布的地址,可以在下面添加。", "aliases_section": "房间地址", "avatar_field_label": "房间头像", "canonical_alias_field_label": "主要地址", - "default_url_previews_off": "已对此房间的参与者默认禁用URL预览。", - "default_url_previews_on": "已对此房间的参与者默认启用URL预览。", - "description_space": "编辑关于你的空间的设置。", - "error_creating_alias_description": "创建地址时出现错误。可能是服务器不允许,也可能是出现了一个暂时的错误。", - "error_creating_alias_title": "创建地址时出现错误", - "error_deleting_alias_description": "删除那个地址时出现错误。可能它已不存在,也可能出现了一个暂时的错误。", - "error_deleting_alias_description_forbidden": "你没有权限删除此地址。", - "error_deleting_alias_title": "删除地址时出现错误", - "error_save_space_settings": "空间设置保存失败。", - "error_updating_alias_description": "更新此房间的备用地址时出现错误。可能是服务器不允许,也可能是出现了一个暂时的错误。", - "error_updating_canonical_alias_description": "更新房间的主要地址时发生错误。可能是此服务器不允许,也可能是出现了一个临时错误。", - "error_updating_canonical_alias_title": "更新主要地址时发生错误", + "description_space": "编辑空间相关设置。", + "error_creating_alias_description": "创建该地址时出错。服务器可能不允许该地址,或者发生了临时故障。", + "error_creating_alias_title": "创建地址时出错", + "error_deleting_alias_description": "移除此地址时出错。它可能不再存在或临时出错。", + "error_deleting_alias_description_forbidden": "你无权删除地址。", + "error_deleting_alias_title": "移除地址时出错", + "error_publishing": "无法发布房间", + "error_publishing_detail": "发布此房间时出错", + "error_save_space_settings": "保存空间设置时出错", + "error_updating_alias_description": "更新房间的备选地址时出错。服务器可能不允许此操作,或者发生了临时故障。", + "error_updating_canonical_alias_description": "更新房间主要地址时出错。服务器可能不允许这样做,或者发生了临时故障。", + "error_updating_canonical_alias_title": "更新主要地址时出错", "leave_space": "离开空间", "local_alias_field_label": "本地地址", - "local_aliases_explainer_room": "为此房间设置地址以便用户通过你的家服务器(%(localDomain)s)找到此房间", - "local_aliases_explainer_space": "设置此空间的地址,这样用户就能通过你的家服务器找到此空间(%(localDomain)s)", + "local_aliases_explainer_room": "为此房间设置地址,以便用户通过主服务器找到此房间。", + "local_aliases_explainer_space": "设置此空间的地址以便用户可以通过主服务器(%(localDomain)s)找到此空间", "local_aliases_section": "本地地址", "name_field_label": "房间名称", - "new_alias_placeholder": "新的公布的地址(例如 #alias:server)", + "new_alias_placeholder": "新的发布地址(例如:#alias:server)", "no_aliases_room": "此房间没有本地地址", "no_aliases_space": "此空间没有本地地址", - "other_section": "其他", - "publish_toggle": "是否将此房间发布至 %(domain)s 的房间目录中?", - "published_aliases_description": "要公布地址,首先需要将其设为本地地址。", - "published_aliases_explainer_room": "任何服务器上的人均可通过公布的地址加入你的房间。", - "published_aliases_explainer_space": "任何服务器上的人均可通过公布的地址加入你的空间。", - "published_aliases_section": "公布的地址", - "save": "保存修改", - "topic_field_label": "房间话题", - "url_preview_encryption_warning": "在加密的房间中,比如此房间,URL预览默认是禁用的,以确保你的家服务器(生成预览的地方)无法收集与你在此房间中看到的链接有关的信息。", - "url_preview_explainer": "当有人在他们的消息里放置URL时,可显示URL预览以给出更多有关链接的信息,如其网站的标题、描述以及图片。", - "url_previews_section": "URL预览", - "user_url_previews_default_off": "你已经默认禁用URL预览。", - "user_url_previews_default_on": "你已经默认启用URL预览。" + "other_section": "其它", + "publish_toggle": "发布此房间到 %(domain)s 的房间目录?", + "publish_warn_invite_only": "你无法发布被设为仅限邀请的房间。", + "publish_warn_no_canonical_permission": "你必须拥有设置主要地址的权限才能发布房间。", + "published_aliases_description": "要发布地址,首先需要将其设置为本地地址。", + "published_aliases_explainer_room": "任何服务器上的任何人都可以使用已发布的地址加入此房间。", + "published_aliases_explainer_space": "任何服务器上的任何人都可以使用已发布的地址加入此空间。", + "published_aliases_section": "发布的地址", + "save": "保存更改", + "topic_field_label": "房间主题" }, "notifications": { "browse_button": "浏览", "custom_sound_prompt": "设置新的自定义声音", "notification_sound": "通知声音", - "settings_link": "如设置中设定的那样获取通知", + "settings_link": "按设置中的配置获取通知", "sounds_section": "声音", + "upload_sound_label": "上传自定义声音", "uploaded_sound": "已上传的声音" }, + "people": { + "knock_empty": "暂无请求", + "knock_section": "申请加入", + "see_less": "查看更少", + "see_more": "查看更多" + }, "permissions": { - "add_privileged_user_description": "授权给该房间内的某人或某些人", - "add_privileged_user_filter_placeholder": "搜索该房间内的用户……", + "add_privileged_user_description": "授予此房间中一个或多个用户更多特权", + "add_privileged_user_filter_placeholder": "在此房间中搜索用户…", "add_privileged_user_heading": "添加特权用户", "ban": "封禁用户", "ban_reason": "理由", "banned_by": "被 %(displayName)s 封禁", - "banned_users_section": "被封禁的用户", - "error_changing_pl_description": "更改此用户的权力级别时出错。请确保你有足够权限后重试。", - "error_changing_pl_reqs_description": "更改此房间的权力级别需求时出错。请确保你有足够的权限后重试。", - "error_changing_pl_reqs_title": "更改权力级别需求时出错", - "error_changing_pl_title": "更改权力级别时出错", - "error_unbanning": "解除封禁失败", + "banned_users_section": "已被封禁的用户", + "error_changing_pl_description": "更改用户权力值时出错。请确保你拥有足够的权限并重试。", + "error_changing_pl_reqs_description": "更改房间权力值要求时出错。请确保你拥有足够的权限并重试。", + "error_changing_pl_reqs_title": "更改权力值要求时出错", + "error_changing_pl_title": "更改权力值时出错", + "error_unbanning": "解封失败", "events_default": "发送消息", "invite": "邀请用户", "kick": "移除用户", - "m.call": "开始%(brand)s呼叫", - "m.call.member": "加入%(brand)s呼叫", + "m.call": "开始 %(brand)s 通话", + "m.call.member": "加入 %(brand)s 通话", "m.reaction": "发送反应", "m.room.avatar": "更改房间头像", "m.room.avatar_space": "更改空间头像", - "m.room.canonical_alias": "更改房间主要地址", - "m.room.canonical_alias_space": "更改空间主地址", + "m.room.canonical_alias": "更改房间的主要地址", + "m.room.canonical_alias_space": "更改空间的主要地址", "m.room.encryption": "启用房间加密", - "m.room.history_visibility": "更改历史记录可见性", + "m.room.history_visibility": "更改历史可见性", "m.room.name": "更改房间名称", "m.room.name_space": "更改空间名称", - "m.room.pinned_events": "管理置顶事件", + "m.room.pinned_events": "管理已置顶事件", "m.room.power_levels": "更改权限", "m.room.redaction": "移除我发送的消息", - "m.room.server_acl": "更改服务器访问控制列表", - "m.room.tombstone": "更新房间", - "m.room.topic": "更改话题", + "m.room.server_acl": "更改服务器 ACL", + "m.room.tombstone": "升级房间", + "m.room.topic": "更改主题", "m.room.topic_space": "更改描述", - "m.space.child": "管理此空间中的房间", - "m.widget": "修改挂件", - "muted_users_section": "被禁言的用户", - "no_privileged_users": "此房间中没有用户有特殊权限", - "notifications.room": "通知每个人", + "m.space.child": "在此空间中管理房间", + "m.widget": "修改小部件", + "muted_users_section": "已被静默的用户", + "no_privileged_users": "此房间中的用户均无特定权限", + "notifications.room": "通知所有人", "permissions_section": "权限", - "permissions_section_description_room": "选择更改房间各个部分所需的角色", - "permissions_section_description_space": "选择改变空间各个部分所需的角色", + "permissions_section_description_room": "选择更改房间各部分所需的角色", + "permissions_section_description_space": "选择更改空间各部分所需的角色", "privileged_users_section": "特权用户", - "redact": "移除其他人的消息", + "redact": "移除其他人发送的消息", "send_event_type": "发送 %(eventType)s 事件", "state_default": "更改设置", "title": "角色与权限", "users_default": "默认角色" }, "security": { - "enable_encryption_confirm_description": "房间加密一经启用,便无法禁用。在加密房间中,发送的消息无法被服务器看到,只能被房间的参与者看到。启用加密可能会使许多机器人和桥接无法正常运作。 详细了解加密。", + "cannot_change_to_private_due_to_missing_history_visiblity_permissions": { + "description": "您没有权限更改该频道的历史记录可见性。此操作存在风险,可能导致未加入频道的用户读取消息。", + "title": "无法将房间设为私有房间" + }, + "enable_encryption_confirm_description": "房间的加密一旦启用就无法禁用。服务器无法看到在加密房间中发送的消息,只有房间内的参与者才能看到。启用加密可能会阻止大多数机器人与桥接器正常工作。了解更多加密相关", "enable_encryption_confirm_title": "启用加密?", - "enable_encryption_public_room_confirm_description_1": "不建议为公共房间添加加密。任何人都能找到并加入公共房间,所以任何人都能阅读其中的消息。你不会获得加密的任何好处,并且之后你无法将其关闭。在公共房间中加密消息会使接收和发送消息变慢。", - "enable_encryption_public_room_confirm_description_2": "为避免这些问题,请为计划中的对话创建一个新的加密房间。", - "enable_encryption_public_room_confirm_title": "你确定要为此公开房间开启加密吗?", - "encrypted_room_public_confirm_description_1": "不建议公开加密房间。这意味着任何人都可以找到并加入房间,因此任何人都可以阅读消息。你将不会得到任何加密带来的好处。在公共房间加密消息还会拖慢收发消息的速度。", - "encrypted_room_public_confirm_description_2": "为避免这些问题,请为计划中的对话创建一个新的加密房间。", - "encrypted_room_public_confirm_title": "你确定要公开此加密房间吗?", - "encryption_permanent": "加密一经启用,便无法禁用。", - "error_join_rule_change_title": "未能更新加入列表", - "error_join_rule_change_unknown": "未知失败", - "guest_access_warning": "拥有受支持客户端的人无需注册账户即可加入房间。", - "history_visibility_invited": "只有成员(从他们被邀请开始)", - "history_visibility_legend": "谁可以阅读历史消息?", - "history_visibility_shared": "仅成员(从选中此选项时开始)", - "history_visibility_warning": "历史记录阅读权限的更改只会应用到此房间中将来的消息。既有历史记录的可见性将不会更改。", - "history_visibility_world_readable": "任何人", + "enable_encryption_public_room_confirm_description_1": "不建议为公共房间启用加密。任何人都可以找到并加入公共房间,因此任何人都可以阅读其中的消息。这种情况下你将无法受益于加密,并且以后也无法关闭加密功能。在公共房间中,加密消息会降低消息的收发效率。", + "enable_encryption_public_room_confirm_description_2": "为避免这些问题,请为你计划进行的对话创建一个新的加密房间。", + "enable_encryption_public_room_confirm_title": "你确定要为此公共房间添加加密功能?", + "encrypted_room_public_confirm_description_1": "不建将加密房间设为公开。这意味着任何人都可以找到并加入公共房间,因此任何人都可以阅读其中的消息。这种情况下你将无法受益于加密。在公共房间中,加密消息会降低消息的收发效率。", + "encrypted_room_public_confirm_description_2": "为避免这些问题,请为你计划进行的对话创建一个新的公共房间。", + "encrypted_room_public_confirm_title": "你确定要公开此加密房间?", + "encryption_forced": "服务器要求禁用加密。", + "encryption_permanent": "加密一旦启用就无法禁用。", + "error_join_rule_change_title": "连接规则更新失败", + "error_join_rule_change_unknown": "未知故障", + "guest_access_warning": "使用受支持的客户端的人员无需注册账户即可加入房间。", + "history_visibility_invited": "自成员被邀请时起", + "history_visibility_legend": "谁可以查看历史?", + "history_visibility_shared": "成员(完整历史)", + "history_visibility_warning": "此更改不会影响过去的消息,而只会影响新消息。了解更多", + "history_visibility_world_readable": "任何人(公开历史)", "join_rule_description": "决定谁可以加入 %(roomName)s。", - "join_rule_invite": "私有(仅邀请)", - "join_rule_invite_description": "只有受邀的人才能加入。", + "join_rule_invite": "仅限邀请", + "join_rule_invite_description": "仅限被邀请的人员加入", "join_rule_knock": "申请加入", - "join_rule_public_description": "任何人都可以找到并加入。", + "join_rule_knock_description": "除非被授予权限, 否则人员无法加入。", + "join_rule_public": "任何人", + "join_rule_public_description": "任何人都可以加入。", "join_rule_restricted": "空间成员", - "join_rule_restricted_description": "空间中的任何人都可以找到并加入。在此处编辑哪些空间可以访问。", - "join_rule_restricted_description_active_space": " 中的任何人都可以寻找和加入。你也可以选择其他空间。", - "join_rule_restricted_description_prompt": "空间中的任何人都可以找到并加入。你可以选择多个空间。", - "join_rule_restricted_description_spaces": "可访问的空间", - "join_rule_restricted_dialog_description": "决定哪些空间可以访问这个房间。如果一个空间被选中,它的成员可以找到并加入。", - "join_rule_restricted_dialog_empty_warning": "你正在移除所有空间。访问权限将预设为仅邀请", + "join_rule_restricted_description": "位于被授权的空间的任何人无需邀请即可加入。管理空间", + "join_rule_restricted_description_active_space": "任何在 中的成员都可以加入。", + "join_rule_restricted_description_prompt": "在空间中的任何人都可以加入。", + "join_rule_restricted_description_spaces": "被授权的空间", + "join_rule_restricted_dialog_description": "无需邀请即可加入 。", + "join_rule_restricted_dialog_empty_warning": "你将移除所有已授权的空间。访问权将默认变更为“仅邀请”。", "join_rule_restricted_dialog_filter_placeholder": "搜索空间", - "join_rule_restricted_dialog_heading_other": "你可能不知道的其他空间或房间", - "join_rule_restricted_dialog_heading_room": "你知道的包含此房间的空间", - "join_rule_restricted_dialog_heading_space": "你知道的包含这个空间的空间", - "join_rule_restricted_dialog_heading_unknown": "这些可能是其他房间管理员的一部分。", - "join_rule_restricted_dialog_title": "选择空间", + "join_rule_restricted_dialog_heading_known": "此房间未被包含在你的空间", + "join_rule_restricted_dialog_heading_other": "其它你并非其成员的空间", + "join_rule_restricted_dialog_heading_room": "包含此房间的空间", + "join_rule_restricted_dialog_heading_space": "你已知包含到此空间的子空间", + "join_rule_restricted_dialog_heading_unknown": "这些可能是其他房间管理员参与的房间。", + "join_rule_restricted_dialog_title": "管理空间", "join_rule_restricted_n_more": { - "other": "以及另 %(count)s", - "one": "& 另外 %(count)s" + "one": "与更多 %(count)s 个", + "other": "与更多 %(count)s 个" }, "join_rule_restricted_summary": { - "other": "目前,%(count)s 个空间可以访问", - "one": "目前,一个空间有访问权限" + "one": "当前已授权 1 个空间", + "other": "当前已授权 %(count)s 个空间" }, - "join_rule_restricted_upgrade_description": "此升级将允许选定的空间成员无需邀请即可访问此房间。", - "join_rule_restricted_upgrade_warning": "这个房间位于你不是管理员的某些空间中。 在这些空间中,旧房间仍将显示,但系统会提示人们加入新房间。", - "join_rule_upgrade_awaiting_room": "正在加载新房间", + "join_rule_restricted_upgrade_description": "此升级将允许选定空间的成员无需邀请即可访问此房间。", + "join_rule_restricted_upgrade_warning": "此房间位于你并非管理员的某些空间中。在这些空间中,旧房间仍会显示,但系统会提示用户加入新房间。", + "join_rule_upgrade_awaiting_room": "正在载入新房间", "join_rule_upgrade_required": "需要升级", "join_rule_upgrade_sending_invites": { "one": "正在发送邀请…", - "other": "正在发送邀请… (%(count)s 中的 %(progress)s)" + "other": "正在发送邀请…(%(count)s 个中的第 %(progress)s 个)" }, "join_rule_upgrade_updating_spaces": { - "other": "正在更新房间… (%(count)s 中的 %(progress)s)", - "one": "正在更新空间…" + "one": "正在升级空间…", + "other": "正在更新空间…(%(count)s 个中的第 %(progress)s 个)" }, "join_rule_upgrade_upgrading_room": "正在升级房间", - "public_without_alias_warning": "要链接至此房间,请添加一个地址。", - "publish_room": "使此房间在公共房间目录中可见。", - "publish_space": "使此空间在公共房间目录中可见。", - "strict_encryption": "永不从此会话向此房间中未验证的会话发送加密消息", - "title": "隐私安全" + "join_rule_world_readable_description": "更改谁可以加入房间也会更改未来消息的可见性。", + "public_without_alias_warning": "要关联到此房间,请添加地址。", + "publish_room": "使此房间在公共房间目录可见。", + "publish_space": "使此空间在公共房间目录可见。", + "strict_encryption": "仅发送消息到已验证的用户。", + "title": "安全与隐私" }, - "title": "房间设置 - %(roomName)s", + "title": "房间设置:%(roomName)s", "upload_avatar_label": "上传头像", "visibility": { "alias_section": "地址", - "error_failed_save": "更新此空间的可见性失败", - "error_update_guest_access": "更新此空间的游客访问权限失败", - "error_update_history_visibility": "更新此空间的历史记录可见性失败", - "guest_access_explainer": "游客无需账户即可加入空间。", - "guest_access_label": "启用游客访问权限", + "error_failed_save": "此空间的设置更新失败", + "error_update_guest_access": "此空间的访客访问更新失败", + "error_update_history_visibility": "此空间的历史可见性更新失败", + "guest_access_disabled": "你无权更改访客访问。", + "guest_access_explainer": "访客可以在没有账户的情况下加入。这将对公共空间有用。", + "guest_access_label": "启用访客访问", "history_visibility_anyone_space": "预览空间", - "history_visibility_anyone_space_description": "允许人们在加入前预览你的空间。", - "history_visibility_anyone_space_recommendation": "建议用于公开空间。", - "title": "可见性" + "history_visibility_anyone_space_description": "允许人员在加入前预览空间。", + "history_visibility_anyone_space_disabled": "你无权更改历史可见性。", + "history_visibility_anyone_space_recommendation": "推荐用于公共空间。", + "title": "安全与隐私" }, "voip": { "call_type_section": "通话类型", - "enable_element_call_caption": "%(brand)s是端到端加密的,但是目前仅限于少数用户。", - "enable_element_call_label": "启用%(brand)s作为此房间的额外通话选项", - "enable_element_call_no_permissions_tooltip": "你没有足够的权限更改这个。" + "enable_element_call_caption": "%(brand)s 为端到端加密,但当前仅限少数用户。", + "enable_element_call_label": "在此房间启用 %(brand)s 作为额外通话选项", + "enable_element_call_no_permissions_tooltip": "你无权更改此设置。" } }, "room_summary_card_back_action_label": "房间信息", "scalar": { - "error_create": "无法创建挂件。", - "error_membership": "你不在此房间中。", - "error_missing_room_id": "缺少roomId。", - "error_missing_room_id_request": "请求中缺少room_id", - "error_missing_user_id_request": "请求中缺少user_id", - "error_permission": "你没有权限在此房间进行那个操作。", - "error_power_level_invalid": "权力级别必须是正整数。", - "error_room_not_visible": "房间%(roomId)s不可见", + "error_create": "无法创建小部件。", + "error_membership": "你不在此房间。", + "error_missing_room_id": "缺少房间 ID.", + "error_missing_room_id_request": "请求中缺少“room_id”", + "error_missing_user_id_request": "请求中缺少“user_id”", + "error_permission": "你无权在此房间内执行此操作。", + "error_power_level_invalid": "权力值必须为正整数。", + "error_room_not_visible": "房间 %(roomId)s 不可见", "error_room_unknown": "无法识别此房间。", "error_send_request": "请求发送失败。", - "failed_read_event": "读取时间失败", - "failed_send_event": "发送事件失败" + "failed_read_event": "事件读取失败", + "failed_send_event": "事件发送失败" }, "server_offline": { - "description": "你的服务器未响应你的一些请求。下方是一些最可能的原因。", - "description_1": "服务器(%(serverName)s)花了太长时间响应。", - "description_2": "你的防火墙或防病毒软件阻止了此请求。", - "description_3": "一个浏览器扩展阻止了此请求。", - "description_4": "此服务器为离线状态。", - "description_5": "此服务器拒绝了你的请求。", - "description_6": "你的区域难以连接上互联网。", - "description_7": "尝试联系服务器时出现连接错误。", - "description_8": "服务器没有配置为提示错误是什么(CORS)。", - "empty_timeline": "全数阅毕。", - "recent_changes_heading": "尚未被接受的最近更改", + "description": "服务器未响应你的某些请求。以下是一些最可能的原因。", + "description_1": "服务器(%(serverName)s)响应时间过长。", + "description_2": "你的防火墙或杀毒软件阻止了请求。", + "description_3": "浏览器扩展阻止了该请求。", + "description_4": "此服务器已离线。", + "description_5": "服务器已拒绝你的请求。", + "description_6": "你所在的区域在连接 Internet 时遇到困难。", + "description_7": "尝试联系服务器时发生连接错误。", + "description_8": "服务器的配置未能说明问题原因(CORS)。", + "empty_timeline": "你已阅读所有消息", + "recent_changes_heading": "尚未收到最近的更改", "title": "服务器未响应" }, + "service_worker_error": { + "description": "%(brand)s 需要一个 Service Worker 来从 Matrix 内容存储库加载经过身份验证的媒体。当前浏览器不支持此功能,因此你可能会遇到媒体加载失败的情况。", + "title": "Services Worker 载入失败" + }, "seshat": { - "error_initialising": "消息搜索初始化失败,请检查你的设置以获取更多信息", - "reset_button": "重置活动存储", - "reset_description": "你大概率不想重置你的活动缩影存储", - "reset_explainer": "如果这样做,请注意你的消息并不会被删除,但在重新建立索引时,搜索体验可能会降低片刻", - "reset_title": "重置活动存储?", - "warning_kind_files": "当前版本的 %(brand)s 不支持查看某些加密文件", - "warning_kind_files_app": "使用桌面端应用来查看所有加密文件", - "warning_kind_search": "当前版本的 %(brand)s 不支持搜索加密消息", - "warning_kind_search_app": "使用桌面端英语来搜索加密消息" + "error_initialising": "消息搜索初始化失败,请检查设置了解更多信息。", + "reset_button": "重置事件存储", + "reset_description": "你很可能不想重置事件索引存储", + "reset_explainer": "如果你这样做,请注意,你的任何消息都不会被删除,但在重新创建索引期间,搜索体验可能会暂时下降。", + "reset_title": "重置事件存储?", + "warning_kind_files": "此版本的 %(brand)s 不支持查看某些加密文件", + "warning_kind_files_app": "使用桌面 App 查看所有加密文件", + "warning_kind_search": "此版本的 %(brand)s 不支持搜索加密消息", + "warning_kind_search_app": "使用桌面 App 搜索加密消息" }, "setting": { "help_about": { - "access_token_detail": "你的访问令牌可以完全访问你的账户。不要将其与任何人分享。", + "access_token_detail": "访问 Token 可以完全控制账户,请勿分享给任何人。", "brand_version": "%(brand)s 版本:", - "clear_cache_reload": "清理缓存并重载", - "help_link": "关于 %(brand)s 的使用说明。", - "homeserver": "服务器介绍:%(homeserverUrl)s", - "title": "帮助及关于", + "clear_cache_reload": "清除缓存并重载", + "crypto_version": "加密组件版本:", + "dialog_title": "设置:帮助与关于", + "help_link": "点击此处获取有关使用 %(brand)s 的帮助。", + "homeserver": "主服务器 URL:%(homeserverUrl)s", + "identity_server": "身份服务器为 %(identityServerUrl)s", + "title": "帮助与关于", "versions": "版本" } }, "settings": { + "account": { + "dialog_title": "设置:账户", + "title": "账户" + }, "all_rooms_home": "在主页显示所有房间", - "all_rooms_home_description": "你加入的所有房间都会显示在主页。", - "always_show_message_timestamps": "总是显示消息时间戳", + "all_rooms_home_description": "你加入的所有房间都将显示在主页。", + "always_show_message_timestamps": "始终显示消息时间戳", "appearance": { + "bundled_emoji_font": "使用内置的 Emoji 样式", + "compact_layout": "显示紧凑文字及消息", + "compact_layout_description": "必须选择“现代布局”以启用此功能。", "custom_font": "使用系统字体", - "custom_font_description": "设置一个安装在你的系统上的字体名称,%(brand)s 会尝试使用它。", + "custom_font_description": "设置当前系统已安装的字体名称,%(brand)s 将尝试使用该字体。", "custom_font_name": "系统字体名称", - "custom_font_size": "使用自定义大小", - "custom_theme_error_downloading": "下载主题信息时发生错误。", - "custom_theme_invalid": "主题方案无效。", - "font_size": "字体大小", + "custom_font_size": "使用自定义字号", + "custom_theme_add": "添加自定义主题", + "custom_theme_downloading": "正在下载自定义主题…", + "custom_theme_error_downloading": "下载主题时出错", + "custom_theme_help": "输入要应用的自定义主题的 URL。", + "custom_theme_invalid": "主题协议无效。", + "dialog_title": "设置:外观", + "font_size": "文字大小", + "font_size_default": "%(fontSize)s(默认)", + "high_contrast": "高对比度", "image_size_default": "默认", - "image_size_large": "大", + "image_size_large": "大型", "layout_bubbles": "消息气泡", "layout_irc": "IRC(实验性)", - "match_system_theme": "匹配系统主题", - "timeline_image_size": "时间线中的图像大小" + "match_system_theme": "跟随系统主题", + "timeline_image_size": "时间线中的图像尺寸" }, - "automatic_language_detection_syntax_highlight": "启用语法高亮的自动语言检测", + "automatic_language_detection_syntax_highlight": "启用“自动检测语法高亮中的编程语言”", "autoplay_gifs": "自动播放 GIF", "autoplay_videos": "自动播放视频", - "big_emoji": "在聊天中启用大型表情符号", + "big_emoji": "在聊天中启用加大的 Emoji", "code_block_expand_default": "默认展开代码块", - "code_block_line_numbers": "在代码块中显示行号", - "disable_historical_profile": "在消息历史记录中显示用户当前的头像和姓名", - "emoji_autocomplete": "启用实时表情符号建议", - "enable_markdown": "启用Markdown", - "enable_markdown_description": "以/plain 开始发送 Markdown 格式的信息。", + "code_block_line_numbers": "显示代码块中的行号", + "disable_historical_profile": "在消息历史中显示用户当前名称与个人资料图像", + "discovery": { + "title": "如何找到你" + }, + "emoji_autocomplete": "在键入期间启用 Emoji 建议", + "enable_markdown": "启用 Markdown", + "enable_markdown_description": "在消息开头输入 /plain 以临时禁用 Markdown。", + "encryption": { + "advanced": { + "breadcrumb_first_description": "你的账户的详细信息、联系人、偏好设置与聊天列表将被保留", + "breadcrumb_page": "重置加密", + "breadcrumb_second_description": "你将丢失所有仅存储在服务器上的消息历史", + "breadcrumb_third_description": "你将需要再次验证你的所有设备与联系人", + "breadcrumb_title": "你确定要重置数字身份?", + "breadcrumb_title_cant_confirm": "你需要重置数字身份", + "breadcrumb_title_forgot": "忘记恢复密钥?你需要重置数字身份。", + "breadcrumb_title_sync_failed": "同步密钥存储失败。你需要重置数字身份。", + "breadcrumb_warning": "仅当你认为账户被盗时才这么做。", + "details_title": "加密详细信息", + "do_not_close_warning": "在重置完成之前请勿关闭此窗口", + "export_keys": "导出密钥", + "import_keys": "导入密钥", + "other_people_device_description": "警告:未向你明确验证(例如使用 Emoji)的用户、已验证用户的未验证设备都将不会收到你的加密消息。要使此更改生效需要重新启动应用程序。", + "other_people_device_label": "在加密房间中仅发送消息到已验证的用户", + "other_people_device_title": "其他人的设备", + "reset_identity": "重置密码学身份", + "reset_in_progress": "正在重置…", + "session_id": "会话 ID:", + "session_key": "会话密钥:", + "title": "高级" + }, + "confirm_key_storage_off": "你确定要持续关闭密钥存储?", + "confirm_key_storage_off_description": "如果你移除所有设备将丢失消息历史,并需要重新验证所有现有联系人。了解更多", + "delete_key_storage": { + "breadcrumb_page": "删除密钥存储", + "confirm": "删除密钥存储", + "description": "删除密钥存储将从服务器中移除密码学身份与消息密钥,并关闭以下安全功能:", + "list_first": "你将不会在新设备上拥有加密消息历史", + "list_second": "如果你不再登录任何设备,你将丢失对加密消息的访问权。", + "title": "你确定要关闭密钥存储并将其删除?" + }, + "device_not_verified_button": "验证此设备", + "device_not_verified_description": "你需要验证此设备才能查看加密设置。", + "device_not_verified_title": "设备未验证", + "dialog_title": "设置:加密", + "key_storage": { + "allow_key_storage": "允许密钥存储", + "description": "这将允许你在任意新设备上查看聊天历史,这对备份聊天和数字身份是必需的。", + "title": "密钥存储" + }, + "recovery": { + "change_recovery_confirm_button": "确认新的恢复密钥", + "change_recovery_confirm_description": "请在下面输入新的恢复密钥以完成验证。你的旧密钥将不再有效。", + "change_recovery_confirm_title": "输入新的恢复密钥", + "change_recovery_key": "更改恢复密钥", + "change_recovery_key_description": "在安全的地方记下新的恢复密钥。点击“继续”按钮以确认更改。", + "change_recovery_key_title": "更改恢复密钥?", + "description": "你的聊天已被端到端加密自动备份。如果你无法访问所有设备,则需要使用恢复密钥恢复备份并保留数字身份。", + "enter_key_error": "你输入的恢复密钥不正确。", + "enter_recovery_key": "输入恢复密钥", + "forgot_recovery_key": "忘记恢复密钥?", + "key_storage_warning": "你的密钥存储不同步。点击以下按钮之一即可修复此问题。", + "save_key_description": "不要与任何人分享!", + "save_key_title": "恢复密钥", + "set_up_recovery": "获取恢复密钥", + "set_up_recovery_confirm_button": "完成设置", + "set_up_recovery_confirm_description": "请输入上一屏幕显示的恢复密钥以完成恢复设置。", + "set_up_recovery_confirm_title": "输入恢复密钥以确认", + "set_up_recovery_description": "你的密钥存储受恢复密钥保护。如果设置后需要新的恢复密钥,可以选择“%(changeRecoveryKeyButton)s”重新创建。", + "set_up_recovery_save_key_description": "请将此恢复密钥保存到安全的地方,例如密码管理器、加密笔记或物理保险箱。", + "set_up_recovery_save_key_title": "保存恢复密钥到安全的地方", + "set_up_recovery_secondary_description": "点击“继续”后,我们将生成恢复密钥。", + "title": "备份" + }, + "title": "加密" + }, "general": { "account_management_section": "账户管理", "account_section": "账户", - "add_email_dialog_title": "添加邮箱", - "add_email_failed_verification": "邮箱验证失败:请确保你已点击邮件中的链接", - "add_email_instructions": "我们已向你发送了一封电子邮件,以验证你的地址。 请按照里面的说明操作,然后单击下面的按钮。", - "add_msisdn_confirm_body": "点击下面的按钮,以确认添加此电话号码。", + "add_email_dialog_title": "添加邮件地址", + "add_email_failed_verification": "邮件地址验证失败:请确保点击了邮件中的链接。", + "add_email_instructions": "我们已向你发送了一封邮件,用于验证你的地址。请按照邮件中的说明操作,然后点击以下按钮。", + "add_msisdn_confirm_body": "点击以下按钮确认添加此电话号码。", "add_msisdn_confirm_button": "确认添加电话号码", - "add_msisdn_confirm_sso_button": "通过单点登录以证明你的身份,并确认添加此电话号码。", + "add_msisdn_confirm_sso_button": "请确认使用单点登录添加此电话号码以证明身份。", "add_msisdn_dialog_title": "添加电话号码", - "add_msisdn_instructions": "一封短信已发送至 +%(msisdn)s。请输入其中包含的验证码。", - "add_msisdn_misconfigured": "MSISDN的新增/绑定流程配置错误", - "confirm_adding_email_body": "点击下面的按钮,以确认添加此邮箱地址。", - "confirm_adding_email_title": "确认添加邮箱", - "deactivate_confirm_body": "你确定要停用你的账户吗?此操作不可逆。", - "deactivate_confirm_body_sso": "通过单点登录证明你的身份并确认停用你的账户。", - "deactivate_confirm_content_1": "你将无法重新激活你的账户", - "deactivate_confirm_continue": "确认账户停用", + "add_msisdn_instructions": "一条短信已发送到 +%(msisdn)s。请输入短信中包含的验证码。", + "add_msisdn_misconfigured": "添加或绑定 MSISDN 流程配置错误", + "allow_spellcheck": "允许拼写检查", + "application_language": "应用程序语言", + "application_language_reload_hint": "选择另一种语言将重载 App", + "avatar_open_menu": "打开头像菜单", + "avatar_remove_progress": "正在移除图像…", + "avatar_save_progress": "正在上传图像…", + "avatar_upload_error_text": "不支持的文件格式或图像大于 %(size)s。", + "avatar_upload_error_text_generic": "文件格式可能不受支持。", + "avatar_upload_error_title": "无法上传头像", + "confirm_adding_email_body": "点击以下按钮确认添加此邮件地址。", + "confirm_adding_email_title": "确认添加邮件", + "deactivate_confirm_body": "你确定要停用账户?这将不可逆转。", + "deactivate_confirm_body_sso": "要确认停用你的账户,请使用单点登录以证明身份。", + "deactivate_confirm_content": "确认要停用你的账户。如果你继续:", + "deactivate_confirm_content_1": "你将无法重新激活账户", + "deactivate_confirm_content_2": "你将无法再登录", + "deactivate_confirm_content_3": "任何人都无法重复使用你的用户名 (MXID),包括你自己:此用户名将保持不可用状态。", + "deactivate_confirm_content_4": "你将离开你所在的所有房间与私聊", + "deactivate_confirm_content_5": "你将被从身份服务器中移除:你的好友将无法再通过你的邮件地址或电话号码找到你。", + "deactivate_confirm_content_6": "你的旧消息仍然会被接收者看到,就像你过去发送的邮件一样。是否对以后加入房间的人员隐藏你发送的消息?", + "deactivate_confirm_continue": "确认停用账户", + "deactivate_confirm_erase_label": "隐藏我的信息不被新加入者看到", "deactivate_section": "停用账户", - "deactivate_warning": "停用你的账户是永久性动作——小心!", - "discovery_email_empty": "你在上方添加邮箱后发现选项将会出现。", - "discovery_email_verification_instructions": "验证你的收件箱中的链接", - "discovery_msisdn_empty": "你添加电话号码后发现选项将会出现。", - "discovery_needs_terms": "同意身份服务器(%(serverName)s)的服务协议以允许自己被通过邮件地址或电话号码发现。", - "email_address_in_use": "此邮箱地址已被使用", - "email_address_label": "电子邮箱地址", - "email_not_verified": "你的邮件地址尚未被验证", - "email_verification_instructions": "点击你所收到的电子邮件中的链接进行验证,然后再次点击继续。", - "emails_heading": "电子邮箱地址", - "error_add_email": "无法添加邮箱地址", - "error_deactivate_communication": "联系服务器时出现问题。请重试。", - "error_deactivate_invalid_auth": "服务器未返回有效认证信息。", - "error_deactivate_no_auth": "服务器不要求任何认证", - "error_email_verification": "无法验证邮箱地址。", - "error_invalid_email": "邮箱地址格式错误", - "error_invalid_email_detail": "这似乎不是有效的邮箱地址", + "deactivate_warning": "停用账户是一项永久性操作,请务必小心!", + "discovery_email_empty": "添加邮件后,将出现发现选项。", + "discovery_email_verification_instructions": "验证收件箱中的链接", + "discovery_msisdn_empty": "添加电话号码后将出现发现选项。", + "discovery_needs_terms": "同意身份服务器 (%(serverName)s) 的服务条款,以便允许他人通过邮件地址或电话号码找到你。", + "discovery_needs_terms_title": "让人们找到你", + "display_name": "显示名称", + "display_name_error": "无法设置显示名称", + "email_adding_unsupported_by_hs": "此主服务器不支持向账户添加邮件地址。", + "email_address_in_use": "此邮件地址已被使用", + "email_address_label": "邮件地址", + "email_not_verified": "你的邮件地址尚未经过验证", + "email_verification_instructions": "点击你收到的邮件中的链接进行验证,然后再次点击“继续”。", + "emails_heading": "邮件地址", + "error_add_email": "无法添加邮件地址", + "error_deactivate_communication": "与服务器通信时出现问题。请重试。", + "error_deactivate_invalid_auth": "服务器未能返回有效的身份验证信息。", + "error_deactivate_no_auth": "服务器无需任何身份验证", + "error_email_verification": "无法验证邮件地址。", + "error_invalid_email": "无效邮件地址", + "error_invalid_email_detail": "这似乎不是有效的邮件地址", "error_msisdn_verification": "无法验证电话号码。", - "error_password_change_403": "修改密码失败。确认原密码输入正确吗?", + "error_password_change_403": "密码更改失败。密码是否正确?", + "error_password_change_http": "%(errorMessage)s(HTTP 状态码 %(httpStatus)s)", + "error_password_change_title": "更改密码时出错", + "error_password_change_unknown": "更改密码时出现未知错误 (%(stringifiedError)s)", "error_remove_3pid": "无法移除联系人信息", - "error_revoke_email_discovery": "无法撤消电子邮件地址共享", - "error_revoke_msisdn_discovery": "无法撤销电话号码共享", - "error_share_email_discovery": "无法共享邮件地址", - "error_share_msisdn_discovery": "无法共享电话号码", - "identity_server_no_token": "找不到身份访问令牌", - "identity_server_not_set": "身份服务器未设置", - "language_section": "语言与地区", + "error_revoke_email_discovery": "无法撤消分享的邮件地址", + "error_revoke_msisdn_discovery": "无法找到与此电话号码对应的分享", + "error_share_email_discovery": "无法分享邮件地址", + "error_share_msisdn_discovery": "无法分享电话号码", + "identity_server_no_token": "未找到身份访问 Token", + "identity_server_not_set": "未设置身份服务器", + "invalid_phone_number": "提供的电话号码似乎无效。", + "language_section": "语言", + "msisdn_adding_unsupported_by_hs": "此主服务器不支持向账户添加电话号码。", "msisdn_in_use": "此电话号码已被使用", "msisdn_label": "电话号码", "msisdn_verification_field_label": "验证码", - "msisdn_verification_instructions": "请输入短信中发送的验证码。", + "msisdn_verification_instructions": "请输入通过短信发送的验证码。", "msisdns_heading": "电话号码", - "password_change_section": "设置一个新密码", - "password_change_success": "你的密码已成功更改。", - "remove_email_prompt": "删除 %(email)s 吗?", - "remove_msisdn_prompt": "删除 %(phone)s 吗?", - "spell_check_locale_placeholder": "选择区域设置" + "oidc_manage_button": "管理账户", + "password_change_section": "设置新的账户密码…", + "password_change_success": "密码已成功更改。", + "personal_info": "个人信息", + "profile_subtitle": "这是你在 App 上向他人展示的形象。", + "profile_subtitle_oidc": "你的账户由身份提供者单独管理,因此某些个人信息无法在此处更改。", + "remove_email_prompt": "移除 %(email)s?", + "remove_msisdn_prompt": "移除 %(phone)s?", + "spell_check_locale_placeholder": "选择区域", + "unable_to_load_emails": "无法加载邮件地址", + "unable_to_load_msisdns": "无法载入电话号码", + "username": "用户名" }, - "inline_url_previews_default": "默认启用行内URL预览", - "inline_url_previews_room": "对此房间的所有参与者默认启用URL预览", - "inline_url_previews_room_account": "对此房间启用URL预览(仅影响你)", - "insert_trailing_colon_mentions": "在消息开头的提及用户的地方后面插入尾随冒号", - "jump_to_bottom_on_send": "发送消息时跳转到时间线底部", + "inline_url_previews_default": "启用预览", + "inline_url_previews_encrypted": "在加密房间启用 URL 预览", + "insert_trailing_colon_mentions": "在位于消息开头的用户提及后插入一个冒号", + "invite_controls": { + "default_label": "允许用户邀请你到房间" + }, + "jump_to_bottom_on_send": "发送消息后跳转到时间线末尾", "key_backup": { "setup_secure_backup": { - "cancel_warning": "如果你现在取消,你可能会丢失加密的消息和数据,如果你丢失了登录信息的话。", - "confirm_security_phrase": "确认你的安全短语", - "description": "通过在你的服务器上备份加密密钥来防止丢失你对加密消息和数据的访问权。", - "download_or_copy": "%(downloadButton)s或%(copyButton)s", - "enter_phrase_title": "输入安全短语", - "enter_phrase_to_confirm": "再次输入你的安全短语进行确认。", - "generate_security_key_description": "我们将为您生成一个安全密钥,将其存储在安全的地方,例如密码管理器或保险箱。", - "generate_security_key_title": "生成一个安全密钥", - "pass_phrase_match_failed": "不匹配。", - "pass_phrase_match_success": "匹配成功!", - "phrase_strong_enough": "棒!这个安全短语看着够强。", + "backup_setup_success_description": "你的密钥正在从此设备备份。", + "backup_setup_success_title": "安全备份成功", + "cancel_warning": "如果你现在取消,在无法访问登录信息的情况下,可能会丢失加密消息与数据。", + "confirm_security_phrase": "确认安全口令", + "description": "通过在服务器上备份加密密钥,防止丢失对加密消息和数据的访问权。", + "download_or_copy": "%(downloadButton)s 或 %(copyButton)s", + "enter_phrase_description": "输入只有你知道的安全口令用于保护你的数据。为了安全起见,请勿重复使用账户密码。", + "enter_phrase_title": "输入安全口令", + "enter_phrase_to_confirm": "再次输入安全口令以确认。", + "generate_security_key_description": "我们将为你生成一个恢复密钥,以便你将其存储在安全的地方,例如密码管理器或保险箱。", + "generate_security_key_title": "生成恢复密钥", + "pass_phrase_match_failed": "不匹配", + "pass_phrase_match_success": "匹配!", + "phrase_strong_enough": "很好!此口令看起来足够强。", "secret_storage_query_failure": "无法查询秘密存储状态", - "security_key_safety_reminder": "将您的安全密钥存放在安全的地方,例如密码管理器或保险箱,因为它用于保护您的加密数据。", - "set_phrase_again": "返回重新设置。", - "settings_reminder": "你也可以在设置中设置安全备份并管理你的密钥。", - "title_confirm_phrase": "确认安全密码", - "title_save_key": "保存你的安全密钥", - "title_set_phrase": "设置一个安全密码", + "security_key_safety_reminder": "请将恢复密钥存储在安全的地方,例如密码管理器或保险箱,因为它用于保护你的加密数据。", + "set_phrase_again": "返回以再次设置。", + "settings_reminder": "你还可以在“设置”中设置安全备份与管理密钥。", + "title_confirm_phrase": "确认安全口令", + "title_save_key": "保存恢复密钥", + "title_set_phrase": "设置安全口令", "unable_to_setup": "无法设置秘密存储", - "use_different_passphrase": "使用不同的口令词组?", - "use_phrase_only_you_know": "使用一个只有你知道的密码,你也可以保存安全密钥以供备份使用。" + "use_different_passphrase": "使用不同的口令?", + "use_phrase_only_you_know": "使用只有你知道的密码,并可选择保存恢复密钥以用于备份。" } }, "key_export_import": { - "confirm_passphrase": "确认口令词组", - "enter_passphrase": "输入口令词组", - "export_description_1": "此操作允许你将加密房间中收到的消息的密钥导出为本地文件。你可以将文件导入其他 Matrix 客户端,以便让别的客户端在未收到密钥的情况下解密这些消息。", + "confirm_passphrase": "确认口令", + "enter_passphrase": "输入口令", + "export_description_1": "此过程允许你将加密房间中收到的消息的密钥导出到本地文件。你之后可以将该文件导入到另一个 Matrix 客户端以便该客户端也能解密这些消息。", + "export_description_2": "导出的文件将允许任何可以读取它的人解密你可以看到的任何加密消息,应小心确保其安全。为此你应该在下面输入一个唯一的口令,该口令仅用于加密导出的数据,并且只能使用相同的口令导入数据。", "export_title": "导出房间密钥", "file_to_import": "要导入的文件", - "import_description_1": "此操作允许你导入之前从另一个 Matrix 客户端中导出的加密密钥文件。导入完成后,你将能够解密那个客户端可以解密的加密消息。", - "import_description_2": "导出文件受口令词组保护。你应该在此输入口令词组以解密此文件。", + "import_description_1": "此过程允许你导入以前从其它 Matrix 客户端导出的加密密钥,然后你将能够解密其它客户端可以解密的任何消息。", + "import_description_2": "已导出的文件使用口令进行保护。你应该在此处输入口令来解密此文件。", "import_title": "导入房间密钥", - "phrase_cannot_be_empty": "口令词组不能为空", - "phrase_must_match": "口令词组必须匹配" + "phrase_cannot_be_empty": "口令不能为空", + "phrase_must_match": "口令必须匹配", + "phrase_strong_enough": "很好!此口令看起来足够强" }, "keyboard": { + "dialog_title": "设置:键盘", "title": "键盘" }, + "labs": { + "dialog_title": "设置:实验室" + }, + "labs_mjolnir": { + "dialog_title": "设置:已忽略的用户" + }, + "media_preview": { + "hide_avatars": "隐藏房间与邀请者的头像", + "hide_media": "始终隐藏", + "media_preview_description": "点击隐藏的媒体即可将其恢复显示", + "media_preview_label": "在时间线上显示媒体", + "show_in_private": "在私有房间", + "show_media": "始终显示" + }, + "not_supported": "服务器尚未实现此功能。", "notifications": { - "enable_audible_notifications_session": "为此会话启用声音通知", + "default_setting_description": "此设置将默认应用于你的所有房间。", + "default_setting_section": "要接收的通知类型(默认设置)", + "desktop_notification_message_preview": "在桌面通知中显示消息预览", + "dialog_title": "设置:通知", + "email_description": "接收错过的通知的邮件摘要", + "email_section": "邮件摘要", + "email_select": "选择要向其发送摘要的邮件地址。可以在中管理邮件地址。", + "enable_audible_notifications_session": "为此会话的通知启用声音", "enable_desktop_notifications_session": "为此会话启用桌面通知", - "enable_email_notifications": "为 %(email)s 启用电子邮件通知", + "enable_email_notifications": "为 %(email)s 启用邮件通知", "enable_notifications_account": "为此账户启用通知", - "enable_notifications_account_detail": "关闭以在你全部设备和会话上停用通知", - "enable_notifications_device": "为此设备启用通知", - "error_loading": "加载你的通知设置时出错。", - "error_permissions_denied": "%(brand)s 没有通知发送权限 - 请检查你的浏览器设置", - "error_permissions_missing": "%(brand)s 没有通知发送权限 - 请重试", - "error_saving": "保存通知偏好时出错", - "error_saving_detail": "保存你的通知偏好时出错。", + "enable_notifications_account_detail": "关闭此选项将禁用你所有设备及会话的通知", + "enable_notifications_device": "启用此设备的通知", + "error_loading": "加载通知设置时出错。", + "error_permissions_denied": "%(brand)s 无权向你发送通知,请检查浏览器设置。", + "error_permissions_missing": "%(brand)s 未被授予发送通知的权限,请重试。", + "error_saving": "保存通知设置时出错", + "error_saving_detail": "保存通知偏好时出错。", "error_title": "无法启用通知", - "messages_containing_keywords": "当消息包含关键词时", + "error_updating": "更新通知首选项时出错。请尝试再次切换选项。", + "invites": "邀请到房间", + "keywords": "在房间中使用特定关键词时显示 标记。", + "keywords_prompt": "在此处输入关键词、拼写变体或昵称", + "labs_notice_prompt": "更新:我们简化了通知设置使选项更易于查找。你曾经选择的某些自定义设置不会在此处显示,但它们仍然处于活动状态。如果继续,你的某些设置可能会更改。了解更多", + "mentions_keywords": "提及与关键词", + "mentions_keywords_only": "仅提及与关键词", + "messages_containing_keywords": "消息包含的关键词", "noisy": "响铃", + "notices": "由机器人发送的消息", + "notify_at_room": "当有人提及使用“@room”时通知", + "notify_keyword": "当有人使用关键字时通知", + "notify_mention": "当有人提及使用“@displayname”或“%(mxid)s”时通知", + "other_section": "你可能感兴趣的其它内容:", + "people_mentions_keywords": "人员、提及与关键词", + "play_sound_for_description": "此设置将默认应用于你所有设备中的所有房间。", + "play_sound_for_section": "播放声音", "push_targets": "通知目标", - "rule_call": "当受到通话邀请时", - "rule_contains_display_name": "当消息包含我的显示名称时", - "rule_contains_user_name": "当消息包含我的用户名时", + "quick_actions_mark_all_read": "所有消息设为已读", + "quick_actions_reset": "重置为默认设置", + "quick_actions_section": "快速设置", + "room_activity": "新产生的房间活动、升级与状态消息", + "rule_call": "通话邀请", + "rule_contains_display_name": "包含我的显示名称的消息", + "rule_contains_user_name": "包含我的用户名的消息", "rule_encrypted": "群聊中的加密消息", - "rule_encrypted_room_one_to_one": "私聊中的加密消息", - "rule_invite_for_me": "当我被邀请进入房间", + "rule_encrypted_room_one_to_one": "一对一聊天中的加密消息", + "rule_invite_for_me": "当我受邀到房间", "rule_message": "群聊中的消息", - "rule_room_one_to_one": "私聊中的消息", - "rule_roomnotif": "当消息包含 @room 时", - "rule_suppress_notices": "由机器人发出的消息", - "rule_tombstone": "当房间升级时", - "show_message_desktop_notification": "在桌面通知中显示消息" + "rule_room_one_to_one": "一对一聊天中的消息", + "rule_roomnotif": "包含 @room 的消息", + "rule_suppress_notices": "由机器人发送的消息", + "rule_tombstone": "当房间升级时。", + "show_message_desktop_notification": "在桌面通知中显示消息内容", + "voip": "音频与视频通话" }, "preferences": { - "always_show_menu_bar": "总是显示窗口菜单栏", - "autocomplete_delay": "自动完成延迟(毫秒)", + "Electron.enableContentProtection": "阻止窗口内容被其它应用程序捕获", + "Electron.enableHardwareAcceleration": "启用硬件加速(需要重新启动 %(appName)s 以生效)", + "always_show_menu_bar": "始终显示窗口菜单栏", + "autocomplete_delay": "自动补全延时(毫秒)", "code_blocks_heading": "代码块", - "compact_modern": "使用更紧凑的“现代”布局", + "compact_modern": "使用更简洁的“现代”布局", "composer_heading": "编辑器", + "default_timezone": "浏览器默认(%(timezone)s)", + "dialog_title": "设置:偏好", + "enable_content_protection": "启用内容保护", "enable_hardware_acceleration": "启用硬件加速", - "enable_tray_icon": "显示托盘图标并在关闭时最小化窗口至托盘", + "enable_tray_icon": "当窗口关闭时最小化其到托盘", "keyboard_heading": "键盘快捷键", - "keyboard_view_shortcuts_button": "要查看所有的键盘快捷键,点击此处。", - "media_heading": "图片、GIF 和视频", - "presence_description": "与别人分享你的活动和状态。", - "rm_lifetime": "已读标记生存期(毫秒)", - "rm_lifetime_offscreen": "已读标记屏幕外生存期(毫秒)", + "keyboard_view_shortcuts_button": "要查看所有键盘快捷键请点击此处。", + "link_previews_description": "在消息下方显示链接相关信息", + "link_previews_heading": "链接预览", + "media_heading": "图像、GIF 与视频", + "presence_description": "向其他人分享你的活跃情况。", + "publish_timezone": "在公开资料上公布时区", + "rm_lifetime": "已读标记生存时间(毫秒)", + "rm_lifetime_offscreen": "已读标记屏外生存时间(毫秒)", + "room_directory_heading": "房间目录", "room_list_heading": "房间列表", + "show_avatars_pills": "在用户、房间与事件提及中显示头像", "show_polls_button": "显示投票按钮", - "surround_text": "输入特殊字符时圈出选定的文本", - "time_heading": "显示的时间戳" + "startup_window_behaviour_label": "启动时的窗口行为", + "surround_text": "当键入特殊字符时圈选文字", + "time_heading": "显示时间", + "user_timezone": "设置时区" }, - "prompt_invite": "在发送邀请之前提示可能无效的 Matrix ID", - "replace_plain_emoji": "自动取代纯文本为表情符号", + "prompt_invite": "向可能无效的 Matrix ID 发送邀请前提示", + "replace_plain_emoji": "自动替换纯文本 Emoji", "security": { - "bulk_options_accept_all_invites": "接受所有 %(invitedRooms)s 邀请", - "bulk_options_reject_all_invites": "拒绝所有 %(invitedRooms)s 的邀请", + "analytics_description": "分享匿名数据已帮助我们识别问题。不涉及个人隐私及第三方。", + "bulk_options_accept_all_invites": "接受所有 %(invitedRooms)s 的邀请", + "bulk_options_reject_all_invites": "拒绝所有 %(invitedRooms)s 邀请", "bulk_options_section": "批量选择", - "e2ee_default_disabled_warning": "你的服务器管理员默认关闭了私人房间和私聊中的端到端加密。", - "enable_message_search": "在加密房间中启用消息搜索", + "dehydrated_device_description": "离线设备功能允许你即使未登录任何设备也能接收加密消息。", + "dehydrated_device_enabled": "离线设备已启用", + "dialog_title": "设置:安全与隐私", + "e2ee_default_disabled_warning": "私有房间与私聊默认的端到端加密已被服务器管理员禁用。", + "enable_message_search": "在加密房间启用消息搜索", "encryption_section": "加密", - "ignore_users_empty": "你没有设置忽略用户。", + "ignore_users_empty": "暂无已忽略的用户", "ignore_users_section": "已忽略的用户", "key_backup_algorithm": "算法:", - "message_search_disable_warning": "如果被禁用,加密房间内的消息不会显示在搜索结果中。", - "message_search_disabled": "在本地安全地缓存加密消息以使其出现在搜索结果中。", + "message_search_disable_warning": "如果停用此功能,来自加密房间的消息将不会显示在搜索结果中。", + "message_search_disabled": "在安全地在本地缓存加密消息以使其出现在搜索结果中。", "message_search_enabled": { - "one": "使用%(size)s存储%(rooms)s个房间的消息。在本地安全地缓存已加密的消息以使其出现在搜索结果中。", - "other": "使用%(size)s存储%(rooms)s个房间的消息。在本地安全地缓存已加密的消息以使其出现在搜索结果中。" + "one": "占用 %(size)s 的空间以存储来自 %(rooms)s 个房间的消息,加密消息已安全地在本地缓存,并可以使其显示在搜索结果中。", + "other": "占用 %(size)s 的空间以存储来自 %(rooms)s 个房间的消息,加密消息已安全地在本地缓存,并可以使其显示在搜索结果中。" }, "message_search_failed": "消息搜索初始化失败", "message_search_indexed_messages": "已索引的消息:", "message_search_indexed_rooms": "已索引的房间:", - "message_search_indexing": "正在索引:%(currentRoom)s", - "message_search_indexing_idle": "现在没有为任何房间索引消息。", + "message_search_indexing": "当前正在索引:%(currentRoom)s", + "message_search_indexing_idle": "尚未为任何房间索引消息。", "message_search_intro": "%(brand)s 正在安全地在本地缓存加密消息以使其出现在搜索结果中:", - "message_search_room_progress": "%(totalRooms)s 中之 %(doneRooms)s", + "message_search_pending_rooms": "等待索引的房间:%(pendingRooms)s", + "message_search_room_progress": "%(doneRooms)s 个房间中的 %(totalRooms)s 个", "message_search_section": "消息搜索", - "message_search_sleep_time": "消息下载速度。", + "message_search_sleep_time": "消息下载速度", "message_search_space_used": "已使用空间:", - "message_search_unsupported": "%(brand)s缺少安全地在本地缓存加密信息所必须的部件。如果你想实验此功能,请构建一个自定义的带有搜索部件的%(brand)s桌面版。", - "message_search_unsupported_web": "%(brand)s 在浏览器中运行时不能安全地在本地缓存加密信息。请使用%(brand)s 桌面版以使加密信息出现在搜索结果中。", - "record_session_details": "记录客户端名称、版本和url以便在会话管理器里更易识别", - "send_analytics": "发送统计数据", - "strict_encryption": "永不从本会话向未验证的会话发送加密消息" + "message_search_unsupported": "%(brand)s 缺少一些在本地安全缓存加密消息所需的组件。如果你想试用此功能,请构建一个自定义的 %(brand)s 桌面,并添加搜索组件。", + "message_search_unsupported_web": "%(brand)s 在 Web 浏览器中运行时无法安全地在本地缓存加密消息。请使用 %(brand)s Desktop 使加密消息出现在搜索结果中。", + "record_session_details": "在会话管理器中记录客户端名称、版本及 URL 以更易于识别", + "send_analytics": "发送分析数据", + "strict_encryption": "仅向已验证用户发送信息" }, "send_read_receipts": "发送已读回执", "send_read_receipts_unsupported": "你的服务器不支持禁用发送已读回执。", - "send_typing_notifications": "发送正在输入通知", + "send_typing_notifications": "发送键入通知", "sessions": { - "best_security_note": "为获得最佳安全性,请验证您的设备并把您不再信任或使用的设备注销。", + "best_security_note": "为了最佳的安全性请验证此会话,并移除任何你不认识或不再使用的会话。", + "browser": "浏览器", "confirm_sign_out": { - "one": "确认登出此设备", - "other": "确认登出这些设备" + "one": "确认移除此设备", + "other": "确认移除这些设备" }, "confirm_sign_out_body": { - "one": "单击下面的按钮以确认登出此设备。", - "other": "单击下面的按钮以确认登出这些设备。" + "one": "点击以下按钮确认移除此设备", + "other": "点击以下按钮确认移除这些设备" }, "confirm_sign_out_continue": { - "one": "注销设备", - "other": "注销设备" + "one": "移除设备", + "other": "移除设备" }, "confirm_sign_out_sso": { - "one": "确认注销此设备需要使用单点登录来证明您的身份。", - "other": "确认注销这些设备需要使用单点登录来证明你的身份。" + "one": "通过使用单点登录验证您的身份以确认移除此设备。", + "other": "通过使用单点登录验证您的身份以确认移除这些设备。" }, "current_session": "当前会话", - "details_heading": "会话详情", - "device_unverified_description": "验证此会话或从之登出,以取得最佳安全性和可靠性。", - "device_verified_description": "此会话已准备好进行安全的消息传输。", + "desktop_session": "桌面会话", + "details_heading": "会话详细信息", + "device_unverified_description": "验证或注销此会话以获得最佳安全性与可靠性。", + "device_unverified_description_current": "验证当前会话以增强安全消息传递。", + "device_verified_description": "此会话的安全消息传递已就绪。", + "device_verified_description_current": "当前会话的安全消息传递已就绪。", + "dialog_title": "设置:会话", + "error_pusher_state": "设置推送机制失败", + "error_set_name": "设置会话名称失败", "filter_all": "全部", - "filter_inactive": "不活跃", - "filter_inactive_description": "%(inactiveAgeDays)s天或更久不活跃", + "filter_inactive": "静默", + "filter_inactive_description": "处于静默状态 %(inactiveAgeDays)s 天甚至更长时间", "filter_label": "筛选设备", - "filter_unverified_description": "尚未准备好安全通信", - "filter_verified_description": "准备好进行安全通信了", - "inactive_days": "%(inactiveAgeDays)s+天不活跃", - "inactive_sessions": "不活跃的会话", - "ip": "IP地址", - "last_activity": "上次活动", - "no_inactive_sessions": "未找到不活跃的会话。", + "filter_unverified_description": "安全消息传递未就绪", + "filter_verified_description": "安全消息传递已就绪", + "hide_details": "隐藏详细信息", + "inactive_days": "已持续静默至少 %(inactiveAgeDays)s 天", + "inactive_sessions": "静默会话", + "inactive_sessions_explainer_1": "静默会话是指你一段时间内未使用,但仍会继续接收加密密钥的会话。", + "inactive_sessions_explainer_2": "移除静默会话可提高安全性与性能,并让你更容易识别新的会话是否可疑。", + "inactive_sessions_list_description": "考虑移除不再使用的旧会话(%(inactiveAgeDays)s 天或更早)。", + "ip": "IP 地址", + "last_activity": "最后活跃于", + "manage": "管理此会话", + "mobile_session": "移动端会话", + "n_sessions_selected": { + "one": "已选择 %(count)s 个会话", + "other": "已选择 %(count)s 个会话" + }, + "no_inactive_sessions": "未找到静默的会话。", "no_sessions": "未找到会话。", "no_unverified_sessions": "未找到未验证的会话。", "no_verified_sessions": "未找到已验证的会话。", - "other_sessions_heading": "其他会话", + "os": "操作系统", + "other_sessions_heading": "其它会话", + "push_heading": "推送通知", + "push_subheading": "允许此会话接收通知推送", + "push_toggle": "切换此会话的推送通知。", + "rename_form_caption": "请注意,会话名称也对与你通信的人可见。", "rename_form_heading": "重命名会话", + "rename_form_learn_more": "正在重命名会话", + "rename_form_learn_more_description_1": "你加入的私聊与房间中的其他用户可以查看你的完整会话列表。", + "rename_form_learn_more_description_2": "这不仅能使其确信自己确实在与你通话,还能看到你在此处输入的会话名称。", "security_recommendations": "安全建议", - "security_recommendations_description": "按照以下建议来提高您的帐户安全性。", + "security_recommendations_description": "通过以下建议增强账户安全性", "session_id": "会话 ID", + "show_details": "显示详细信息", + "sign_in_with_qr": "关联新设备", + "sign_in_with_qr_button": "显示二维码", + "sign_in_with_qr_description": "使用二维码登录到另一设备并设置安全消息传递。", + "sign_in_with_qr_unsupported": "账户提供者不支持", + "sign_out": "移除此会话", + "sign_out_all_other_sessions": "移除所有其它会话(%(otherSessionsCount)s)", "sign_out_confirm_description": { - "other": "你确定要从这 %(count)s 个会话中退出吗?" + "one": "你确定要移除 %(count)s 个会话?", + "other": "你确定要移除 %(count)s 个会话?" + }, + "sign_out_n_sessions": { + "one": "移除 %(count)s 个会话", + "other": "移除 %(count)s 个会话" }, "title": "会话", + "unknown_session": "未知会话类型", "unverified_session": "未验证的会话", + "unverified_session_explainer_1": "此会话不支持加密,因此无法验证。", + "unverified_session_explainer_2": "使用此会话时,你将无法加入启用加密的房间。", + "unverified_session_explainer_3": "为了获得最佳的安全性与隐私性,建议使用支持加密的 Matrix 客户端。", "unverified_sessions": "未验证的会话", - "unverified_sessions_list_description": "验证你的会话以增强消息传输的安全性,或从那些你不认识或不再使用的会话登出。", + "unverified_sessions_explainer_1": "未验证的会话是指已使用你的凭据登录但尚未通过你的数字身份确认的会话。", + "unverified_sessions_explainer_2": "你应特别确认自己能够识别这些会话,因为它们可能代表你的账户被未经授权使用。", + "unverified_sessions_list_description": "验证此会话以强化安全消息传递,或移除你不认识或不再使用的会话。", + "url": "URL", "verified_session": "已验证的会话", "verified_sessions": "已验证的会话", - "verified_sessions_list_description": "为了最佳安全性,请从任何不认识或不再使用的会话登出。", - "verify_session": "验证会话" + "verified_sessions_explainer_1": "已验证会话是指已经过你的数字身份确认的会话。", + "verified_sessions_explainer_2": "这意味着你拥有解锁加密消息所需的所有密钥,并向其他用户确认你信任此会话。", + "verified_sessions_list_description": "为了最佳的安全性,请移除任何你不认识或不再使用的会话。", + "verify_session": "验证会话", + "web_session": "Web 会话" }, - "show_avatar_changes": "显示个人头像变更", - "show_breadcrumbs": "在房间列表上方显示最近浏览过的房间的快捷方式", - "show_chat_effects": "显示聊天特效(如收到五彩纸屑时的动画效果)", - "show_displayname_changes": "显示显示名称更改", - "show_join_leave": "显示加入/离开消息(邀请/移除/封禁不受影响)", + "show_avatar_changes": "显示个人资料图像更改", + "show_breadcrumbs": "在房间列表之上显示最近访问的房间的捷径", + "show_chat_effects": "显示聊天特效(例如当收到“五彩纸屑”时)", + "show_displayname_changes": "显示用户的“显示名称更改”", + "show_join_leave": "显示加入及离开消息(邀请、移除及封禁不受影响)", + "show_message_previews": "显示消息预览", "show_nsfw_content": "显示 NSFW 内容", - "show_read_receipts": "显示其他用户发送的已读回执", - "show_redaction_placeholder": "已移除的消息显示为一个占位符", + "show_read_receipts": "显示其他用户的已读回执", + "show_redaction_placeholder": "为已被移除的消息显示占位符", "show_stickers_button": "显示贴纸按钮", - "show_typing_notifications": "显示正在输入通知", + "show_typing_notifications": "显示用户的键入通知", + "showbold": "在房间列表上显示所有活动(用于未读消息的圆点与数字)", "sidebar": { - "metaspaces_favourites_description": "将所有你最爱的房间和人集中在一处。", + "dialog_title": "设置:边栏", + "metaspaces_favourites_description": "集中所有设为收藏的房间与人员分组在一处。", "metaspaces_home_all_rooms": "显示所有房间", - "metaspaces_home_all_rooms_description": "在主页展示你所有的房间,即使它们是在一个空间里。", - "metaspaces_home_description": "对于了解所有事情的概况来说,主页很有用。", - "metaspaces_orphans": "空间之外的房间", - "metaspaces_orphans_description": "将所有你那些不属于某个空间的房间集中一处。", - "metaspaces_people_description": "将你所有的联系人集中一处。", + "metaspaces_home_all_rooms_description": "在主页显示所有房间,即使它们位于不同的空间。", + "metaspaces_home_description": "主页有助于概览所有对话。", + "metaspaces_orphans": "空间外的房间", + "metaspaces_orphans_description": "集中所有不属于任何空间的房间在一处。", + "metaspaces_people_description": "集中所有人员在一处。", "metaspaces_subsection": "要显示的空间", - "title": "侧边栏" + "metaspaces_video_rooms": "视频房间与会议", + "metaspaces_video_rooms_description": "集中所有私有视频房间与会议在一处。", + "metaspaces_video_rooms_description_invite_extension": "在会议中,你可以邀请 Matrix 生态以外的人。", + "spaces_explainer": "空间是对房间与人员分组的方式,除了你所在的空间,还可以使用一些预置空间。", + "title": "边栏" }, - "use_12_hour_format": "使用 12 小时制显示时间戳 (下午 2:30)", - "use_command_enter_send_message": "使用 Command + Enter 发送消息", - "use_command_f_search": "使用 Command + F 搜索时间线", - "use_control_enter_send_message": "使用Ctrl + Enter发送消息", - "use_control_f_search": "使用 Ctrl + F 搜索时间线", + "start_automatically": { + "disabled": "否", + "enabled": "是", + "label": "登录时启动 %(brand)s", + "minimised": "最小化" + }, + "tac_only_notifications": "仅在消息列中枢显示通知", + "use_12_hour_format": "以 12 小时制显示时间戳(例如:下午 2:30)", + "use_command_enter_send_message": "按 Command + Enter 发送消息", + "use_command_f_search": "允许按 Command + F 搜索时间线", + "use_control_enter_send_message": "允许按 Ctrl + Enter 发送消息", + "use_control_f_search": "允许按 Ctrl + F 搜索时间线", "voip": { - "allow_p2p": "允许1:1通话的点对点", - "allow_p2p_description": "启用后,对方可能能看到你的IP地址", + "allow_p2p": "允许点对点进行一对一通话", + "allow_p2p_description": "启用此项后, 第三方可能会看到你的 IP 地址", "audio_input_empty": "未检测到麦克风", "audio_output": "音频输出", - "audio_output_empty": "未检测到可用的音频输出方式", - "auto_gain_control": "自动获得控制权", + "audio_output_empty": "未检测到音频输出", + "auto_gain_control": "自动增益控制", "connection_section": "连接", + "dialog_title": "设置:语音与视频", "echo_cancellation": "回声消除", - "enable_fallback_ice_server": "允许使用官方的ICE辅助服务器 (%(server)s)", - "enable_fallback_ice_server_description": "仅当你的服务器不提供时才会使用。你的IP地址在通话期间会被知晓。", + "echo_cancellation_description": "在通话期间移除回声。此设置亦将应用于 Element Call。", + "enable_fallback_ice_server": "允许备选的通话辅助服务器", + "enable_fallback_ice_server_description": "仅用于你所在的主服务器不提供时。你的 IP 地址会在通话期间被分享。", "mirror_local_feed": "镜像本地视频源", - "missing_permissions_prompt": "缺少媒体权限,点击下面的按钮以请求权限。", - "noise_suppression": "噪音抑制", + "missing_permissions_prompt": "缺少媒体权限,点击按钮以请求。", + "noise_suppression": "降噪", + "noise_suppression_description": "在通话期间降低背景噪音。此设置亦将应用于 Element Call。", "request_permissions": "请求媒体权限", - "title": "语音和视频", + "title": "语音与视频", "video_input_empty": "未检测到摄像头", "video_section": "视频设置", - "voice_agc": "自动调整话筒音量", + "voice_agc": "自动调整麦克风音量", "voice_processing": "语音处理", "voice_section": "语音设置" }, @@ -2257,623 +3054,678 @@ "warning": "警告:" }, "share": { - "permalink_message": "选中消息的链接", - "permalink_most_recent": "最新消息的链接", - "title_message": "分享房间消息", + "link_copied": "链接已复制", + "permalink_message": "链接可指向所选消息", + "permalink_most_recent": "链接到最近消息", + "share_call": "会议邀请链接", + "share_call_subtitle": "外部用户无需 Matrix 账户即可加入通话的链接:", + "title_link": "分享链接", + "title_message": "分享房间中的消息", "title_room": "分享房间", "title_user": "分享用户" }, "slash_command": { - "addwidget": "通过URL添加自定义挂件到房间", - "addwidget_iframe_missing_src": "iframe无src属性", - "addwidget_invalid_protocol": "请提供一个 https:// 或 http:// 挂件URL", - "addwidget_missing_url": "请提供一个挂件URL或嵌入代码", - "addwidget_no_permissions": "你无法修改此房间的插件。", - "ban": "按照 ID 封禁用户", - "category_actions": "动作", + "addwidget": "添加自定义小部件", + "addwidget_iframe_missing_src": "“iframe”没有“src”属性", + "addwidget_invalid_protocol": "请提供“http://”或“https://”开头的小部件 URL", + "addwidget_missing_url": "请提供小部件 URL 或其嵌入式代码", + "addwidget_no_permissions": "你在此房间无法修改小部件。", + "ban": "使用指定 ID 封禁用户", + "category_actions": "操作", "category_admin": "管理员", "category_advanced": "高级", - "category_effects": "效果", + "category_effects": "特效", "category_messages": "消息", - "category_other": "其他", - "command_error": "命令错误", - "converttodm": "将此房间会话转化为私聊会话", - "converttoroom": "将此私聊会话转化为房间会话", + "category_other": "其它", + "command_error": "指令出错", + "converttodm": "转换房间到私聊", + "converttoroom": "转换私聊到房间", "could_not_find_room": "无法找到房间", - "deop": "按照 ID 取消特定用户的管理员权限", - "devtools": "打开开发者工具窗口", - "discardsession": "强制丢弃加密房间中的当前出站群组会话", - "error_invalid_rendering_type": "命令错误:无法找到渲染类型(%(renderingType)s)", - "error_invalid_runfn": "命令错误:无法处理斜杠命令。", - "help": "显示指令清单与其描述和用法", + "deop": "通过指定的 ID 降权用户", + "devtools": "打开开发者工具对话框", + "discardsession": "强制丢弃加密房间中当前的出站组会话", + "error_invalid_rendering_type": "指令错误:无法找到修饰类型(%(renderingType)s)", + "error_invalid_room": "指令执行失败: 无法找到房间(%(roomId)s)", + "error_invalid_runfn": "指令错误:无法处理斜杠指令。", + "error_invalid_user_in_room": "在房间中无法找到用户", + "help": "将指令与其对应的用法、说明显示为一个列表", "help_dialog_title": "命令帮助", - "holdcall": "挂起当前房间的通话", - "html": "以 html 格式发送消息,不将其作为 markdown 处理", - "ignore": "忽略用户,隐藏他们发送的消息", - "ignore_dialog_description": "你忽略了 %(userId)s", + "holdcall": "将当前房间中的通话置于保持状态", + "html": "作为 HTML 发送消息,而不是将其解释为 Markdown", + "ignore": "忽略用户以为你隐藏其消息", + "ignore_dialog_description": "你正在忽略 %(userId)s", "ignore_dialog_title": "已忽略的用户", - "invite": "邀请指定ID的用户到当前房间", - "invite_3pid_needs_is_error": "使用身份服务器以通过电子邮件邀请其他用户。在设置中进行管理。", + "invite": "通过指定的 ID 邀请用户到当前房间", + "invite_3pid_needs_is_error": "使用身份服务器以通过邮件发出邀请。在设置中进行管理。", "invite_3pid_use_default_is_title": "使用身份服务器", - "invite_3pid_use_default_is_title_description": "使用身份服务器以通过电子邮件邀请其他用户。单击继续以使用默认身份服务器(%(defaultIdentityServerName)s),或在设置中进行管理。", - "invite_failed": "用户(%(user)s)最终未被邀请到%(roomId)s,但邀请工具没给出错误", - "join": "使用指定地址加入房间", - "jumptodate": "跳转到时间线中的给定日期", - "jumptodate_invalid_input": "我们无法理解给定日期 (%(inputDate)s)。尝试使用如下格式 YYYY-MM-DD。", - "lenny": "在纯文本消息开头添加 ( ͡° ͜ʖ ͡°)", + "invite_3pid_use_default_is_title_description": "使用身份服务器通过邮件地址邀请。点击“继续”以使用默认身份服务器 (%(defaultIdentityServerName)s),或在“设置”中管理。", + "invite_failed": "用户(%(user)s)最终未被邀请加入 %(roomId)s,但邀请程序未显示任何错误。", + "join": "通过指定的房间地址加入", + "jumptodate": "在时间线中跳转到指定日期", + "jumptodate_invalid_input": "我们无法理解指定的日期 (%(inputDate)s)。尝试使用 YYYY-MM-DD 格式。", + "lenny": "附加“( ͡° ͜ʖ ͡°)”到纯文本消息", + "manual_device_verification_confirm_description": "这将允许其它设备以你的身份发送和接收消息。如果有人告诉你在此处粘贴内容,你很可能被诈骗!你确定要验证此设备?", + "manual_device_verification_confirm_title": "警告:手动设备验证", "me": "显示操作", - "msg": "向指定用户发消息", - "myavatar": "在所有房间中更新您的头像", - "myroomavatar": "仅在当前房间中更改您的头像", - "myroomnick": "仅更改当前房间中的显示昵称", - "nick": "修改显示昵称", - "no_active_call": "此房间未有活跃中的通话", - "op": "定义一名用户的权力级别", + "msg": "向指定用户发送信息", + "myavatar": "更改个人资料图像(应用于所有房间)", + "myroomavatar": "仅更改我在当前房间内的个人资料图像", + "myroomnick": "仅更改我在当前房间内的显示名称", + "nick": "更改显示名称", + "no_active_call": "此房间没有活跃的通话", + "op": "定义用户的权力值", "part_unknown_alias": "无法识别的房间地址:%(roomAlias)s", - "plain": "以纯文本形式发送消息,不将其作为 markdown 处理", - "query": "与指定用户发起聊天", - "query_not_found_phone_number": "未能找到与此手机号码关联的 Matrix ID", - "rageshake": "发送带日志的错误报告", - "rainbow": "此消息以彩虹色进行渲染", - "rainbowme": "以彩虹色发送给定表情符号", - "remove": "将给定 ID 的用户移除此房间", - "roomavatar": "更改当前房间头像", + "plain": "作为纯文本发送消息,而不是将其解释为 Markdown", + "query": "打开与指定用户的聊天", + "query_not_found_phone_number": "无法找到与此电话号码对应的 Matrix ID", + "rageshake": "通过日志发送 Bug 报告", + "rainbow": "发送一个渲染为彩虹色的消息", + "rainbowme": "发送一个带有我的显示名称与渲染为彩虹色的消息", + "remove": "通过指定的 ID 从当前房间移除用户", + "roomavatar": "更改当前房间的头像", "roomname": "设置房间名称", "server_error": "服务器错误", - "server_error_detail": "服务器不可用、超载或其他东西出错了。", - "shrug": "在纯文本消息开头添加 ¯\\_(ツ)_/¯", - "spoiler": "此消息包含剧透", - "tableflip": "在纯文本消息开头添加 (╯°□°)╯︵ ┻━┻", - "topic": "获取或设置房间话题", - "topic_none": "此房间没有话题。", - "topic_room_error": "获取房间话题失败:无法找到房间(%(roomId)s)", - "unban": "按照 ID 解封用户", - "unflip": "在纯文本消息开头添加 ┬──┬ ノ( ゜-゜ノ)", - "unholdcall": "解除挂起当前房间的通话", - "unignore": "解除忽略用户,显示他们的消息", - "unignore_dialog_description": "你不再忽视 %(userId)s", - "unignore_dialog_title": "未忽略的用户", - "unknown_command": "未知命令", + "server_error_detail": "服务器不可用、超载或出现其它问题。", + "shrug": "附加“ˉ\\_(ツ)_/ˉ”到纯文本消息", + "spoiler": "作为剧透发送指定消息", + "tableflip": "附加“(╯°□°)╯︵ ┻━┻”到纯文本消息", + "topic": "获取或设置房间主题", + "topic_none": "此房间没有主题", + "topic_room_error": "获取房间主题失败:无法找到房间(%(roomId)s)", + "unban": "使用指定的 ID 解封用户", + "unflip": "附加“┬──┬ ノ( ゜-゜ノ)”到纯文本消息", + "unholdcall": "取消当前房间中的通话保持状态", + "unignore": "解除忽略用户,向你显示其未来的消息", + "unignore_dialog_description": "你不再忽略 %(userId)s", + "unignore_dialog_title": "已解除忽略的用户", + "unknown_command": "未知指令", "unknown_command_button": "作为消息发送", - "unknown_command_detail": "未识别的命令:%(commandText)s", - "unknown_command_help": "你可以使用 /help 列出可用命令。你是否要将其作为消息发送?", - "unknown_command_hint": "提示:以 // 开始你的消息来使其以一个斜杠开始。", + "unknown_command_detail": "无法识别的指令:%(commandText)s", + "unknown_command_help": "你可以使用 /help 列出可用的指令。是否将此文本作为消息发送?", + "unknown_command_hint": "提示:请以 / 开头。", "upgraderoom": "将房间升级到新版本", - "upgraderoom_permission_error": "你没有权限使用此命令。", + "upgraderoom_permission_error": "你无权使用此命令。", "usage": "用法", - "verify": "验证用户、会话和公钥元组", - "whois": "显示关于用户的信息" + "verify": "手动验证一个你拥有的设备", + "view": "通过指定的地址查看房间", + "whois": "显示用户信息" }, + "sliding_sync_legacy_no_longer_supported": "旧版滑动同步不再受支持:请重新登录以启用新的滑动同步标志", "space": { "add_existing_room_space": { - "create": "想要添加一个新的房间吗?", + "create": "想要添加新房间?", "create_prompt": "创建新房间", "dm_heading": "私聊", - "error_heading": "并非所有选中的都被添加", + "error_heading": "并非所有选定项都已添加", "progress_text": { - "one": "正在新增房间……", - "other": "正在新增房间……(%(count)s 中的第 %(progress)s 个)" + "one": "正在添加房间…", + "other": "正在添加房间…(%(count)s 中的 %(progress)s 个)" }, "space_dropdown_label": "空间选择", "space_dropdown_title": "添加现有房间", - "subspace_moved_note": "新增空间已移动。" + "subspace_moved_note": "添加的空间已移动。" }, "add_existing_subspace": { "create_button": "创建新空间", - "create_prompt": "想要添加一个新空间?", + "create_prompt": "添加一个新空间?", "filter_placeholder": "搜索空间", - "space_dropdown_title": "增加现有的空间" + "space_dropdown_title": "添加现有空间" }, "context_menu": { - "devtools_open_timeline": "查看房间时间线(开发工具)", - "explore": "探索房间", - "home": "空间首页", - "manage_and_explore": "管理并探索房间", + "devtools_open_timeline": "查看房间时间线(开发者工具)", + "explore": "浏览房间", + "home": "空间主页", + "manage_and_explore": "房间浏览与管理", "options": "空间选项" }, - "failed_load_rooms": "加载房间列表失败。", - "failed_remove_rooms": "无法移除某些房间。请稍后再试", - "incompatible_server_hierarchy": "你的服务器不支持显示空间层次结构。", - "invite": "邀请人们", - "invite_description": "使用邮箱或者用户名邀请", + "failed_load_rooms": "载入房间列表失败。", + "failed_remove_rooms": "某些房间移除失败。请稍后再试。", + "incompatible_server_hierarchy": "你的服务器不支持显示空间层级。", + "invite": "邀请人员", + "invite_description": "通过电话号码或邮件地址邀请", "invite_link": "分享邀请链接", - "joining_space": "加入中", + "joining_space": "正在加入", "landing_welcome": "欢迎来到 ", "leave_dialog_action": "离开空间", "leave_dialog_description": "你即将离开 。", - "leave_dialog_only_admin_room_warning": "你是某些要离开的房间或空间的唯一管理员。离开将使它们没有任何管理员。", - "leave_dialog_only_admin_warning": "你是此空间的唯一管理员。离开它将意味着没有人可以控制它。", + "leave_dialog_only_admin_room_warning": "对于你想要离开的某些房间或空间,你是其唯一的管理员。离开这些房间或空间将使其失去任何管理角色。", + "leave_dialog_only_admin_warning": "你是此空间的唯一管理员。离开后,任何人都无法控制它。", "leave_dialog_option_all": "离开所有房间", - "leave_dialog_option_intro": "你想俩开此空间内的房间吗?", - "leave_dialog_option_none": "不离开任何房间", - "leave_dialog_option_specific": "离开一些房间", - "leave_dialog_public_rejoin_warning": "除非你被重新邀请,否则你将无法重新加入。", + "leave_dialog_option_intro": "你确定要离开此空间中的房间?", + "leave_dialog_option_none": "不要离开任何房间", + "leave_dialog_option_specific": "离开房间", + "leave_dialog_public_rejoin_warning": "除非重新被邀请,否则你将无法重新加入。", "leave_dialog_title": "离开 %(spaceName)s", - "mark_suggested": "标记为建议", - "no_search_result_hint": "你可能要尝试其他搜索或检查是否有错别字。", + "mark_suggested": "设为建议", + "no_search_result_hint": "你可能需要尝试不同的搜索或检查错别字。", "preferences": { "sections_section": "要显示的部分", - "show_people_in_space": "将您与该空间的成员的聊天进行分组。关闭这个后你将无法在 %(spaceName)s 内看到这些聊天。" + "show_people_in_space": "这会将你的聊天与此空间的成员分组。关闭此项将在 %(spaceName)s 视图中隐藏这些聊天。" }, "room_filter_placeholder": "搜索房间", "search_children": "搜索 %(spaceName)s", - "search_placeholder": "搜索名称和描述", - "select_room_below": "首先选择一个房间", - "share_public": "分享你的公共空间", + "search_placeholder": "搜索名称与描述", + "select_room_below": "首选以下房间", + "share_public": "分享公共空间", "suggested": "建议", - "suggested_tooltip": "此房间很适合加入", + "suggested_tooltip": "建议加入此房间", "title_when_query_available": "结果", "title_when_query_unavailable": "房间与空间", - "unmark_suggested": "标记为不建议", + "unmark_suggested": "设为不建议", "user_lacks_permission": "你没有权限" }, "space_settings": { - "title": "设置 - %(spaceName)s" + "title": "设置:%(spaceName)s" }, "spaces": { - "error_no_permission_add_room": "你没有权限添加房间至此空间", - "error_no_permission_add_space": "你没有权限向此空间添加空间", - "error_no_permission_create_room": "你没有权限在此空间内创建新的房间", - "error_no_permission_invite": "你无权邀请他人加入此空间" + "error_no_permission_add_room": "你无权在此空间添加房间", + "error_no_permission_add_space": "你无权在此空间中添加空间", + "error_no_permission_create_room": "你无权在此空间创建新房间", + "error_no_permission_invite": "你无权邀请人员访问此空间" }, "spotlight": { "public_rooms": { - "network_dropdown_add_dialog_description": "输入你想探索的新服务器的服务器名。", - "network_dropdown_add_dialog_placeholder": "服务器名", - "network_dropdown_add_dialog_title": "添加新服务器", - "network_dropdown_add_server_option": "添加新的服务器…", - "network_dropdown_available_invalid": "找不到此服务器或其房间列表", + "network_dropdown_add_dialog_description": "输入要浏览的新服务器名称。", + "network_dropdown_add_dialog_placeholder": "服务器名称", + "network_dropdown_add_dialog_title": "添加新服务器…", + "network_dropdown_add_server_option": "添加新服务器…", + "network_dropdown_available_invalid": "无法找到此服务器或其房间列表", "network_dropdown_available_invalid_forbidden": "你不被允许查看此服务器的房间列表", - "network_dropdown_available_valid": "看着不错", + "network_dropdown_available_valid": "良好", "network_dropdown_remove_server_adornment": "移除服务器“%(roomServer)s”", - "network_dropdown_required_invalid": "请输入服务器名", - "network_dropdown_selected_label": "显示:Matrix房间", - "network_dropdown_selected_label_instance": "显示:%(instance)s房间(%(server)s)", + "network_dropdown_required_invalid": "输入服务器名称", + "network_dropdown_selected_label": "显示:Matrix 房间", + "network_dropdown_selected_label_instance": "显示 %(instance)s 房间(%(server)s)", "network_dropdown_your_server_description": "你的服务器" } }, "spotlight_dialog": { - "cant_find_person_helpful_hint": "若你无法看到你正在查找的人,给他们发送你的邀请链接。", - "cant_find_room_helpful_hint": "若你找不到要找的房间,请请求邀请或创建新房间。", + "cant_find_person_helpful_hint": "如果你看不到要找的人员,请复制并向其发送以下邀请链接。", + "cant_find_room_helpful_hint": "如果你找不到所需的房间,请向其申请加入或创建新房间。", "copy_link_text": "复制邀请链接", "count_of_members": { - "one": "%(count)s个成员", - "other": "%(count)s个成员" + "one": "%(count)s 位成员", + "other": "%(count)s 个成员" }, "create_new_room_button": "创建新房间", - "failed_querying_public_rooms": "查询公开房间失败", - "group_chat_section_title": "其他选项", - "heading_with_query": "使用 \"%(query)s\" 来搜索", - "heading_without_query": "搜索", - "join_button_text": "加入%(roomAddress)s", - "keyboard_scroll_hint": "用来滚动", - "other_rooms_in_space": "%(spaceName)s 中的其他房间", + "failed_querying_public_rooms": "公共房间查询失败", + "failed_querying_public_spaces": "公共空间查询失败", + "group_chat_section_title": "其它选项", + "heading_with_query": "使用“%(query)s”搜索", + "heading_without_query": "搜索类型", + "join_button_text": "加入 %(roomAddress)s", + "keyboard_scroll_hint": "使用 滚动", + "messages_label": "消息", + "other_rooms_in_space": "%(spaceName)s 中的其它房间", "public_rooms_label": "公共房间", "public_spaces_label": "公共空间", - "recent_searches_section_title": "最近的搜索", + "recent_searches_section_title": "最近搜索", "recently_viewed_section_title": "最近查看", - "remove_filter": "移除%(filter)s搜索过滤条件", - "result_may_be_hidden_privacy_warning": "为保护隐私,一些结果可能被隐藏", - "result_may_be_hidden_warning": "一些结果可能被隐藏", - "search_dialog": "搜索对话", + "remove_filter": "移除筛选 %(filter)s 的搜索", + "result_may_be_hidden_privacy_warning": "由于隐私原因,某些结果可能被隐藏。", + "result_may_be_hidden_warning": "某些结果可能被隐藏", + "search_dialog": "搜索对话框", "spaces_title": "你所在的空间", - "start_group_chat_button": "发起群聊天" + "start_group_chat_button": "开始群聊" }, "stickers": { - "empty": "你目前未启用任何贴纸包", + "empty": "你当前未启用任何贴纸包", "empty_add_prompt": "立即添加" }, "terms": { "column_document": "文档", "column_service": "服务", - "column_summary": "总结", - "identity_server_no_terms_description_1": "此操作需要访问默认的身份服务器 以验证邮箱或电话号码,但此服务器无任何服务条款。", - "identity_server_no_terms_description_2": "只有在你信任服务器所有者后才能继续。", - "identity_server_no_terms_title": "身份服务器无服务条款", + "column_summary": "摘要", + "identity_server_no_terms_description_1": "此操作需要访问默认身份服务器 以验证邮件地址或电话号码,但该服务器没有任何服务条款。", + "identity_server_no_terms_description_2": "只有在你信任服务器所有者的情况下才能继续。", + "identity_server_no_terms_title": "身份服务器暂无服务条款", "inline_intro_text": "接受 以继续:", - "integration_manager": "使用机器人、桥接、挂件和贴纸包", - "intro": "要继续,你需要接受此服务协议。", - "summary_identity_server_1": "通过电话或邮箱寻找别人", - "summary_identity_server_2": "通过电话或邮箱被寻找", + "integration_manager": "使用机器人、桥接器、小部件与贴纸包", + "intro": "若要继续,你必须接受此服务的条款。", + "summary_identity_server_1": "通过电话号码或邮件地址查找", + "summary_identity_server_2": "通过电话号码或邮件地址查找", "tac_button": "浏览条款与要求", - "tac_description": "若要继续使用家服务器 %(homeserverDomain)s,你必须浏览并同意我们的条款与要求。", - "tac_title": "条款与要求", - "tos": "服务协议" + "tac_description": "要继续使用主服务器 %(homeserverDomain)s,你必须查看并同意我们的条款和条件。", + "tac_title": "条款与条件", + "tos": "服务条款" }, "theme": { - "light_high_contrast": "浅色高对比", - "match_system": "匹配系统" + "light_high_contrast": "高对比度浅色", + "match_system": "跟随系统" }, - "thread_view_back_action_label": "返回消息列", + "thread_view_back_action_label": "回到消息列", "threads": { "all_threads": "所有消息列", "all_threads_description": "显示当前房间的所有消息列", "count_of_reply": { - "one": "%(count)s 条回复", - "other": "%(count)s 条回复" + "one": "%(count)s 个回复", + "other": "%(count)s 个回复" }, + "empty_description": "将鼠标指针悬停在某个消息上并点击“%(replyInThread)s”。", + "empty_title": "消息列有助于持续并跟进题内的对话。", + "mark_all_read": "全部设为已读", "my_threads": "我的消息列", - "my_threads_description": "显示您参与的所有消息列", + "my_threads_description": "显示你参与的所有消息列", "open_thread": "打开消息列", "show_thread_filter": "显示:" }, + "threads_activity_centre": { + "header": "消息列活动", + "no_rooms_with_threads_notifs": "暂无包含消息列通知的房间。", + "no_rooms_with_unread_threads": "暂无包含未读消息列的房间。" + }, "time": { "date_at_time": "%(date)s 的 %(time)s", - "hours_minutes_seconds_left": "剩余%(hours)s小时%(minutes)s分钟%(seconds)s秒", - "left": "剩余%(timeRemaining)s", - "minutes_seconds_left": "剩余%(minutes)s分钟%(seconds)s秒", - "seconds_left": "剩余 %(seconds)s 秒", + "hours_minutes_seconds_left": "已离开 %(hours)s 小时 %(minutes)s 分钟 %(seconds)s 秒", + "left": "剩余 %(timeRemaining)s", + "minutes_seconds_left": "已离开 %(minutes)s 分钟 %(seconds)ss 秒", + "seconds_left": "已离开 %(seconds)ss 秒", "short_days": "%(value)s 天", - "short_days_hours_minutes_seconds": "%(days)s天%(hours)s小时%(minutes)s分钟%(seconds)s秒", + "short_days_hours_minutes_seconds": "%(days)s 天 %(hours)s 小时 %(minutes)s 分钟 %(seconds)s 秒", "short_hours": "%(value)s 小时", - "short_hours_minutes_seconds": "%(hours)s小时%(minutes)s分钟%(seconds)s秒", + "short_hours_minutes_seconds": "%(hours)s 小时 %(minutes)s 分钟 %(seconds)ss 秒", "short_minutes": "%(value)s 分钟", - "short_minutes_seconds": "%(minutes)s分钟%(seconds)s秒", + "short_minutes_seconds": "%(minutes)s 分钟 %(seconds)s 秒", "short_seconds": "%(value)s 秒" }, "timeline": { "context_menu": { "collapse_reply_thread": "折叠回复消息列", - "external_url": "源网址", + "external_url": "源 URL", "open_in_osm": "在 OpenStreetMap 中打开", "report": "举报", - "resent_unsent_reactions": "重新发送%(unsentCount)s个反应", + "resent_unsent_reactions": "重新发送 %(unsentCount)s 的反应", "show_url_preview": "显示预览", "view_related_event": "查看相关事件", "view_source": "查看源代码" }, - "creation_summary_dm": "%(creator)s 创建了此私聊。", + "creation_summary_dm": "%(creator)s 创建了私聊。", "creation_summary_room": "%(creator)s 创建并配置了此房间。", - "download_action_downloading": "下载中", - "edits": { - "tooltip_label": "编辑于 %(date)s。点击以查看编辑历史。", - "tooltip_sub": "点击查看编辑历史", - "tooltip_title": "编辑于 %(date)s" + "decryption_failure": { + "sender_identity_previously_verified": "发件人已验证的身份已被重置", + "unable_to_decrypt": "无法解密消息" }, - "error_no_renderer": "无法显示此事件", + "disambiguated_profile": "%(displayName)s(%(matrixId)s)", + "download_action_downloading": "下载中", + "download_failed": "下载失败", + "download_failed_description": "下载此文件时出错", + "e2e_state": "端到端加密状态", + "edits": { + "tooltip_label": "编辑于 %(date)s,点击查看编辑历史。", + "tooltip_sub": "点击查看编辑历史", + "tooltip_title": "最后编辑于 %(date)s" + }, + "error_no_renderer": "此事件无法显示", "error_rendering_message": "无法加载此消息", - "historical_messages_unavailable": "你不能查看更早的消息", + "historical_messages_unavailable": "你无法查看之前的消息", + "in_room_name": "位于 %(room)s", "io.element.widgets.layout": "%(senderName)s 更新了房间布局", + "late_event_separator": "原定发送于 %(dateTime)s", "load_error": { - "no_permission": "尝试了加载此房间时间线上的特定点,但你没有查看相关消息的权限。", - "title": "加载时间线位置失败", - "unable_to_find": "尝试加载此房间的时间线的特定时间点,但是无法找到。" + "no_permission": "尝试加载此房间时间线中的特定时间点,但你无权查看相关消息。", + "title": "时间线位置加载失败", + "unable_to_find": "尝试加载此房间时间线中的特定时间点,但无法找到。" }, "m.audio": { "error_downloading_audio": "下载音频时出错", "error_processing_audio": "处理音频消息时出错", - "error_processing_voice_message": "处理语音消息时发生错误" + "error_processing_voice_message": "处理语音消息时出错" }, "m.beacon_info": { "view_live_location": "查看实时位置" }, "m.call": { - "video_call_started": "%(roomName)s里的视频通话开始了。", - "video_call_started_unsupported": "%(roomName)s里的视频通话开始了。(此浏览器不支持)" + "video_call_ended": "视频通话已结束", + "video_call_started": "在 %(roomName)s 开始视频通话。", + "video_call_started_text": "%(name)s 已开始视频通话", + "video_call_started_unsupported": "已在 %(roomName)s 开始视频通话。(此浏览器不支持)" }, "m.call.hangup": { - "dm": "通话结束" + "dm": "通话已结束" }, "m.call.invite": { + "answered_elsewhere": "已在别处接听", "call_back_prompt": "回拨", - "declined": "拒绝通话", + "declined": "来电被拒接", "failed_connect_media": "无法连接媒体", "failed_connection": "连接失败", - "failed_opponent_media": "他们的设备无法启动摄像头或麦克风", + "failed_opponent_media": "其设备无法开启摄像头或麦克风", "missed_call": "未接来电", - "no_answer": "无响应", - "unknown_error": "出现未知错误", + "no_answer": "未接听", + "unknown_error": "发生未知错误", "unknown_failure": "未知错误:%(reason)s", "unknown_state": "通话处于未知状态!", - "video_call": "%(senderName)s 发起了视频通话。", - "video_call_unsupported": "%(senderName)s 发起了视频通话。(此浏览器不支持)", - "voice_call": "%(senderName)s 发起了语音通话。", - "voice_call_unsupported": "%(senderName)s 发起了语音通话。(此浏览器不支持)" + "video_call": "来自 %(senderName)s 的视频通话。", + "video_call_unsupported": "%(senderName)s 已开始视频通话。(此浏览器不支持)", + "voice_call": "来自 %(senderName)s 的语音通话。", + "voice_call_unsupported": "%(senderName)s 已开始语音通话。(此浏览器不支持)" }, "m.file": { "error_decrypting": "解密附件时出错" }, "m.image": { + "error": "由于错误无法显示图片", "error_decrypting": "解密图像时出错", - "sent": "%(senderDisplayName)s 发送了一张图片。", + "error_downloading": "下载图像时出错", + "sent": "%(senderDisplayName)s 发送了一个图像。", "show_image": "显示图像" }, "m.key.verification.request": { "user_wants_to_verify": "%(name)s 想要验证", - "you_started": "你发送了一个验证请求" + "you_started": "你发送了验证请求" }, "m.location": { - "full": "%(senderName)s 分享了他们的位置", - "location": "分享了位置: ", - "self_location": "分享了他们的位置: " + "full": "%(senderName)s 分享了其位置", + "location": "分享位置:", + "self_location": "已分享其位置:" }, "m.poll": { "count_of_votes": { - "one": "%(count)s 票", - "other": "%(count)s 票" + "one": "%(count)s 个投票", + "other": "%(count)s 个投票" } }, "m.poll.end": { "sender_ended": "%(senderName)s 结束了投票" }, - "m.poll.start": "%(senderName)s 发起了投票:%(pollQuestion)s", + "m.poll.start": "%(senderName)s 发起投票: %(pollQuestion)s", "m.room.avatar": { - "changed": "%(senderDisplayName)s 更改了房间头像。", - "changed_img": "%(senderDisplayName)s 将房间的头像更改为 ", - "lightbox_title": "%(senderDisplayName)s 修改了 %(roomName)s 的头像", - "removed": "%(senderDisplayName)s 移除了房间头像。" + "changed": "%(senderDisplayName)s 已更改房间头像。", + "changed_img": "%(senderDisplayName)s 已将房间头像更改为 ", + "lightbox_title": "%(senderDisplayName)s 已更改房间 %(roomName)s 中的头像", + "removed": "%(senderDisplayName)s 已移除房间头像。" }, "m.room.canonical_alias": { "alt_added": { - "other": "%(senderName)s 为此房间添加备用地址 %(addresses)s。", - "one": "%(senderName)s 为此房间添加了备用地址 %(addresses)s。" + "one": "%(senderName)s 为此房间添加了备选地址 %(address)s。", + "other": "%(senderName)s 已为此房间添加了备用地址 %(addresses)s。" }, "alt_removed": { - "other": "%(senderName)s 为此房间移除了备用地址 %(addresses)s。", - "one": "%(senderName)s 为此房间移除了备用地址 %(addresses)s。" + "one": "%(senderName)s 已移除此房间的备选地址 %(address)s。", + "other": "%(senderName)s 已移除此房间的备选地址 %(addresses)s。" }, "changed": "%(senderName)s 更改了此房间的地址。", - "changed_alternative": "%(senderName)s 更改了此房间的备用地址。", - "changed_main_and_alternative": "%(senderName)s 更改了此房间的主要地址与备用地址。", - "removed": "%(senderName)s 移除了此房间的主要地址。", - "set": "%(senderName)s 将此房间的主要地址设为了 %(address)s。" + "changed_alternative": "%(senderName)s 更改了此房间的备选地址。", + "changed_main_and_alternative": "%(senderName)s 已更改此房间的主要地址与备用地址。", + "removed": "%(senderName)s 已移除此房间的主地址。", + "set": "%(senderName)s 已将此房间的主地址设置为 %(address)s。" }, "m.room.create": { - "continuation": "此房间是另一个对话的延续之处。", - "see_older_messages": "点击这里以查看更早的消息。" + "continuation": "此房间是另一个对话的延续。", + "see_older_messages": "点击此处查看更旧的消息。", + "unknown_predecessor": "找不到此房间的旧版本(房间 ID:%(roomId)s),并且我们未提供“via_servers”来查找它。", + "unknown_predecessor_guess_server": "无法找到此房间(房间 ID:%(roomId)s)的旧版本,并且尚未提供“via_servers”用于查找。根据房间 ID 猜测服务器或许可行。要尝试请点击此链接:" }, "m.room.guest_access": { - "can_join": "%(senderDisplayName)s 将此房间改为允许游客加入。", - "forbidden": "%(senderDisplayName)s 将此房间改为游客禁入。", - "unknown": "%(senderDisplayName)s 将此房间的游客加入规则改为 %(rule)s" + "can_join": "%(senderDisplayName)s 配置此房间为允许访客加入。", + "forbidden": "%(senderDisplayName)s 已阻止访客加入房间。", + "unknown": "%(senderDisplayName)s 已将访客的访问权限更改为 %(rule)s" }, "m.room.history_visibility": { - "invited": "%(senderName)s使未来的房间历史对所有房间成员从他们被邀请开始可见。", - "joined": "%(senderName)s使未来的房间历史对所有房间成员从他们加入开始可见。", - "shared": "%(senderName)s使未来的房间历史对所有房间成员可见。", - "unknown": "%(senderName)s使未来的房间历史对未知(%(visibility)s)可见。", - "world_readable": "%(senderName)s使未来的房间历史对任何人可见。" + "invited": "%(senderName)s 为此房间的所有成员调整了未来房间历史的可见性(自新成员被邀请进入时起)。", + "joined": "%(senderName)s 已将未来的房间历史记录对所有成员可见,自其加入房间时起。", + "shared": "%(senderName)s 使所有房间成员都能看到未来的房间历史记录。", + "unknown": "%(senderName)s 已将未来的房间历史记录对未知用户可见(%(visibility)s)。", + "world_readable": "%(senderName)s 调整了未来房间历史的可见性(对任何人可见)。" }, "m.room.join_rules": { - "invite": "%(senderDisplayName)s 将此房间改为仅限邀请。", - "knock": "%(senderDisplayName)s 将加入规则更改为 ”需要验证加入请求“。", - "public": "%(senderDisplayName)s 将此房间对知道此房间链接的人公开。", - "restricted": "%(senderDisplayName)s 更改了谁能加入这个房间。", - "restricted_settings": "%(senderDisplayName)s 更改了谁能加入这个房间。查看设置。", - "unknown": "%(senderDisplayName)s 将加入规则改为 %(rule)s" + "invite": "%(senderDisplayName)s 配置此房间为仅限邀请。", + "knock": "%(senderDisplayName)s 更改了加入规则以申请加入。", + "public": "%(senderDisplayName)s 向知道链接的人公开了房间。", + "restricted": "%(senderDisplayName)s 更改了允许加入此房间的人员。", + "restricted_settings": "%(senderDisplayName)s 已更改谁可以加入此房间。查看设置", + "unknown": "%(senderDisplayName)s 已将加入规则更改为 %(rule)s" }, "m.room.member": { - "accepted_3pid_invite": "%(targetName)s 已接受 %(displayName)s 的邀请", + "accepted_3pid_invite": "%(targetName)s 接受了 %(displayName)s 的邀请", "accepted_invite": "%(targetName)s 已接受邀请", - "ban": "%(senderName)s 已封禁 %(targetName)s", - "ban_reason": "%(senderName)s 已封禁 %(targetName)s: %(reason)s", - "change_avatar": "%(senderName)s 已更改他们的资料图片", - "change_name": "%(oldDisplayName)s将其显示名称改为%(displayName)s", - "change_name_avatar": "%(oldDisplayName)s更改了其显示名称和用户资料图片", - "invite": "%(senderName)s 已邀请 %(targetName)s", + "ban": "%(senderName)s 已封禁 1 个用户", + "ban_reason": "%(senderName)s 已封禁 1 个用户: %(reason)s", + "ban_reason_spoiler": "%(senderName)s 已封禁 ", + "ban_spoiler": "%(senderName)s 已封禁 : %(reason)s", + "change_avatar": "%(senderName)s 已更改其个人资料图像", + "change_name": "%(oldDisplayName)s 已将其显示名称更改为 %(displayName)s", + "change_name_avatar": "%(oldDisplayName)s 已更改其显示名称与个人资料图片", + "invite": "%(senderName)s 邀请了 %(targetName)s", "join": "%(targetName)s 已加入房间", - "kick": "%(senderName)s 移除了 %(targetName)s", - "kick_reason": "%(senderName)s 移除了 %(targetName)s:%(reason)s", - "left": "%(targetName)s 已离开房间", - "left_reason": "%(targetName)s 已离开房间:%(reason)s", - "no_change": "%(senderName)s 未发生更改", + "kick": "%(senderName)s 已移除 %(targetName)s", + "kick_reason": "%(senderName)s 已移除 %(targetName)s: %(reason)s", + "left": "%(targetName)s 离开了房间", + "left_reason": "%(targetName)s 离开了房间:%(reason)s", + "no_change": "%(senderName)s 未产生任何更改", "reject_invite": "%(targetName)s 已拒绝邀请", - "remove_avatar": "%(senderName)s 已移除他们的资料图片", - "remove_name": "%(senderName)s已移除他们的显示名称(%(oldDisplayName)s)", - "set_avatar": "%(senderName)s 已设置资料图片", - "set_name": "%(senderName)s已将他们的显示名称设置为%(displayName)s", - "unban": "%(senderName)s 已取消封禁 %(targetName)s", - "withdrew_invite": "%(senderName)s 已撤回向 %(targetName)s 的邀请", - "withdrew_invite_reason": "%(senderName)s 已撤回向 %(targetName)s 的邀请:%(reason)s" + "reject_invite_reason": "%(targetName)s 拒绝了邀请:%(reason)s", + "remove_avatar": "%(senderName)s 已移除其个人资料图像", + "remove_name": "%(senderName)s 已移除其显示名称(%(oldDisplayName)s)", + "set_avatar": "%(senderName)s 设置了个人资料图像", + "set_name": "%(senderName)s 已设置其显示名称为 %(displayName)s", + "unban": "%(senderName)s 解封了 %(targetName)s", + "withdrew_invite": "%(senderName)s 撤消了 %(targetName)s 的邀请", + "withdrew_invite_reason": "%(senderName)s 已撤消对 %(targetName)s 的邀请:%(reason)s" }, "m.room.name": { - "change": "%(senderDisplayName)s 将房间名称从 %(oldRoomName)s 改为 %(newRoomName)s。", - "remove": "%(senderDisplayName)s 移除了房间名称。", - "set": "%(senderDisplayName)s 将房间名称改为 %(roomName)s。" + "change": "%(senderDisplayName)s 已更改房间名称从 %(oldRoomName)s 到 %(newRoomName)s。", + "remove": "%(senderDisplayName)s 已移除房间名称。", + "set": "%(senderDisplayName)s 更改房间名称为 %(roomName)s。" }, "m.room.pinned_events": { - "changed": "%(senderName)s 更改了房间的置顶消息。", - "changed_link": "%(senderName)s 已更改此房间的固定消息。", - "pinned": "%(senderName)s将一条消息固定到此房间。查看所有固定消息。", - "pinned_link": "%(senderName)s 将一条消息固定到此房间。查看所有固定消息。", - "unpinned": "%(senderName)s从此房间中取消固定了一条消息。查看所有固定消息。", - "unpinned_link": "%(senderName)s 从此房间中取消固定了一条消息。查看所有固定消息。" + "changed": "%(senderName)s 已更改此房间已置顶的消息。", + "changed_link": "%(senderName)s 更改了房间的已置顶消息。", + "pinned": "%(senderName)s 将一个消息置顶到此房间。查看所有已置顶的消息。", + "pinned_link": "%(senderName)s 已在此房间置顶了一个消息。 查看所有已置顶的消息。", + "unpinned": "%(senderName)s 已在此房间取消置顶一个消息。查看所有已置顶的消息。", + "unpinned_link": "%(senderName)s 已在此房间取消置顶了一个消息。 查看所有已置顶的消息。" }, "m.room.power_levels": { - "changed": "%(senderName)s更改了%(powerLevelDiffText)s的权力级别。", - "user_from_to": "%(userId)s 从 %(fromPowerLevel)s 变为 %(toPowerLevel)s" + "changed": "%(senderName)s 已更改 %(powerLevelDiffText)s。", + "user_from_to": "%(userId)s 的权力值从 %(fromPowerLevel)s 到 %(toPowerLevel)s" }, "m.room.third_party_invite": { - "revoked": "%(senderName)s 撤销了对 %(targetDisplayName)s 加入房间的邀请。", - "sent": "%(senderName)s 向 %(targetDisplayName)s 发了加入房间的邀请。" + "revoked": "%(senderName)s 已撤消 %(targetDisplayName)s 加入房间的邀请。", + "sent": "%(senderName)s 已向 %(targetDisplayName)s 发送了加入房间的邀请。" }, - "m.room.tombstone": "%(senderDisplayName)s 升级了此房间。", + "m.room.tombstone": "%(senderDisplayName)s 已升级此房间。", "m.room.topic": { - "changed": "%(senderDisplayName)s 将话题修改为 “%(topic)s”。" + "changed": "%(senderDisplayName)s 已更改主题为“%(topic)s”。", + "removed": "%(senderDisplayName)s 已移除房间主题。" }, - "m.sticker": "%(senderDisplayName)s 发送了一张贴纸。", + "m.sticker": "%(senderDisplayName)s 发送了一个贴纸。", "m.video": { - "error_decrypting": "解密视频时出错" + "error_decrypting": "解密视频时出错", + "show_video": "显示视频" }, "m.widget": { - "added": "%(senderName)s 添加了 %(widgetName)s 挂件", - "jitsi_ended": "由 %(senderName)s 结束的视频会议", + "added": "%(senderName)s 添加了小部件:%(widgetName)s", + "jitsi_ended": "%(senderName)s 结束了视频会议", "jitsi_join_right_prompt": "从右侧的房间信息卡片加入会议", - "jitsi_join_top_prompt": "点击房间顶部加入会议", - "jitsi_started": "由 %(senderName)s 发起的视频会议", - "jitsi_updated": "由 %(senderName)s 更新的视频会议", - "modified": "%(senderName)s 修改了 %(widgetName)s 挂件", - "removed": "%(senderName)s 移除了 %(widgetName)s 挂件" + "jitsi_join_top_prompt": "在此房间加入会议的前排", + "jitsi_started": "%(senderName)s 已开始视频会议", + "jitsi_updated": "%(senderName)s 已更新视频会议", + "modified": "小部件 %(widgetName)s 已被 %(senderName)s 修改", + "removed": "%(senderName)s 已移除小部件:%(widgetName)s" }, "mab": { "copy_link_thread": "复制到消息列的链接", "view_in_room": "在房间内查看" }, "mjolnir": { - "changed_rule_glob": "%(senderName)s 更新了一个由于%(reason)s而禁止%(oldGlob)s跟%(newGlob)s匹配的规则", - "changed_rule_rooms": "%(senderName)s更改了一个由于%(reason)s而禁止房间%(oldGlob)s跟%(newGlob)s匹配的规则", - "changed_rule_servers": "%(senderName)s 更新了一个由于%(reason)s而禁止服务器%(oldGlob)s跟%(newGlob)s匹配的规则", - "changed_rule_users": "%(senderName)s 更改了一个由于%(reason)s而禁止用户%(oldGlob)s跟%(newGlob)s匹配的规则", - "created_rule": "%(senderName)s 创建了由于%(reason)s而禁止匹配%(glob)s的规则", - "created_rule_rooms": "%(senderName)s 创建了由于%(reason)s而禁止房间匹配%(glob)s的规则", - "created_rule_servers": "%(senderName)s 创建了由于%(reason)s而禁止服务器匹配%(glob)s的规则", - "created_rule_users": "%(senderName)s 创建了因为%(reason)s而禁止用户匹配%(glob)s的规则", - "message_hidden": "你已忽略此用户,所以其消息已被隐藏。仍然显示。", - "removed_rule": "%(senderName)s 移除了禁止匹配 %(glob)s 的规则", - "removed_rule_rooms": "%(senderName)s 删除了禁止房间匹配%(glob)s的规则", - "removed_rule_servers": "%(senderName)s 移除了禁止匹配 %(glob)s 的服务器的规则", - "removed_rule_users": "%(senderName)s 移除了禁止匹配 %(glob)s 的用户的规则", - "updated_invalid_rule": "%(senderName)s 更新了一个无效的禁止规则", - "updated_rule": "%(senderName)s 更新了由于%(reason)s而禁止匹配%(glob)s的规则", - "updated_rule_rooms": "%(senderName)s 更新了由于%(reason)s而禁止房间匹配%(glob)s的规则", - "updated_rule_servers": "%(senderName)s 更新了由于%(reason)s而禁止服务器匹配%(glob)s的规则", - "updated_rule_users": "%(senderName)s 更新了由于%(reason)s 而禁止用户匹配%(glob)s的规则" + "changed_rule_glob": "%(senderName)s 已更新一条与 %(oldGlob)s 匹配的封禁规则,曾经为 %(newGlob)s,原因:%(reason)s", + "changed_rule_rooms": "%(senderName)s 已更改一条与 %(newGlob)s 匹配的房间封禁规则,曾经为 %(oldGlob)s,原因:%(reason)s", + "changed_rule_servers": "%(senderName)s 已更改一条与 %(newGlob)s 匹配的服务器封禁规则,曾经为 %(oldGlob)s,原因:%(reason)s", + "changed_rule_users": "%(senderName)s 已更改一条与 %(newGlob)s 匹配的用户封禁规则,曾经为 %(oldGlob)s,原因:%(reason)s", + "created_rule": "%(senderName)s 为 %(reason)s 创建了一条与 %(glob)s 匹配的封禁规则", + "created_rule_rooms": "%(senderName)s 已创建一条与 %(glob)s 匹配的房间封禁规则,原因:%(reason)s", + "created_rule_servers": "%(senderName)s 已创建一条与 %(glob)s 匹配的服务器封禁规则,原因:%(reason)s", + "created_rule_users": "%(senderName)s 已创建一条与 %(glob)s 匹配的用户封禁规则,原因:%(reason)s", + "message_hidden": "你已忽略此用户,因此其消息已被隐藏。仍然显示。", + "removed_rule": "%(senderName)s 已移除一条与 %(glob)s 匹配的封禁规则", + "removed_rule_rooms": "%(senderName)s 已移除匹配 %(glob)s 的房间封禁规则。", + "removed_rule_servers": "%(senderName)s 已移除与 %(glob)s 匹配的服务器封禁规则", + "removed_rule_users": "%(senderName)s 已移除匹配 %(glob)s 的用户封禁规则。", + "updated_invalid_rule": "%(senderName)s 更新了无效的封禁规则", + "updated_rule": "%(senderName)s 已更新一条与 %(glob)s 匹配的封禁规则", + "updated_rule_rooms": "%(senderName)s 已更新一条与 %(glob)s 匹配的房间封禁规则,原因:%(reason)s", + "updated_rule_servers": "%(senderName)s 已更新与 %(glob)s 匹配的服务器封禁规则,原因:%(reason)s。", + "updated_rule_users": "%(senderName)s 已更新一条与 %(glob)s 匹配的用户封禁规则,原因:%(reason)s" }, - "no_permission_messages_before_invite": "你没有权限查看你被邀请之前的消息。", - "no_permission_messages_before_join": "你没有权限查看你加入前的消息。", - "pending_moderation": "待审核的消息", - "pending_moderation_reason": "消息待审核:%(reason)s", + "no_permission_messages_before_invite": "你无权查看你被邀请前的消息。", + "no_permission_messages_before_join": "你无权查看你加入前的消息。", + "pending_moderation": "消息等待审核", + "pending_moderation_reason": "消息等待审核:%(reason)s", "reactions": { "add_reaction_prompt": "添加反应", - "label": "%(reactors)s做出了%(content)s的反应" + "custom_reaction_fallback_label": "自定义反应", + "label": "%(reactors)s 使用 %(content)s 作出反应", + "tooltip_caption": "已使用 %(shortName)s 作出反应" }, "read_receipt_title": { - "one": "已被%(count)s人查看", - "other": "已被%(count)s人查看" + "one": "已被 %(count)s 个人查看", + "other": "已被 %(count)s 个人查看" }, "read_receipts_label": "已读回执", "redacted": { - "tooltip": "消息于 %(date)s 被删除" + "tooltip": "消息已于 %(date)s 删除" }, - "redaction": "消息被 %(name)s 删除", + "redaction": "消息已被 %(name)s 删除", "reply": { - "error_loading": "无法加载被回复的事件,它可能不存在,也可能是你没有权限查看它。", - "in_reply_to": "答复 ", - "in_reply_to_for_export": "答复此消息" + "error_loading": "无法加载已回复的事件,该事件可能不存在或你无权查看。", + "in_reply_to": "回复给 ", + "in_reply_to_for_export": "回复此消息" }, "scalar_starter_link": { - "dialog_description": "你将被带到一个第三方网站以便验证你的账户来使用 %(integrationsUrl)s 提供的集成。你希望继续吗?", + "dialog_description": "你将被带到第三方网站,以便验证你的账户是否可用于 %(integrationsUrl)s。是否继续?", "dialog_title": "添加集成" }, - "self_redaction": "消息已删除", + "self_redaction": "消息已被删除", + "send_state_encrypting": "正在解密消息…", "send_state_failed": "发送失败", + "send_state_sending": "发送消息…", "send_state_sent": "消息已发送", "summary": { "banned": { - "other": "被封禁 %(count)s 次", - "one": "被封禁" + "one": "已被封禁", + "other": "已被封禁 %(count)s 次" }, "banned_multiple": { - "other": "被封禁 %(count)s 次", - "one": "被封禁" + "one": "已被封禁", + "other": "已被封禁 %(count)s 次" + }, + "changed_avatar": { + "one": "%(oneUser)s 已更改其个人资料图像", + "other": "%(oneUser)s 已更改其个人资料图像 %(count)s 次" + }, + "changed_avatar_multiple": { + "one": "%(severalUsers)s 已更改其个人资料图像", + "other": "%(severalUsers)s 已更改其个人资料图像 %(count)s 次" }, "changed_name": { - "other": "%(oneUser)s 修改了自己的名称 %(count)s 次", - "one": "%(oneUser)s 修改了自己的名称" + "one": "%(oneUser)s 已更改其名称", + "other": "%(oneUser)s 已更改其名称 %(count)s 次" }, "changed_name_multiple": { - "other": "%(severalUsers)s 修改了他们的名称 %(count)s 次", - "one": "%(severalUsers)s 修改了他们的名称" + "one": "%(severalUsers)s 已更改其名称", + "other": "%(severalUsers)s 更改了其名称 %(count)s 次" }, + "format": "%(nameList)s %(transitionList)s", "hidden_event": { - "other": "%(oneUser)s发送了%(count)s条隐藏消息", - "one": "%(oneUser)s发送了一条隐藏消息" + "one": "%(oneUser)s 发送了一个隐藏消息", + "other": "%(oneUser)s 发送了 %(count)s 个隐藏消息" }, "hidden_event_multiple": { - "one": "%(severalUsers)s发送了一条隐藏消息", - "other": "%(severalUsers)s发送了%(count)s条隐藏消息" + "one": "%(severalUsers)s 发送了一个隐藏事件", + "other": "%(severalUsers)s 发送了 %(count)s 个隐藏消息" }, "invite_withdrawn": { - "other": "%(oneUser)s 撤回了他们的邀请共 %(count)s 次", - "one": "%(oneUser)s 撤回了他们的邀请" + "one": "%(oneUser)s 已撤消其邀请", + "other": "%(oneUser)s 已撤消其邀请 %(count)s 次" }, "invite_withdrawn_multiple": { - "other": "%(severalUsers)s 撤回了他们的邀请共 %(count)s 次", - "one": "%(severalUsers)s 撤回了他们的邀请" + "one": "已撤消 %(severalUsers)s 的邀请", + "other": "%(severalUsers)s 已撤消其邀请 %(count)s 次" }, "invited": { - "other": "被邀请 %(count)s 次", - "one": "被邀请" + "one": "被邀请", + "other": "已被邀请 %(count)s 次" }, "invited_multiple": { - "other": "被邀请 %(count)s 次", - "one": "被邀请" + "one": "已被邀请", + "other": "被邀请 %(count)s 次" }, "joined": { - "other": "%(oneUser)s 已加入 %(count)s 次", - "one": "%(oneUser)s 已加入" + "one": "%(oneUser)s 已加入", + "other": "%(oneUser)s 加入了 %(count)s 次" }, "joined_and_left": { - "other": "%(oneUser)s加入并离开了%(count)s次", - "one": "%(oneUser)s加入并离开了" + "one": "%(oneUser)s 已加入并离开", + "other": "%(oneUser)s 加入并离开了 %(count)s 次" }, "joined_and_left_multiple": { - "other": "%(severalUsers)s加入并离开了%(count)s次", - "one": "%(severalUsers)s加入并离开了" + "one": "%(severalUsers)s 加入并离开", + "other": "%(severalUsers)s 加入并离开 %(count)s 次" }, "joined_multiple": { - "other": "%(severalUsers)s 已加入 %(count)s 次", - "one": "%(severalUsers)s 已加入" + "one": "%(severalUsers)s 已加入", + "other": "%(severalUsers)s 加入了 %(count)s 次" }, "kicked": { "one": "被移除", - "other": "被移除%(count)s次" + "other": "已被移除 %(count)s 次" }, "kicked_multiple": { - "one": "被移除", - "other": "被移除了%(count)s次" + "one": "已被移除", + "other": "被移除 %(count)s 次" }, "left": { - "other": "%(oneUser)s 已离开 %(count)s 次", - "one": "%(oneUser)s 已离开" + "one": "%(oneUser)s 已离开", + "other": "%(oneUser)s 离开 %(count)s 次" }, "left_multiple": { - "other": "%(severalUsers)s 已离开 %(count)s 次", - "one": "%(severalUsers)s 已离开" + "one": "%(severalUsers)s 已离开", + "other": "%(severalUsers)s 离开 %(count)s 次" }, "no_change": { - "other": "%(oneUser)s 未做更改 %(count)s 次", - "one": "%(oneUser)s 未做更改" + "one": "%(oneUser)s 未产生任何更改", + "other": "%(oneUser)s未产生任何更改 %(count)s 次" }, "no_change_multiple": { - "other": "%(severalUsers)s 未做更改 %(count)s 次", - "one": "%(severalUsers)s 未做更改" + "one": "%(severalUsers)s 未产生任何更改", + "other": "%(severalUsers)s 未产生任何更改 %(count)s 次" }, "pinned_events": { - "one": "%(oneUser)s更改了房间的固定消息", - "other": "%(oneUser)s更改了房间的固定消息%(count)s次" + "one": "%(oneUser)s 更改了此房间的已置顶消息", + "other": "%(oneUser)s 已更改房间内的 已置顶消息 %(count)s 次" }, "pinned_events_multiple": { - "one": "%(severalUsers)s更改了房间的固定消息", - "other": "%(severalUsers)s更改了房间的固定消息%(count)s次" + "one": "%(severalUsers)s 已更改房间的 已置顶消息", + "other": "%(severalUsers) 已更改该房间的已置顶消息 %(count)s 次。" }, "redacted": { - "one": "%(oneUser)s移除了一条消息", - "other": "%(oneUser)s移除了%(count)s条消息" + "one": "%(oneUser)s 已移除一个消息", + "other": "%(oneUser)s 已移除 %(count)s 个消息" }, "redacted_multiple": { - "one": "%(severalUsers)s移除了1条消息", - "other": "%(severalUsers)s移除了%(count)s条消息" + "one": "%(severalUsers)s 已移除一个消息", + "other": "%(severalUsers)s 已移除 %(count)s 个消息" }, "rejected_invite": { - "other": "%(oneUser)s 拒绝了他们的邀请共 %(count)s 次", - "one": "%(oneUser)s 拒绝了他们的邀请" + "one": "%(oneUser)s 已拒绝其邀请", + "other": "%(oneUser)s 已拒绝其邀请 %(count)s 次" }, "rejected_invite_multiple": { - "one": "%(severalUsers)s 拒绝了他们的邀请", - "other": "%(severalUsers)s 拒绝了他们的邀请共 %(count)s 次" + "one": "%(severalUsers)s 已拒绝其邀请", + "other": "%(severalUsers)s 已拒绝其邀请 %(count)s 次" }, "rejoined": { - "other": "%(oneUser)s离开并重新加入了%(count)s次", - "one": "%(oneUser)s离开并重新加入了" + "one": "%(oneUser)s 离开并重新加入", + "other": "%(oneUser)s 离开并重新加入了 %(count)s 次" }, "rejoined_multiple": { - "other": "%(severalUsers)s离开并重新加入了%(count)s次", - "one": "%(severalUsers)s离开并重新加入了" + "one": "%(severalUsers)s 离开并重新加入", + "other": "%(severalUsers)s 离开并重新加入了 %(count)s 次" }, "unbanned": { - "other": "被解封 %(count)s 次", - "one": "被解封" + "one": "已被解封", + "other": "被解封 %(count)s 次" }, "unbanned_multiple": { "other": "被解封 %(count)s 次", @@ -2883,274 +3735,323 @@ "thread_info_basic": "来自消息列", "typing_indicator": { "more_users": { - "other": "%(names)s 与其他 %(count)s 位正在输入…", - "one": "%(names)s 与另一位正在输入…" + "one": "%(name)s 与剩余 1 个用户正在输入...", + "other": "%(names)s 与其他 %(count)s 个人正在输入…" }, "one_user": "%(displayName)s 正在输入…", - "two_users": "%(names)s和%(lastPerson)s正在输入……" - } + "two_users": "%(names)s 与其他 %(lastPerson)s 个人正在输入…" + }, + "undecryptable_tooltip": "此消息无法解密" }, "truncated_list_n_more": { - "other": "和 %(count)s 个其他…" + "other": "以及剩余 %(count)s 个…" }, + "unsupported_browser": { + "description": "如果继续,某些功能可能会停止运行,并且你将来可能会丢失数据。请更新浏览器以继续使用 %(brand)s。", + "title": "%(brand)s 不支持此浏览器" + }, + "unsupported_server_description": "此服务器正在使用旧版本的 Matrix。请升级到 Matrix %(version)s 以正常使用 %(brand)s。", + "unsupported_server_title": "服务器不支持", "update": { "changelog": "更改日志", "check_action": "检查更新", - "error_encountered": "遇到错误 (%(errorDetail)s)。", - "error_unable_load_commit": "无法加载提交详情:%(msg)s", - "new_version_available": "新版本可用。现在更新。", - "no_update": "没有可用更新。", - "release_notes_toast_title": "更新内容", - "see_changes_button": "有何新变动?", - "toast_description": "%(brand)s 有新版本可用", + "checking": "正在检查更新…", + "downloading": "正在下载更新…", + "error_encountered": "遇到错误(%(errorDetail)s)。", + "error_unable_load_commit": "无法载入提交的详细信息: %(msg)s", + "new_version_available": "有新版本可用。立即更新", + "no_update": "无可用更新。", + "release_notes_toast_title": "新特性", + "see_changes_button": "新增内容?", + "toast_description": "%(brand)s 有新版本可供更新", "toast_title": "更新 %(brand)s", - "unavailable": "无法获得" + "unavailable": "不可用" }, - "upload_failed_generic": "文件《%(fileName)s》上传失败。", - "upload_failed_size": "文件“%(fileName)s”超过了此家服务器的上传大小限制", + "update_room_access_modal": { + "description": "要创建共享链接,请将此房间设为公开,或启用用户申请加入的选项。这样,访客无需邀请即可加入。", + "dont_change_description": "如果你不想更改此房间的访问权限,你可以为通话链接创建一个新的房间。", + "no_change": "我不想更改访问权力。", + "revert_access_description": "(可在“房间设置”中将其恢复为先前的值:安全与隐私/访问)", + "title": "允许访客加入此房间" + }, + "upload_failed_generic": "文件“%(fileName)s”上传失败。", + "upload_failed_size": "文件“%(fileName)s”超出了此主服务器的上传大小限制", "upload_failed_title": "上传失败", "upload_file": { "cancel_all_button": "全部取消", - "error_file_too_large": "此文件过大而不能上传。文件大小限制是 %(limit)s 但此文件为 %(sizeOfThisFile)s。", - "error_files_too_large": "这些文件过大而不能上传。文件大小限制为 %(limit)s。", - "error_some_files_too_large": "一些文件过大而不能上传。文件大小限制为 %(limit)s。", - "error_title": "上传错误", + "error_file_too_large": "此文件太大,无法上传。文件大小限制为 %(limit)s,但此文件为 %(sizeOfThisFile)s。", + "error_files_too_large": "这些文件太大,无法上传。文件大小限制为 %(limit)s。", + "error_some_files_too_large": "某些文件过大,无法上传。文件大小限制为 %(limit)s。", + "error_title": "上传出错", + "not_image": "你选择的文件不是有效的图像文件。", "title": "上传文件", - "title_progress": "上传文件(%(total)s 中之 %(current)s)", + "title_progress": "上传文件(%(total)s 个中的第 %(current)s 个)", "upload_all_button": "全部上传", "upload_n_others_button": { - "other": "上传 %(count)s 个别的文件", - "one": "上传 %(count)s 个别的文件" + "one": "上传剩余 %(count)s 个文件", + "other": "上传剩余 %(count)s 个文件" } }, "user_info": { "admin_tools_section": "管理员工具", "ban_button_room": "从房间封禁", "ban_button_space": "从空间封禁", - "ban_room_confirm_title": "禁止进入 %(roomName)s", - "ban_space_everything": "禁止这些人做任何我有权决定的事", + "ban_room_confirm_title": "在 %(roomName)s 中封禁", + "ban_space_everything": "禁止他们访问我所能访问的一切", "ban_space_specific": "禁止这些人做某些我有权决定的事", "deactivate_confirm_action": "停用用户", - "deactivate_confirm_description": "停用此用户将会使其登出并阻止其再次登入。而且此用户也会离开其所在的所有房间。此操作不可逆。你确定要停用此用户吗?", - "deactivate_confirm_title": "停用用户吗?", + "deactivate_confirm_description": "停用此用户将移除其设备,并且无法重新登录。同时将离开其所在的所有房间。此操作无法撤消。你确定要停用此用户?", + "deactivate_confirm_title": "停用用户?", "demote_button": "降权", - "demote_self_confirm_description_space": "当你将自己降级后,你将无法撤销此更改。如果你是此空间的最后一名拥有权限的用户,则无法重新获得权限。", - "demote_self_confirm_room": "如果你是房间中最后一位拥有权限的用户,在你降低自己的权限等级后将无法撤销此修改,你将无法重新获得权限。", - "demote_self_confirm_title": "是否降低你自己的权限?", - "disinvite_button_room": "从房间取消邀请", - "disinvite_button_room_name": "取消邀请加入 %(roomName)s", - "disinvite_button_space": "从空间取消邀请", - "error_ban_user": "封禁失败", - "error_deactivate": "停用用户失败", - "error_kicking_user": "移除用户失败", - "error_mute_user": "禁言用户失败", - "error_revoke_3pid_invite_description": "无法撤销邀请。此服务器可能出现了临时错误,或者你没有足够的权限来撤销邀请。", - "error_revoke_3pid_invite_title": "撤销邀请失败", - "invited_by": "被 %(sender)s 邀请", - "jump_to_rr_button": "跳到阅读回执", + "demote_self_confirm_description_space": "由于你正在降级自身,因此无法撤消此更改。如果你是房间中最后一个拥有特权的用户,则无法重新获得特权。", + "demote_self_confirm_room": "由于你正在降级自身,因此无法撤消此更改。如果你是房间中最后一个拥有特权的用户,则无法重新获得特权。", + "demote_self_confirm_title": "降级自身?", + "disinvite_button_room": "撤消邀请", + "disinvite_button_room_name": "从 %(roomName)s 取消邀请", + "disinvite_button_space": "取消邀请", + "error_ban_user": "封禁用户失败", + "error_deactivate": "用户停用失败", + "error_kicking_user": "用户移除失败", + "error_mute_user": "静默用户失败", + "error_revoke_3pid_invite_description": "无法撤消邀请。服务器可能出现临时问题,或者你没有足够的权限撤消邀请。", + "error_revoke_3pid_invite_title": "邀请撤消失败", + "ignore_button": "忽略", + "ignore_confirm_description": "来自此用户的所有消息与邀请都将被隐藏。你确定要忽略?", + "ignore_confirm_title": "忽略 %(user)s", + "invited_by": "由 %(sender)s 邀请", + "jump_to_rr_button": "跳转到已读回执", "kick_button_room": "从房间移除", - "kick_button_room_name": "从%(roomName)s移除", + "kick_button_room_name": "从 %(roomName)s 移除", "kick_button_space": "从空间移除", - "kick_space_warning": "他们仍然可以访问任何你不是管理员的地方。", - "promote_warning": "你将无法撤回此修改,因为此用户的权力级别将与你相同。", + "kick_button_space_everything": "从我所能及的一切中将它们移除", + "kick_space_specific": "将它们从我能够处理的特定事物中移除", + "kick_space_warning": "他们仍然可以访问任何你不是管理员的空间。", + "promote_warning": "由于你正在将该用户提升为与你相同的权力值,因此你将无法撤消此更改。", "redact": { "confirm_button": { - "other": "删除 %(count)s 条消息", - "one": "删除 1 条消息" + "one": "移除 1 个消息", + "other": "移除 %(count)s 个消息" }, - "confirm_description_2": "对于大量消息,可能会消耗一段时间。在此期间请不要刷新你的客户端。", - "confirm_keep_state_explainer": "若你也想移除关于此用户的系统消息(例如,成员更改、用户资料更改……),则取消勾选", + "confirm_description_1": { + "one": "即将移除 %(user)s 发送的 %(count)s 个消息。这将永久删除对话中所有参与者的消息。是否继续?", + "other": "即将移除 %(user)s 发送的 %(count)s 个消息。这将永久删除对话中所有参与者的消息。是否继续?" + }, + "confirm_description_2": "如果消息量较大,这可能需要一些时间。在此期间请勿刷新客户端。", + "confirm_keep_state_explainer": "如果你还想移除此用户的系统消息(例如,成员关系变更、个人资料变更等),请取消选中。", "confirm_keep_state_label": "保留系统消息", - "confirm_title": "删除 %(user)s 最近发送的消息", - "no_recent_messages_description": "请尝试在时间线中向上滚动以查看是否有更早的。", - "no_recent_messages_title": "没有找到 %(user)s 最近发送的消息" + "confirm_title": "移除 %(user)s 最近的信息", + "no_recent_messages_description": "尝试向上滚动时间线,查看是否有更早的消息。", + "no_recent_messages_title": "未找到 %(user)s 的最近信息" }, - "redact_button": "移除最近消息", - "revoke_invite": "撤销邀请", - "room_encrypted": "此房间内的消息端到端加密。", - "room_encrypted_detail": "你的消息是安全的,只有你和接收者有解开它们的唯一密钥。", - "room_unencrypted": "此房间内的消息未端到端加密。", - "room_unencrypted_detail": "在加密房间中,你的消息是安全的,只有你和接收者有解开它们的唯一密钥。", - "share_button": "分享链接给其他用户", - "unban_button_room": "从房间取消解封", - "unban_button_space": "从空间取消封锁", - "unban_room_confirm_title": "解除 %(roomName)s 禁令", + "redact_button": "移除消息", + "revoke_invite": "撤消申请", + "room_encrypted": "此处的消息是端到端加密的。", + "room_encrypted_detail": "你的消息是安全的,只有你与对方拥有解锁它们的唯一密钥。", + "room_unencrypted": "在此房间中的消息非端到端加密。", + "room_unencrypted_detail": "在加密房间中你的消息是安全的,只有你与收件人拥有解密它们的唯一密钥。", + "send_message": "发送消息", + "share_button": "分享个人资料", + "unban_button_room": "从房间解封", + "unban_button_space": "从空间解封", + "unban_room_confirm_title": "从 %(roomName)s 解封", "unban_space_everything": "解除我权限范围内对这些人的所有禁令", "unban_space_specific": "解除我权限范围内对这些人的某些禁令", - "unban_space_warning": "他们将无法访问你不是管理员的一切。", + "unban_space_warning": "如果你不是管理员,他们将无法访问。", + "unignore_button": "取消忽略", + "verification_unavailable": "用户验证不可用", "verify_button": "验证用户", - "verify_explainer": "为了更加安全,在你两个设备上检查一次性代码来验证此用户。" + "verify_explainer": "为了提高安全性,请通过检查两台设备上的一次性代码来验证此用户。" }, "user_menu": { + "link_new_device": "关联新设备", "settings": "所有设置", - "switch_theme_dark": "切换到深色模式", - "switch_theme_light": "切换到浅色模式" + "switch_theme_dark": "切换为深色模式", + "switch_theme_light": "切换为浅色模式" }, "voip": { - "already_in_call": "正在通话中", - "already_in_call_person": "你正在与其通话。", + "already_in_call": "已在通话中", + "already_in_call_person": "你已正在与此人通话。", "answered_elsewhere": "已在别处接听", - "answered_elsewhere_description": "已在另一台设备上接听了此通话。", - "call_failed": "呼叫失败", + "answered_elsewhere_description": "通话已在其它设备上接听。", + "call_failed": "通话失败", "call_failed_description": "无法建立通话", - "call_failed_media": "通话失败,因为无法使用摄像头或麦克风。请检查:", - "call_failed_media_applications": "没有其他应用程序正在使用摄像头", - "call_failed_media_connected": "已插入并正确设置麦克风和摄像头", - "call_failed_media_permissions": "授权使用摄像头", - "call_failed_microphone": "呼叫失败,因为无法使用任何麦克风。 检查是否已插入并正确设置麦克风。", - "call_held": "%(peerName)s 挂起了通话", - "call_held_resume": "你挂起了通话 恢复", - "call_held_switch": "你挂起了通话 切换", + "call_failed_media": "通话失败,因为无法访问摄像头或麦克风。请检查:", + "call_failed_media_applications": "无其它应用程序使用摄像头", + "call_failed_media_connected": "麦克风与摄像头已插入并正确设置", + "call_failed_media_permissions": "已授予摄像头使用权", + "call_failed_microphone": "通话失败,因为无法访问麦克风。请检查麦克风是否已插入并正确设置。", + "call_held": "%(peerName)s 已挂起通话", + "call_held_resume": "你正在通话 恢复", + "call_held_switch": "你正在通话 切换", "call_toast_unknown_room": "未知房间", - "camera_disabled": "你的摄像头已关闭", - "camera_enabled": "你的摄像头仍然处于启用状态", - "cannot_call_yourself_description": "你不能打给自己。", - "connecting": "连接中", - "connection_lost": "已丢失与服务器的连接", - "connection_lost_description": "你不能在未连接到服务器时进行呼叫。", - "consulting": "与 %(transferTarget)s 进行协商。转让至 %(transferee)s", + "camera_disabled": "摄像头已关闭", + "camera_enabled": "摄像头仍处于开启状态", + "cannot_call_yourself_description": "你无法与自己通话。", + "close_lobby": "关闭大厅", + "connecting": "正在连接", + "connection_lost": "与服务器的连接已丢失", + "connection_lost_description": "在未连接到服务器的情况下,你无法拨打电话。", + "consulting": "正在与 %(transferTarget)s 协商。转接到 %(transferee)s", + "decline_call": "拒绝", "default_device": "默认设备", "dial": "拨号", "dialpad": "拨号盘", - "disable_camera": "关闭相机", + "disable_camera": "关闭摄像头", "disable_microphone": "静音麦克风", - "disabled_no_perms_start_video_call": "你没有权限开始视频通话", - "disabled_no_perms_start_voice_call": "你没有权限开始语音通话", - "disabled_ongoing_call": "正在进行的通话", - "enable_camera": "启动相机", + "disabled_no_perms_start_video_call": "你无权发起视频通话", + "disabled_no_perms_start_voice_call": "你无权发起语音通话", + "disabled_ongoing_call": "通话中", + "element_call": "Element Call", + "enable_camera": "打开摄像头", "enable_microphone": "取消静音麦克风", - "expand": "返回通话", + "expand": "返回到通话", + "get_call_link": "分享通话链接", "hangup": "挂断", - "hide_sidebar_button": "隐藏侧边栏", + "hide_sidebar_button": "隐藏边栏", "input_devices": "输入设备", - "maximise": "填满屏幕", - "misconfigured_server": "服务器配置错误导致通话失败", - "misconfigured_server_description": "请联系你的家服务器(%(homeserverDomain)s)的管理员配置 TURN 服务器,以确保通话过程稳定。", + "jitsi_call": "Jitsi 会议", + "legacy_call": "旧版通话", + "maximise": "填充屏幕", + "maximise_call": "最大化", + "metaspace_video_rooms": { + "conference_room_section": "会议" + }, + "minimise_call": "最小化", + "misconfigured_server": "由于服务器配置错误,通话失败", + "misconfigured_server_description": "请要求主服务器 (%(homeserverDomain)s) 的管理员配置 TURN 服务器,以确保通话可靠运行。", + "misconfigured_server_fallback": "或者,你可以尝试使用位于 的公共服务器,但这不完全可靠,并且会与该服务器共享 IP 地址。你也可以在“设置”中管理此设置。", + "misconfigured_server_fallback_accept": "尝试使用 %(server)s", "more_button": "更多", "msisdn_lookup_failed": "无法查询电话号码", - "msisdn_lookup_failed_description": "查询电话号码时发生错误", - "msisdn_transfer_failed": "无法转移通话", + "msisdn_lookup_failed_description": "查询电话号码时出错", + "msisdn_transfer_failed": "无法转接通话", "n_people_joined": { - "one": "%(count)s个人已加入", - "other": "%(count)s个人已加入" + "one": "%(count)s 个人已加入", + "other": "%(count)s 个人已加入" }, - "no_audio_input_description": "我们没能在你的设备上找到麦克风。请检查设置并重试。", + "no_audio_input_description": "我们未在此设备上找到麦克风。请检查设置并重试。", "no_audio_input_title": "未找到麦克风", - "no_media_perms_description": "你可能需要手动授权 %(brand)s 使用你的麦克风或摄像头", - "no_media_perms_title": "没有媒体存取权限", + "no_media_perms_description": "你可能需要手动允许 %(brand)s 访问你的麦克风或摄像头。", + "no_media_perms_title": "无媒体权限", "no_permission_conference": "需要权限", - "no_permission_conference_description": "你没有在此房间发起通话会议的权限", + "no_permission_conference_description": "你无权在此房间启动会议通话", "on_hold": "保留 %(name)s", "output_devices": "输出设备", "screenshare_monitor": "分享整个屏幕", "screenshare_title": "分享内容", "screenshare_window": "应用程序窗口", - "show_sidebar_button": "显示侧边栏", - "silence": "通话静音", + "show_sidebar_button": "显示边栏", + "silence": "静音通话", "silenced": "通知已静音", - "start_screenshare": "开始分享你的屏幕", - "stop_screenshare": "停止分享你的屏幕", - "too_many_calls": "太多通话", + "skip_lobby_toggle_option": "立即加入", + "start_screenshare": "开始分享屏幕", + "stop_screenshare": "停止分享屏幕", + "too_many_calls": "呼叫频繁", "too_many_calls_description": "你已达到同时通话的最大数量。", - "transfer_consult_first_label": "先询问", - "transfer_failed": "转移失败", - "transfer_failed_description": "通话转移失败", - "unable_to_access_audio_input_description": "我们无法访问你的麦克风。 请检查浏览器设置并重试。", + "transfer_consult_first_label": "优先咨询", + "transfer_failed": "传输失败", + "transfer_failed_description": "通话转接失败", + "unable_to_access_audio_input_description": "我们无法访问麦克风。请检查浏览器设置并重试。", "unable_to_access_audio_input_title": "无法访问你的麦克风", - "unable_to_access_media": "无法使用摄像头/麦克风", - "unable_to_access_microphone": "无法使用麦克风", - "unknown_caller": "未知来电人", - "unknown_person": "陌生人", + "unable_to_access_media": "无法访问摄像头或麦克风", + "unable_to_access_microphone": "无法访问麦克风", + "unknown_caller": "未知呼叫方", + "unknown_person": "未知人员", "unsilence": "开启声音", - "unsupported": "不支持通话", - "unsupported_browser": "你无法在此浏览器中进行呼叫。", - "user_busy": "用户正忙", - "user_busy_description": "你所呼叫的用户正忙。", - "user_is_presenting": "%(sharerName)s 正在展示", + "unsupported": "不支持的通话", + "unsupported_browser": "你无法在此浏览器中执行通话。", + "user_busy": "用户忙", + "user_busy_description": "你呼叫的用户正忙。", + "user_is_presenting": "%(sharerName)s 正在分享", "video_call": "视频通话", - "video_call_started": "视频通话已开始", + "video_call_incoming": "视频通话来电", + "video_call_started": "已开始视频通话", + "video_call_using": "视频通话时使用:", "voice_call": "语音通话", - "you_are_presenting": "你正在展示" + "voice_call_incoming": "语音通话来电", + "voice_call_using": "语音通话时使用:", + "you_are_presenting": "你正在演示" }, - "web_default_device_name": "%(appName)s:%(browserName)s在%(osName)s", - "welcome_to_element": "欢迎来到 Element", + "web_default_device_name": "%(appName)s:%(browserName)s 运行于 %(osName)s", + "welcome_to_element": "欢迎使用 Element", "widget": { - "added_by": "挂件添加者", + "added_by": "小部件添加者:", "capabilities_dialog": { - "content_starting_text": "此挂件想要:", + "content_starting_text": "此小部件期望:", "decline_all_permission": "全部拒绝", - "remember_Selection": "记住我对此挂件的选择", - "title": "批准挂件权限" + "remember_Selection": "记住我对此小部件的选择", + "title": "批准小部件权限" }, "capability": { - "always_on_screen_generic": "运行时始终保留在你的屏幕上", - "always_on_screen_viewing_another_room": "运行时始终保留在你的屏幕上,即使你在浏览其它房间", - "any_room": "以上,但也包括你加入或被邀请的任何房间中", - "byline_empty_state_key": "附带一个空的状态键(state key)", + "always_on_screen_generic": "运行期间持续显示于屏幕", + "always_on_screen_viewing_another_room": "正在运行并且正在查看另一个房间时持续保留在屏幕上", + "any_room": "同上,但在任何你加入或被邀请到的房间中也是如此", + "byline_empty_state_key": "使用空状态值", "byline_state_key": "附带有状态键(state key)%(stateKey)s", - "capability": "%(capability)s 容量", + "capability": "%(capability)s 能力", "change_avatar_active_room": "更改活跃房间的头像", - "change_avatar_this_room": "更改当前房间的头像", + "change_avatar_this_room": "更改此房间的头像", "change_name_active_room": "更改活跃房间的名称", - "change_name_this_room": "更改当前房间的名称", - "change_topic_active_room": "更改当前活跃房间的话题", - "change_topic_this_room": "更改当前房间的话题", - "receive_membership_active_room": "查看人们何时加入、离开或被邀请到你所活跃的房间", - "receive_membership_this_room": "查看人们加入、离开或被邀请到此房间的时间", - "remove_ban_invite_leave_active_room": "移除、封禁或邀请他人加入你的活跃房间,方可离开", - "remove_ban_invite_leave_this_room": "移除、封禁或邀请他人加入此房间,方可离开", - "see_avatar_change_active_room": "查看你的活跃房间的头像何时修改", - "see_avatar_change_this_room": "查看此房间的头像何时被修改", - "see_event_type_sent_active_room": "查看你的活跃房间中发送的 %(eventType)s 事件", - "see_event_type_sent_this_room": "查看此房间中发送的 %(eventType)s 事件", - "see_images_sent_active_room": "查看发布到你所活跃的房间的图片", - "see_images_sent_this_room": "查看发布到此房间的图片", - "see_messages_sent_active_room": "查看发布到你所活跃的房间的消息", + "change_name_this_room": "更改在此房间的名称", + "change_topic_active_room": "更改活跃房间的主题", + "change_topic_this_room": "更改此房间的主题", + "download_file": "从媒体仓库下载文件", + "receive_membership_active_room": "查看活跃房间何时有人加入、离开或被邀请", + "receive_membership_this_room": "查看此房间何时有人加入、离开或被邀请", + "remove_ban_invite_leave_active_room": "移除、禁止或邀请他人进入活跃房间并使你离开", + "remove_ban_invite_leave_this_room": "移除、禁止或邀请他人进入此房间并使你离开", + "see_avatar_change_active_room": "查看活跃房间中的头像何时被更改", + "see_avatar_change_this_room": "查看此房间的头像何时被更改", + "see_event_type_sent_active_room": "查看发布到活跃房间的 %(eventType)s 事件", + "see_event_type_sent_this_room": "查看发布到此房间的 %(eventType)s 事件", + "see_images_sent_active_room": "查看发送到活跃房间的图像", + "see_images_sent_this_room": "查看发布到此房间的图像", + "see_messages_sent_active_room": "查看发送到活跃房间的消息", "see_messages_sent_this_room": "查看发布到此房间的消息", - "see_msgtype_sent_active_room": "查看发布到你所活跃的房间的 %(msgtype)s 消息", + "see_msgtype_sent_active_room": "查看发布活跃房间的 %(msgtype)s 消息", "see_msgtype_sent_this_room": "查看发布到此房间的 %(msgtype)s 消息", - "see_name_change_active_room": "查看你的活跃房间的名称何时被修改", - "see_name_change_this_room": "查看此房间的名称何时被修改", - "see_sent_emotes_active_room": "查看发布到你所活跃的房间的表情", + "see_name_change_active_room": "查看活跃房间中的名称何时被更改", + "see_name_change_this_room": "查看此房间的名称何时被更改", + "see_sent_emotes_active_room": "查看发送到活跃房间的表情", "see_sent_emotes_this_room": "查看发布到此房间的表情", - "see_sent_files_active_room": "查看发布到你所活跃的房间的一般性文件", - "see_sent_files_this_room": "查看发布到此房间的一般性文件", - "see_sticker_posted_active_room": "查看何时有人发送贴纸到你所活跃的房间", - "see_sticker_posted_this_room": "查看此房间中何时有人发送贴纸", - "see_text_messages_sent_active_room": "查看发布到你所活跃的房间的文本消息", - "see_text_messages_sent_this_room": "查看发布到此房间的文本消息", - "see_topic_change_active_room": "查看你的活跃房间的话题何时被修改", - "see_topic_change_this_room": "查看此房间的话题何时被修改", - "see_videos_sent_active_room": "查看发布到你所活跃的房间的视频", + "see_sent_files_active_room": "查看发布到活跃房间的普通文件", + "see_sent_files_this_room": "查看发送到此房间的普通文件", + "see_sticker_posted_active_room": "查看任何人发布到活跃房间的贴纸", + "see_sticker_posted_this_room": "查看此房间的贴纸何时被发送", + "see_text_messages_sent_active_room": "查看发布到活跃房间的文本消息", + "see_text_messages_sent_this_room": "查看发送到活跃房间的文本消息", + "see_topic_change_active_room": "查看活跃房间中的主题何时被更改", + "see_topic_change_this_room": "查看此房间的主题何时被更改", + "see_videos_sent_active_room": "查看发送到活跃房间的视频", "see_videos_sent_this_room": "查看发布到此房间的视频", - "send_emotes_active_room": "在你所活跃的房间以你的身份发送表情", - "send_emotes_this_room": "在此房间以你的身份发送表情", - "send_event_type_active_room": "以你的身份在你的活跃房间发送%(eventType)s事件", + "send_emotes_active_room": "以你的身份在活跃房间发送表情", + "send_emotes_this_room": "以你的身份在此房间发送表情", + "send_event_type_active_room": "以你的身份在活跃房间中发送%(eventType)s事件", "send_event_type_this_room": "以你的身份在此房间发送 %(eventType)s 事件", - "send_files_active_room": "在你所活跃的房间以你的身份发送一般性文件", - "send_files_this_room": "查看发布到此房间的一般性文件", - "send_images_active_room": "在你所活跃的房间以你的身份发送图片", - "send_images_this_room": "在此房间以你的身份发送图片", - "send_messages_active_room": "在你所活跃的房间以你的身份发送消息", - "send_messages_this_room": "在此房间以你的身份发送消息", - "send_msgtype_active_room": "在你所活跃的房间以你的身份发送 %(msgtype)s 消息", - "send_msgtype_this_room": "在此房间以你的身份发送 %(msgtype)s 消息", - "send_stickers_active_room": "发送贴纸到你的活跃房间", - "send_stickers_active_room_as_you": "发送贴纸到你所活跃的房间", - "send_stickers_this_room": "发送贴纸到此房间", - "send_stickers_this_room_as_you": "以你的身份发送贴纸到此房间", - "send_text_messages_active_room": "在你所活跃的房间以你的身份发送文本消息", - "send_text_messages_this_room": "在此房间以你的身份发送文本消息", - "send_videos_active_room": "查看发布到你所活跃的房间的视频", - "send_videos_this_room": "查看发布到此房间的视频", + "send_files_active_room": "以你的身份在活跃房间发送普通文件", + "send_files_this_room": "以你的身份在此房间发送普通文件", + "send_images_active_room": "以你的身份在活跃房间发送图像", + "send_images_this_room": "以你的身份在此房间发送图片", + "send_messages_active_room": "以你的身份在活跃房间发送消息", + "send_messages_this_room": "以你的身份在此房间中发送信息", + "send_msgtype_active_room": "以你的身份在活跃房间中发送%(msgtype)s消息", + "send_msgtype_this_room": "以你的身份在此房间发送 %(msgtype)s 消息", + "send_stickers_active_room": "向你的活跃房间发送贴纸", + "send_stickers_active_room_as_you": "以你的身份在活跃房间发送贴纸", + "send_stickers_this_room": "在此房间发送贴纸", + "send_stickers_this_room_as_you": "以你的身份在此房间中发送贴纸", + "send_text_messages_active_room": "以你的身份在活跃房间发送文本消息", + "send_text_messages_this_room": "以你的身份在此房间发送文本消息", + "send_videos_active_room": "以你的身份在活跃房间发送视频", + "send_videos_this_room": "以你的身份在此房间发送视频", "specific_room": "以上,但也包括 ", - "switch_room": "更改当前正在查看哪个房间", - "switch_room_message_user": "更改当前正在查看哪个房间、消息或用户" + "switch_room": "更改你正在查看的房间", + "switch_room_message_user": "更改正在查看的房间、消息或用户" }, - "close_to_view_right_panel": "关闭此小部件以在此面板中查看", + "close_to_view_right_panel": "关闭此小部件以在此面板查看", "context_menu": { - "delete": "删除挂件", - "delete_warning": "删除挂件时将为房间中的所有成员删除。你确定要删除此挂件吗?", + "delete": "删除小部件", + "delete_warning": "删除小部件的同时会为该房间中的所有用户删除。你确定要删除此小部件?", "move_left": "向左移动", "move_right": "向右移动", "remove": "为所有人删除", @@ -3158,71 +4059,75 @@ "screenshot": "拍照", "start_audio_stream": "开始音频流" }, - "cookie_warning": "此挂件可能使用 cookie。", - "error_hangup_description": "你已断开通话。(错误:%(message)s)", + "cookie_warning": "此小部件可能会使用 Cookies。", + "error_hangup_description": "通话已中断。(错误:(message)s)", "error_hangup_title": "连接丢失", - "error_loading": "加载挂件时发生错误", - "error_mixed_content": "错误 - 混合内容", - "error_need_invite_permission": "你需要有邀请用户的权限才能进行此操作。", - "error_need_kick_permission": "你需要能够移除用户才能做到那件事。", + "error_loading": "载入小部件时出错", + "error_mixed_content": "错误:混合内容", + "error_need_invite_permission": "你需要能邀请用户才能验证。", + "error_need_kick_permission": "你需要能踢出用户以执行此操作。", "error_need_to_be_logged_in": "你需要登录。", - "error_unable_start_audio_stream_description": "无法开始音频流媒体。", - "error_unable_start_audio_stream_title": "开始流直播失败", - "modal_data_warning": "此屏幕上的数据与%(widgetDomain)s分享", - "modal_title_default": "模态框挂件(Modal Widget)", - "no_name": "未知应用", + "error_unable_start_audio_stream_description": "无法开始音频串流。", + "error_unable_start_audio_stream_title": "无法开始直播", + "modal_data_warning": "以下数据将分享给 %(widgetDomain)s", + "modal_title_default": "模态窗口小部件", + "no_name": "未知 App", "open_id_permissions_dialog": { - "remember_selection": "记住", - "starting_text": "挂件将会验证你的用户 ID,但将无法为你执行动作:", - "title": "允许此挂件验证你的身份" + "remember_selection": "记住我的选择", + "starting_text": "此小部件将验证你的用户 ID,但无法为你执行以下操作:", + "title": "允许此小部件验证你的身份" }, - "popout": "在弹出式窗口中打开挂件", - "set_room_layout": "将我的房间布局设置给所有人", - "shared_data_avatar": "您的个人资料图片URL", - "shared_data_device_id": "你的设备ID", + "popout": "弹出小部件", + "set_room_layout": "为所有人布局", + "shared_data_avatar": "个人资料图像 URL", + "shared_data_device_id": "你的设备 ID", + "shared_data_lang": "你的语言", "shared_data_mxid": "你的用户 ID", "shared_data_name": "你的显示名称", "shared_data_room_id": "房间 ID", "shared_data_theme": "你的主题", "shared_data_url": "%(brand)s 的链接", - "shared_data_warning": "使用此挂件可能会和 %(widgetDomain)s 共享数据 。", - "shared_data_warning_im": "使用此挂件可能会与 %(widgetDomain)s 及您的集成管理器共享数据 。", - "shared_data_widget_id": "挂件 ID", - "unencrypted_warning": "挂件不适用消息加密。", - "unmaximise": "取消最大化", - "unpin_to_view_right_panel": "取消固定此小部件以在此面板中查看" + "shared_data_warning": "使用此小部件可能会与 %(widgetDomain)s 共享数据 。", + "shared_data_warning_im": "使用此小部件可能会与 %(widgetDomain)s 与你的集成管理器共享数据 。", + "shared_data_widget_id": "小部件 ID", + "unencrypted_warning": "小部件不使用消息加密。", + "unmaximise": "还原尺寸", + "unpin_to_view_right_panel": "取消钉住此小部件以在此面板中查看" }, "zxcvbn": { "suggestions": { - "allUppercase": "全大写的密码通常比全小写的更容易猜测", - "anotherWord": "再加一两个词。不常见的词更好。", - "associatedYears": "避免与你相关联的年份", - "capitalization": "大写字母并没有很大的作用", - "dates": "避免与你相关联的日期与年份", - "l33t": "可预见的替换如将 '@' 替换为 'a' 并不会有太大效果", - "longerKeyboardPattern": "使用变化更丰富的字符组合方式", - "noNeed": "不一定要有符号、数字或大写字母", - "recentYears": "避免年份", - "repeated": "避免重复词语与字符", - "reverseWords": "把单词倒过来不会比原来的难猜很多", - "sequences": "避免递增或递减的序列", - "useWords": "用一些字符,避免常用短语" + "allUppercase": "全大写几乎与全小写一样容易被猜测到", + "anotherWord": "再加一两个词。不常用的词更好。", + "associatedYears": "避免与你相关的年份", + "capitalization": "大写字母对密码强度的帮助不大", + "dates": "避免使用与你相关的日期与年份", + "l33t": "像用“@”代替“a”这样可预料的替换行为并没有什么帮助", + "longerKeyboardPattern": "使用更复杂的击键序列", + "noNeed": "不需要符号、数字或大写字母", + "pwned": "如果你在其它地方使用此密码,则应进行更改。", + "recentYears": "避免近些年", + "repeated": "避免重复的单词与字符", + "reverseWords": "颠倒的单词很难被猜测到", + "sequences": "避免使用序列", + "useWords": "使用若干单词,避免常用口令" }, "warnings": { - "common": "这是一个非常常见的密码", - "commonNames": "常用姓名和姓氏很容易被猜到", - "dates": "日期通常很容易被猜到", - "extendedRepeat": "像 “abcabcabc” 这样的重复字符也只比 “abc” 稍微难猜一点点", - "keyPattern": "键位短序列很容易被猜到", - "namesByThemselves": "姓名和姓氏本身很容易被猜到", - "recentYears": "最近的年份很容易被猜到", - "sequences": "像 abc 或 6543 这样的序列很容易被猜到", - "similarToCommon": "这类似于一个常用密码", - "simpleRepeat": "像 “aaa” 这样的重复字符很容易被猜到", - "straightRow": "键位在一条直线上的组合很容易被猜到", - "topHundred": "这是百大常用密码之一", - "topTen": "这是十大常用密码之一", - "wordByItself": "单词本身很容易被猜到" + "common": "此为常见密码", + "commonNames": "常用姓、名容易被猜测到", + "dates": "日期通常容易被猜测到", + "extendedRepeat": "像“abcabcabc”之类的重复单词比“abc”更难猜测到。", + "keyPattern": "简短的击键序列容易被猜测到", + "namesByThemselves": "姓、名容易被猜测到", + "pwned": "你的密码因 Internet 上的数据泄露而暴露。", + "recentYears": "近年来很容易猜到", + "sequences": "像“abc”或“6543”之类的序列容易被猜测到", + "similarToCommon": "这像是常用密码", + "simpleRepeat": "像“aaa”之类的重复的字符容易被猜测到", + "straightRow": "在同一列且连续的字母容易被猜测到", + "topHundred": "这是百大常用密码", + "topTen": "这是十大常用密码", + "userInputs": "不应有任何个人或页面相关数据。", + "wordByItself": "单词很容易猜测到" } } } diff --git a/apps/web/src/i18n/strings/zh_Hant.json b/apps/web/src/i18n/strings/zh_Hant.json index bc6d8a9be0..47079c9d9d 100644 --- a/apps/web/src/i18n/strings/zh_Hant.json +++ b/apps/web/src/i18n/strings/zh_Hant.json @@ -1874,8 +1874,6 @@ "aliases_section": "聊天室位址", "avatar_field_label": "聊天室大頭照", "canonical_alias_field_label": "主要位址", - "default_url_previews_off": "此聊天室已預設對參與者停用網址預覽。", - "default_url_previews_on": "此聊天室已預設對參與者啟用網址預覽。", "description_space": "編輯您的聊天空間的設定。", "error_creating_alias_description": "建立該位址時發生錯誤。伺服器可能不允許這麼做,或是有暫時性的問題。", "error_creating_alias_title": "建立位址錯誤", @@ -1902,12 +1900,7 @@ "published_aliases_explainer_space": "任何伺服器上的人都可以使用已發佈的位址加入您的聊天空間。", "published_aliases_section": "已發佈的位址", "save": "儲存變更", - "topic_field_label": "聊天室主題", - "url_preview_encryption_warning": "在加密的聊天室中(這個就是),會預設停用網址預覽以確保您的家伺服器(產生預覽資訊的地方)無法透過這個聊天室收集您看到的連結的相關資訊。", - "url_preview_explainer": "當某人在他們的訊息中放置網址時,可以顯示如標題、描述與網頁上的圖片等等來給您更多關於該連結的資訊。", - "url_previews_section": "網址預覽", - "user_url_previews_default_off": "您已預設停用網址預覽。", - "user_url_previews_default_on": "您已預設停用網址預覽。" + "topic_field_label": "聊天室主題" }, "notifications": { "browse_button": "瀏覽", @@ -2242,8 +2235,6 @@ "spell_check_locale_placeholder": "選擇語系" }, "inline_url_previews_default": "預設啟用行內網址預覽", - "inline_url_previews_room": "對此聊天室中的參與者預設啟用網址預覽", - "inline_url_previews_room_account": "對此聊天室啟用網址預覽(僅影響您)", "insert_trailing_colon_mentions": "在使用者於訊息開頭提及之後插入跟隨冒號", "jump_to_bottom_on_send": "傳送訊息時,跳到時間軸底部", "key_backup": { diff --git a/apps/web/src/utils/form.ts b/apps/web/src/utils/form.ts new file mode 100644 index 0000000000..21017c393e --- /dev/null +++ b/apps/web/src/utils/form.ts @@ -0,0 +1,17 @@ +/* +Copyright 2026 Element Creations 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 React from "react"; + +/** + * onSubmit handler which calls preventDefault and stopPropagation on the event + * @param e submit event + */ +export function onSubmitPreventDefault(e: SubmitEvent | React.SubmitEvent): void { + e.preventDefault(); + e.stopPropagation(); +} diff --git a/apps/web/src/utils/oidc/authorize.ts b/apps/web/src/utils/oidc/authorize.ts index d409396db9..2e1420e32e 100644 --- a/apps/web/src/utils/oidc/authorize.ts +++ b/apps/web/src/utils/oidc/authorize.ts @@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details. */ import { completeAuthorizationCodeGrant, generateOidcAuthorizationUrl } from "matrix-js-sdk/src/oidc/authorize"; -import { type QueryDict } from "matrix-js-sdk/src/utils"; import { type OidcClientConfig } from "matrix-js-sdk/src/matrix"; import { secureRandomString } from "matrix-js-sdk/src/randomstring"; import { type IdTokenClaims } from "oidc-client-ts"; import { OidcClientError } from "./error"; import PlatformPeg from "../../PlatformPeg"; +import { type URLParams } from "../../vector/url_utils.ts"; /** * Start OIDC authorization code flow @@ -23,6 +23,7 @@ import PlatformPeg from "../../PlatformPeg"; * @param clientId this client's id as registered with configured issuer * @param homeserverUrl target homeserver * @param identityServerUrl OPTIONAL target identity server + * @param isRegistration if true will set the prompt to "create" * @returns Promise that resolves after we have navigated to auth endpoint */ export const startOidcLogin = async ( @@ -47,24 +48,30 @@ export const startOidcLogin = async ( nonce, prompt, urlState: PlatformPeg.get()?.getOidcClientState(), + responseMode: delegatedAuthConfig.response_modes_supported?.includes("fragment") ? "fragment" : "query", }); window.location.href = authorizationUrl; }; /** - * Gets `code` and `state` query params + * Gets `code` and `state` response params * - * @param queryParams + * @param urlParams - the parameters to read + * @param responseMode - the response_mode used in the auth request * @returns code and state * @throws when code and state are not valid strings */ -const getCodeAndStateFromQueryParams = (queryParams: QueryDict): { code: string; state: string } => { - const code = queryParams["code"]; - const state = queryParams["state"]; - +const getCodeAndStateFromParams = ( + { code, state }: NonNullable, + responseMode: "fragment" | "query", +): { code: string; state: string } => { if (!code || typeof code !== "string" || !state || typeof state !== "string") { - throw new Error(OidcClientError.InvalidQueryParameters); + if (responseMode === "fragment") { + throw new Error(OidcClientError.InvalidFragmentParameters); + } else { + throw new Error(OidcClientError.InvalidQueryParameters); + } } return { code, state }; }; @@ -89,14 +96,18 @@ type CompleteOidcLoginResponse = { }; /** * Attempt to complete authorization code flow to get an access token - * @param queryParams the query-parameters extracted from the real query-string of the starting URI. + * @param urlParams the parameters extracted from the app-load URI. + * @param responseMode - the response_mode used in the auth request * @returns Promise that resolves with a CompleteOidcLoginResponse when login was successful * @throws When we failed to get a valid access token */ -export const completeOidcLogin = async (queryParams: QueryDict): Promise => { - const { code, state } = getCodeAndStateFromQueryParams(queryParams); +export const completeOidcLogin = async ( + urlParams: NonNullable, + responseMode: "fragment" | "query", +): Promise => { + const { code, state } = getCodeAndStateFromParams(urlParams, responseMode); const { homeserverUrl, tokenResponse, idTokenClaims, identityServerUrl, oidcClientSettings } = - await completeAuthorizationCodeGrant(code, state); + await completeAuthorizationCodeGrant(code, state, responseMode); return { homeserverUrl, diff --git a/apps/web/src/utils/oidc/error.ts b/apps/web/src/utils/oidc/error.ts index f9334a739c..3cc5c14ec5 100644 --- a/apps/web/src/utils/oidc/error.ts +++ b/apps/web/src/utils/oidc/error.ts @@ -17,6 +17,7 @@ import { _t } from "../../languageHandler"; */ export enum OidcClientError { InvalidQueryParameters = "Invalid query parameters for OIDC native login. `code` and `state` are required.", + InvalidFragmentParameters = "Invalid fragment parameters for OIDC native login. `code` and `state` are required.", } /** @@ -30,6 +31,7 @@ export const getOidcErrorMessage = (error: Error): string | ReactNode => { case OidcError.MissingOrInvalidStoredState: return _t("auth|oidc|missing_or_invalid_stored_state"); case OidcClientError.InvalidQueryParameters: + case OidcClientError.InvalidFragmentParameters: case OidcError.CodeExchangeFailed: case OidcError.InvalidBearerTokenResponse: case OidcError.InvalidIdToken: diff --git a/apps/web/src/vector/app.tsx b/apps/web/src/vector/app.tsx index cc4da3373a..db5d0e4e99 100644 --- a/apps/web/src/vector/app.tsx +++ b/apps/web/src/vector/app.tsx @@ -17,7 +17,6 @@ import { logger } from "matrix-js-sdk/src/logger"; import { AutoDiscovery, type ClientConfig } from "matrix-js-sdk/src/matrix"; import { WrapperLifecycle, type WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle"; -import type { QueryDict } from "matrix-js-sdk/src/utils"; import PlatformPeg from "../PlatformPeg"; import AutoDiscoveryUtils from "../utils/AutoDiscoveryUtils"; import * as Lifecycle from "../Lifecycle"; @@ -27,8 +26,8 @@ import { SnakedObject } from "../utils/SnakedObject"; import MatrixChat from "../components/structures/MatrixChat"; import { type ValidatedServerConfig } from "../utils/ValidatedServerConfig"; import { ModuleRunner } from "../modules/ModuleRunner"; -import { parseQs } from "./url_utils"; import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing"; +import { type URLParams } from "./url_utils.ts"; import { UserFriendlyError } from "../languageHandler"; import { ModuleApi } from "../modules/Api"; import { RoomView } from "../components/structures/RoomView"; @@ -41,20 +40,22 @@ logger.log(`Application is running in ${process.env.NODE_ENV} mode`); window.matrixLogger = logger; -function onTokenLoginCompleted(): void { - // if we did a token login, we're now left with the token, hs and is - // url as query params in the url; - // if we did an oidc authorization code flow login, we're left with the auth code and state - // as query params in the url; - // a little nasty but let's redirect to clear them. +function onTokenLoginCompleted(urlParams: URLParams, fragmentAfterLogin: string): void { const url = new URL(window.location.href); - url.searchParams.delete("no_universal_links"); - url.searchParams.delete("loginToken"); - url.searchParams.delete("state"); - url.searchParams.delete("code"); + // if we did a token login, we're now left with the login token as query param in the url; clear it out + for (const param in { ...urlParams.legacy_sso, ...urlParams.oidc_query }) { + url.searchParams.delete(param); + } - logger.log(`Redirecting to ${url.href} to drop delegated authentication params from queryparams`); + // Added by OIDC auth to avoid being hijacked by Element X on macOS + url.searchParams.delete("no_universal_links"); + + // if we did an oidc authorization code flow login, we're left with the auth code and state in the fragment in the url, + // we clear it out by using the fragmentAfterLogin + url.hash = fragmentAfterLogin; + + logger.log(`Redirecting to ${url.href} to drop authentication params from url`); window.history.replaceState(null, "", url.href); } @@ -87,7 +88,7 @@ async function redirectToSso(config: ValidatedServerConfig): Promise { return false; } -export async function loadApp(fragParams: QueryDict, matrixChatRef: React.Ref): Promise { +export async function loadApp(urlParams: URLParams, matrixChatRef: React.Ref): Promise { // XXX: This lives here because certain components import so many things that importing it in a sensible place (eg. // the builtins module or init.tsx) causes a circular dependency. ModuleApi.instance.builtins.setComponents({ @@ -99,8 +100,6 @@ export async function loadApp(fragParams: QueryDict, matrixChatRef: React.Ref { // give rageshake a chance to load/fail, we don't actually assert rageshake loads, we allow it to fail if no IDB await settled(rageshakePromise); - const fragparts = parseQsFromFragment(window.location); + const parsedUrl = parseAppUrl(window.location); // don't try to redirect to the native apps if we're // verifying a 3pid (but after we've loaded the config) // or if the user is following a deep link // (https://github.com/element-hq/element-web/issues/7378) - const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0; + const preventRedirect = !!parsedUrl.params.threepid || parsedUrl.location.length > 0; if (!preventRedirect) { const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; @@ -232,7 +232,7 @@ async function start(): Promise { // Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to // run on the components. - await loadApp(fragparts.params); + await loadApp(parsedUrl.params); } catch (err) { logger.error(err); // Like the compatibility page, AWOOOOOGA at the user diff --git a/apps/web/src/vector/init.tsx b/apps/web/src/vector/init.tsx index 6706b75672..2557e74ba3 100644 --- a/apps/web/src/vector/init.tsx +++ b/apps/web/src/vector/init.tsx @@ -13,7 +13,6 @@ import React, { StrictMode } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { ModuleLoader } from "@element-hq/element-web-module-api"; -import type { QueryDict } from "matrix-js-sdk/src/utils"; import * as languageHandler from "../languageHandler"; import SettingsStore from "../settings/SettingsStore"; import PlatformPeg from "../PlatformPeg"; @@ -26,6 +25,7 @@ import PWAPlatform from "./platform/PWAPlatform"; import WebPlatform from "./platform/WebPlatform"; import { initRageshake, initRageshakeStore } from "./rageshakesetup"; import { ModuleApi } from "../modules/Api.ts"; +import { type URLParams } from "./url_utils.ts"; export const rageshakePromise = initRageshake(); @@ -86,7 +86,7 @@ export async function loadTheme(): Promise { return setTheme(); } -export async function loadApp(fragParams: QueryDict): Promise { +export async function loadApp(urlParams: URLParams): Promise { // load app.js async so that its code is not executed immediately and we can catch any exceptions const module = await import( /* webpackChunkName: "element-web-app" */ @@ -96,7 +96,7 @@ export async function loadApp(fragParams: QueryDict): Promise { function setWindowMatrixChat(matrixChat: MatrixChat): void { window.matrixChat = matrixChat; } - const app = await module.loadApp(fragParams, setWindowMatrixChat); + const app = await module.loadApp(urlParams, setWindowMatrixChat); const root = createRoot(document.getElementById("matrixchat")!); root.render(app); } diff --git a/apps/web/src/vector/platform/WebPlatform.ts b/apps/web/src/vector/platform/WebPlatform.ts index a794e160a4..88aa7ee9cc 100644 --- a/apps/web/src/vector/platform/WebPlatform.ts +++ b/apps/web/src/vector/platform/WebPlatform.ts @@ -16,7 +16,6 @@ import dis from "../../dispatcher/dispatcher"; import { hideToast as hideUpdateToast, showToast as showUpdateToast } from "../../toasts/UpdateToast"; import { Action } from "../../dispatcher/actions"; import { type CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload"; -import { parseQs } from "../url_utils"; import { _t } from "../../languageHandler"; import ToastStore from "../../stores/ToastStore.ts"; import GenericToast from "../../components/views/toasts/GenericToast.tsx"; @@ -174,8 +173,8 @@ export default class WebPlatform extends BasePlatform { // cache-control: nocache HTTP header set, but Firefox doesn't always obey it :/ console.log("startUpdater, current version is " + getNormalizedAppVersion(WebPlatform.VERSION)); void this.pollForUpdate((version: string, newVersion: string) => { - const query = parseQs(location); - if (query.updated) { + const url = new URL(window.location.href); + if (url.searchParams.has("updated")) { console.log("Update reloaded but still on an old version, stopping"); // We just reloaded already and are still on the old version! // Show the toast rather than reload in a loop. @@ -184,7 +183,6 @@ export default class WebPlatform extends BasePlatform { } // Set updated as a cachebusting query param and reload the page. - const url = new URL(window.location.href); url.searchParams.set("updated", newVersion); console.log("Update reloading to " + url.toString()); window.location.href = url.toString(); diff --git a/apps/web/src/vector/routing.ts b/apps/web/src/vector/routing.ts index 964c0c0684..149316424c 100644 --- a/apps/web/src/vector/routing.ts +++ b/apps/web/src/vector/routing.ts @@ -11,15 +11,20 @@ Please see LICENSE files in the repository root for full details. import { logger } from "matrix-js-sdk/src/logger"; import { type QueryDict } from "matrix-js-sdk/src/utils"; -import { parseQsFromFragment } from "./url_utils"; +import { parseQsFromFragment, searchParamsToQueryDict } from "./url_utils"; let lastLocationHashSet: string | null = null; -export function getScreenFromLocation(location: Location): { screen: string; params: QueryDict } { +export interface IScreen { + screen: string; + params: QueryDict; +} + +export function getScreenFromLocation(location: Location): IScreen { const fragparts = parseQsFromFragment(location); return { screen: fragparts.location.substring(1), - params: fragparts.params, + params: fragparts.params ? searchParamsToQueryDict(fragparts.params) : {}, }; } diff --git a/apps/web/src/vector/url_utils.ts b/apps/web/src/vector/url_utils.ts index 2b5202806e..34d671c678 100644 --- a/apps/web/src/vector/url_utils.ts +++ b/apps/web/src/vector/url_utils.ts @@ -5,32 +5,134 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type QueryDict, decodeParams } from "matrix-js-sdk/src/utils"; +import { type QueryDict } from "matrix-js-sdk/src/utils"; // We want to support some name / value pairs in the fragment -// so we're re-using query string like format -// -export function parseQsFromFragment(location: Location): { location: string; params: QueryDict } { +// so we're re-using query string like format, where we accept a `?key=value&key2=value2` string at the end of the hash +// but we also accept a hash like `key=value&key2=value2` for compatibility with oAuth response_mode = fragment +export function parseQsFromFragment(url: Location | URL): { location: string; params?: URLSearchParams } { // if we have a fragment, it will start with '#', which we need to drop. // (if we don't, this will return ''). - const fragment = location.hash.substring(1); + const fragment = url.hash.substring(1); // our fragment may contain a query-param-like section. we need to fish // this out *before* URI-decoding because the params may contain ? and & // characters which are only URI-encoded once. - const hashparts = fragment.split("?"); + const [main, query] = fragment.split("?", 2); - const result = { - location: decodeURIComponent(hashparts[0]), - params: {}, - }; - - if (hashparts.length > 1) { - result.params = decodeParams(hashparts[1]); + // Handle oAuth-style fragment parameters + if (main.includes("=")) { + return { + location: "", + params: new URLSearchParams(main), + }; } - return result; + + return { + location: decodeURIComponent(main), + params: query ? new URLSearchParams(query) : undefined, + }; } -export function parseQs(location: Location): QueryDict { - return decodeParams(location.search.substring(1)); +/** + * Convert a URLSearchParams object to QueryDict + * Any keys with multiple values will be grouped into an array + * @param params the URLSearchParams to convert + */ +export function searchParamsToQueryDict(params: URLSearchParams): QueryDict { + const queryDict: QueryDict = {}; + for (const key of params.keys()) { + const val = params.getAll(key); + queryDict[key] = val.length === 1 ? val[0] : val; + } + return queryDict; +} + +const urlParameterConfig = { + // Query string params for legacy SSO login, added by the Matrix homeserver + legacy_sso: { + keys: ["loginToken"], + location: "query", + }, + // Fragment params for OIDC login, added by the Identity Provider + oidc_fragment: { + keys: ["code", "state"], + location: "fragment", + }, + // Query params for OIDC login, added by the Identity Provider, used as fallback when fragment is unsupported + oidc_query: { + keys: ["code", "state"], + location: "query", + }, + // Fragment params relating to 3pid (email) invites, added in url within the invite email itself + threepid: { + keys: ["client_secret", "session_id", "hs_url", "is_url", "sid"], + location: "fragment", + }, + // XXX: unclear where, if anywhere, this is set + defaults: { + keys: ["defaultUsername"], + location: "fragment", + }, + // XXX: Fragment params seemingly relating to 3pid invites, though the code in the area doubts they are ever specified + guest: { + keys: ["guest_user_id", "guest_access_token"], + location: "fragment", + }, +} as const satisfies Record< + string, + { + keys: string[]; + // Query params live in the query string, in the middle of the URL, after a `?`, in a `key=value` format, delimited by `&`. + // Fragment params live in the fragment string, at the end of the URL, after a `?`, in a `key=value` format, delimited by `&`. + location: "query" | "fragment"; + } +>; + +export type URLParams = Partial<{ + -readonly [K in keyof typeof urlParameterConfig]: Partial<{ + [P in (typeof urlParameterConfig)[K]["keys"][number]]: string; + }>; +}>; + +/** + * Utility to parse parameters held in the app's URL. + * Currently focusing only on at-load URL parameters. + * @param url - the URL to parse. + * @return an object keyed by the groups defined in {@link urlParameterConfig} with values for each key listed, + * sourced from the location (query/fragment/either) specified. If no parameters in a group are found the entire group + * will be omitted from the returned object to simplify presence checking. + */ +export function parseAppUrl(url: Location | URL): { + location: string; + params: URLParams; +} { + const queryParams = new URLSearchParams(url.search); + const parsedFragment = parseQsFromFragment(url); + + const urlParams: Partial = {}; + + for (const group in urlParameterConfig) { + const groupKey = group as keyof URLParams; + const groupConfig = urlParameterConfig[groupKey]; + + const params = groupConfig.location === "fragment" ? parsedFragment.params : queryParams; + if (!params) continue; // no params + + const target: Record = {}; + for (const k of groupConfig.keys) { + const key = k as (typeof groupConfig)["keys"][number]; + + const value = params.get(key); + if (value !== null) { + target[key] = value; + } + } + + if (Object.keys(target).length > 0) { + urlParams[groupKey] = target; + } + } + + return { params: urlParams as URLParams, location: parsedFragment.location }; } diff --git a/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts b/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts index 8a5547e6e9..fa7697d084 100644 --- a/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts +++ b/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts @@ -199,8 +199,11 @@ export class RoomListHeaderViewModel SettingsStore.setValue("RoomList.showMessagePreview", null, SettingLevel.DEVICE, isMessagePreviewEnabled); this.snapshot.merge({ isMessagePreviewEnabled }); }; -} + public createSection = (): void => { + // To be implemented when custom section creation is added in vms + }; +} /** * Get the initial snapshot for the RoomListHeaderViewModel. * @param spaceStore - The space store instance. @@ -280,5 +283,8 @@ function computeHeaderSpaceState( displaySpaceMenu, canInviteInSpace, canAccessSpaceSettings, + // To be implemented when custom section creation is added in vms + canCreateSection: false, + useComposeIcon: true, }; } diff --git a/apps/web/src/viewmodels/room-list/RoomListItemViewModel.ts b/apps/web/src/viewmodels/room-list/RoomListItemViewModel.ts index b2f24da66d..ac3f32ad61 100644 --- a/apps/web/src/viewmodels/room-list/RoomListItemViewModel.ts +++ b/apps/web/src/viewmodels/room-list/RoomListItemViewModel.ts @@ -303,6 +303,8 @@ export class RoomListItemViewModel canMarkAsRead, canMarkAsUnread, roomNotifState, + // To be implemented when custom section creation is added in vms + canMoveToSection: false, }; } @@ -381,4 +383,8 @@ export class RoomListItemViewModel const echoChamber = EchoChamber.forRoom(this.props.room); echoChamber.notificationVolume = elementNotifState; }; + + public onCreateSection = (): void => { + // To be implemented when custom section creation is added in vms + }; } diff --git a/apps/web/src/viewmodels/room-list/RoomListViewModel.ts b/apps/web/src/viewmodels/room-list/RoomListViewModel.ts index d2ff465db9..a3e516ea2d 100644 --- a/apps/web/src/viewmodels/room-list/RoomListViewModel.ts +++ b/apps/web/src/viewmodels/room-list/RoomListViewModel.ts @@ -572,6 +572,12 @@ export class RoomListViewModel }); } }; + + public closeToast: () => void = () => { + this.snapshot.merge({ + toast: undefined, + }); + }; } /** diff --git a/apps/web/src/viewmodels/right-panel/WidgetContextMenuViewModel.tsx b/apps/web/src/viewmodels/room/right-panel/WidgetContextMenuViewModel.tsx similarity index 91% rename from apps/web/src/viewmodels/right-panel/WidgetContextMenuViewModel.tsx rename to apps/web/src/viewmodels/room/right-panel/WidgetContextMenuViewModel.tsx index 83f02a1c05..43932592fb 100644 --- a/apps/web/src/viewmodels/right-panel/WidgetContextMenuViewModel.tsx +++ b/apps/web/src/viewmodels/room/right-panel/WidgetContextMenuViewModel.tsx @@ -17,22 +17,22 @@ import { } from "@element-hq/web-shared-components"; import { type ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle"; -import ErrorDialog from "../../components/views/dialogs/ErrorDialog"; -import QuestionDialog from "../../components/views/dialogs/QuestionDialog"; -import MatrixClientContext from "../../contexts/MatrixClientContext"; -import { useScopedRoomContext } from "../../contexts/ScopedRoomContext"; -import { _t } from "../../languageHandler"; -import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../Livestream"; -import Modal from "../../Modal"; -import SettingsStore from "../../settings/SettingsStore"; -import { WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore"; -import { WidgetMessagingStore } from "../../stores/widgets/WidgetMessagingStore"; -import { isAppWidget } from "../../stores/WidgetStore"; -import WidgetUtils from "../../utils/WidgetUtils"; -import { WidgetType } from "../../widgets/WidgetType"; -import { ModuleRunner } from "../../modules/ModuleRunner"; -import { ElementWidget, type WidgetMessaging } from "../../stores/widgets/WidgetMessaging"; -import dis from "../../dispatcher/dispatcher"; +import ErrorDialog from "../../../components/views/dialogs/ErrorDialog"; +import QuestionDialog from "../../../components/views/dialogs/QuestionDialog"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext"; +import { _t } from "../../../languageHandler"; +import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../../Livestream"; +import Modal from "../../../Modal"; +import SettingsStore from "../../../settings/SettingsStore"; +import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; +import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; +import { isAppWidget } from "../../../stores/WidgetStore"; +import WidgetUtils from "../../../utils/WidgetUtils"; +import { WidgetType } from "../../../widgets/WidgetType"; +import { ModuleRunner } from "../../../modules/ModuleRunner"; +import { ElementWidget, type WidgetMessaging } from "../../../stores/widgets/WidgetMessaging"; +import dis from "../../../dispatcher/dispatcher"; const checkRevokeButtonState = ( cli: MatrixClient, diff --git a/apps/web/test/setupTests.ts b/apps/web/test/setupTests.ts index 7e68edf19d..f514c10482 100644 --- a/apps/web/test/setupTests.ts +++ b/apps/web/test/setupTests.ts @@ -66,3 +66,28 @@ if (env["GITHUB_ACTIONS"] !== undefined) { require("./setup/setupManualMocks"); // must be first require("./setup/setupLanguage"); require("./setup/setupConfig"); + +// Utility to check for React errors during the tests +// Fails tests on errors like the following: +// In HTML,
    cannot be a descendant of

    . +// In HTML,
    cannot be a descendant of . +// In HTML, text nodes cannot be a child of

    . +// This will cause a hydration error. +// You provided a `checked` prop to a form field without an `onChange` handler. +let errors: any[] = []; +beforeEach(() => { + errors = []; + const originalError = console.error; + jest.spyOn(console, "error").mockImplementation((...args) => { + if (/validateDOMNesting|Hydration failed|hydration error|prop to a form field without an/i.test(args[0])) { + errors.push(args[0]); + } + originalError.call(console, ...args); + }); +}); +afterEach(() => { + mocked(console.error).mockRestore?.(); + if (errors.length > 0) { + throw new Error("Test failed due to React hydration errors in the console."); + } +}); diff --git a/apps/web/test/unit-tests/Lifecycle-test.ts b/apps/web/test/unit-tests/Lifecycle-test.ts index a9336db114..adac08989d 100644 --- a/apps/web/test/unit-tests/Lifecycle-test.ts +++ b/apps/web/test/unit-tests/Lifecycle-test.ts @@ -169,7 +169,7 @@ describe("Lifecycle", () => { const prom = Lifecycle.loadSession({ enableGuest: true, guestHsUrl: "https://guest.server", - fragmentQueryParams: { guest_user_id: "a", guest_access_token: "b" }, + urlParams: { guest: { guest_user_id: "a", guest_access_token: "b" } }, abortSignal: abortController.signal, }); abortController.abort(); diff --git a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx index ac5c2bad9f..8287cc03c8 100644 --- a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -223,7 +223,7 @@ describe("", () => { }, onNewScreen: jest.fn(), onTokenLoginCompleted: jest.fn(), - realQueryParams: {}, + urlParams: {}, }; mockClient = getMockClientWithEventEmitter(getMockClientMethods()); @@ -320,9 +320,11 @@ describe("", () => { const code = "test-oidc-auth-code"; const state = "test-oidc-state"; - const realQueryParams = { - code, - state: state, + const urlParams = { + oidc_fragment: { + code, + state: state, + }, }; const deviceId = "test-device-id"; @@ -383,11 +385,13 @@ describe("", () => { }); it("should fail when query params do not include valid code and state", async () => { - const queryParams = { - code: 123, - state: "abc", + const urlParams = { + oidc_query: { + code: "", + state: "abc", + }, }; - getComponent({ realQueryParams: queryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -400,15 +404,15 @@ describe("", () => { }); it("should make correct request to complete authorization", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); - expect(completeAuthorizationCodeGrant).toHaveBeenCalledWith(code, state); + expect(completeAuthorizationCodeGrant).toHaveBeenCalledWith(code, state, "fragment"); }); it("should look up userId using access token", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -423,7 +427,7 @@ describe("", () => { it("should log error and return to welcome page when userId lookup fails", async () => { loginClient.whoami.mockRejectedValue(new Error("oups")); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -436,7 +440,7 @@ describe("", () => { it("should call onTokenLoginCompleted", async () => { const onTokenLoginCompleted = jest.fn(); - getComponent({ realQueryParams, onTokenLoginCompleted }); + getComponent({ urlParams, onTokenLoginCompleted }); await waitFor(() => expect(onTokenLoginCompleted).toHaveBeenCalled()); }); @@ -450,7 +454,7 @@ describe("", () => { mocked(completeAuthorizationCodeGrant).mockRejectedValue( new Error(OidcError.MissingOrInvalidStoredState), ); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -465,7 +469,7 @@ describe("", () => { }); it("should log and return to welcome page", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -479,7 +483,7 @@ describe("", () => { }); it("should not clear storage", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -488,7 +492,7 @@ describe("", () => { it("should not store clientId or issuer", async () => { const sessionStorageSetSpy = jest.spyOn(sessionStorage.__proto__, "setItem"); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -509,7 +513,7 @@ describe("", () => { }); it("should persist login credentials", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await waitFor(() => expect(localStorage.getItem("mx_device_id")).toEqual(deviceId)); expect(localStorage.getItem("mx_hs_url")).toEqual(homeserverUrl); @@ -518,14 +522,14 @@ describe("", () => { }); it("should store clientId and issuer in session storage", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await waitFor(() => expect(localStorage.getItem("mx_oidc_client_id")).toEqual(clientId)); await waitFor(() => expect(localStorage.getItem("mx_oidc_token_issuer")).toEqual(issuer)); }); it("should set logged in and start MatrixClient", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); defaultDispatcher.dispatch({ action: Action.WillStartClient, @@ -545,7 +549,7 @@ describe("", () => { jest.spyOn(Lifecycle, "attemptDelegatedAuthLogin"); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); expect(Lifecycle.attemptDelegatedAuthLogin).toHaveBeenCalled(); @@ -559,7 +563,7 @@ describe("", () => { jest.spyOn(Lifecycle, "attemptDelegatedAuthLogin"); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); expect(Lifecycle.attemptDelegatedAuthLogin).toHaveBeenCalled(); @@ -1120,8 +1124,10 @@ describe("", () => { describe("when query params have a loginToken", () => { const loginToken = "test-login-token"; - const realQueryParams = { - loginToken, + const urlParams = { + legacy_sso: { + loginToken, + }, }; let loginClient!: ReturnType; @@ -1150,7 +1156,7 @@ describe("", () => { mocked(loginClient.getCrypto()!.userHasCrossSigningKeys).mockResolvedValue(true); // When we load the page - getComponent({ realQueryParams }); + getComponent({ urlParams }); defaultDispatcher.dispatch({ action: Action.WillStartClient, @@ -1400,8 +1406,10 @@ describe("", () => { describe("when query params have a loginToken", () => { const loginToken = "test-login-token"; - const realQueryParams = { - loginToken, + const urlParams = { + legacy_sso: { + loginToken, + }, }; let loginClient!: ReturnType; @@ -1426,7 +1434,7 @@ describe("", () => { it("should show an error dialog when no homeserver is found in local storage", async () => { localStorage.removeItem("mx_sso_hs_url"); const localStorageGetSpy = jest.spyOn(localStorage.__proto__, "getItem"); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); expect(localStorageGetSpy).toHaveBeenCalledWith("mx_sso_hs_url"); @@ -1444,7 +1452,7 @@ describe("", () => { }); it("should attempt token login", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); expect(loginClient.login).toHaveBeenCalledWith("m.login.token", { @@ -1455,7 +1463,7 @@ describe("", () => { it("should call onTokenLoginCompleted", async () => { const onTokenLoginCompleted = jest.fn(); - getComponent({ realQueryParams, onTokenLoginCompleted }); + getComponent({ urlParams, onTokenLoginCompleted }); await waitFor(() => expect(onTokenLoginCompleted).toHaveBeenCalled()); }); @@ -1465,7 +1473,7 @@ describe("", () => { loginClient.login.mockRejectedValue(new Error("oups")); }); it("should show a dialog", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -1480,7 +1488,7 @@ describe("", () => { }); it("should not clear storage", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await flushPromises(); @@ -1502,7 +1510,7 @@ describe("", () => { it("should clear storage", async () => { const localStorageClearSpy = jest.spyOn(localStorage.__proto__, "clear"); - getComponent({ realQueryParams }); + getComponent({ urlParams }); // just check we called the clearStorage function await waitFor(() => expect(loginClient.clearStores).toHaveBeenCalled()); @@ -1511,7 +1519,7 @@ describe("", () => { }); it("should persist login credentials", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); await waitFor(() => expect(localStorage.getItem("mx_hs_url")).toEqual(serverConfig.hsUrl)); expect(localStorage.getItem("mx_user_id")).toEqual(userId); @@ -1521,7 +1529,7 @@ describe("", () => { it("should set fresh login flag in session storage", async () => { const sessionStorageSetSpy = jest.spyOn(sessionStorage.__proto__, "setItem"); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await waitFor(() => expect(sessionStorageSetSpy).toHaveBeenCalledWith("mx_fresh_login", "true")); }); @@ -1537,13 +1545,13 @@ describe("", () => { }, }; loginClient.login.mockResolvedValue(loginResponseWithWellKnown); - getComponent({ realQueryParams }); + getComponent({ urlParams }); await waitFor(() => expect(localStorage.getItem("mx_hs_url")).toEqual(hsUrlFromWk)); }); it("should continue to post login setup when no session is found in local storage", async () => { - getComponent({ realQueryParams }); + getComponent({ urlParams }); defaultDispatcher.dispatch({ action: Action.WillStartClient, }); @@ -1589,6 +1597,7 @@ describe("", () => { getComponent({ initialScreenAfterLogin: { screen: "start_sso", + params: {}, }, }); @@ -1604,6 +1613,7 @@ describe("", () => { getComponent({ initialScreenAfterLogin: { screen: "start_cas", + params: {}, }, }); diff --git a/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap b/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap index 12a7551687..9f8a89e59d 100644 --- a/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap +++ b/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap @@ -378,114 +378,115 @@ exports[`SpaceHierarchy renders 1`] = ` -
    - -
  • -
    -
    - - N - -
    -
    - - Nested room - -
    -
    - 3 members -
    -
    -
    -
    - Join -
    - -
    - +
    + + Nested room + +
    +
    + 3 members +
    +
    +
    +
    + Join +
    + +
    -
    - +
    + +
    +
    +
    -
    -
    -
    - -
    -
    -
    + + +
  • + + +
  • should not render cycles 1`] = ` -
    diff --git a/apps/web/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap b/apps/web/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap index e144597f9c..4ceeac06b9 100644 --- a/apps/web/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap @@ -19,14 +19,14 @@ exports[` should render 1`] = ` > Did not receive it? - +

    diff --git a/apps/web/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap b/apps/web/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap index 1cc41fd9f3..0ea520e24f 100644 --- a/apps/web/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap @@ -54,10 +54,10 @@ exports[` when a beacon is live and has locations renders beac /> -
    -
    when a beacon is live and has locations renders beac d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
    -
    + + renders sidebar correctly with beacons 1`] = ` /> -
    -
    renders sidebar correctly with beacons 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
    -
    + +
    renders share buttons when there is a location /> -
    -
    renders share buttons when there is a location d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
    -
    + +
    `; diff --git a/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap b/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap index b165dc24b6..721a650ed3 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap @@ -29,11 +29,11 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = ` > Toolbox -
    Room ID: !id -
    -
    -
    + +
    diff --git a/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Crypto-test.tsx.snap b/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Crypto-test.tsx.snap index 4dfa8ecffc..5808e26460 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Crypto-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Crypto-test.tsx.snap @@ -5,7 +5,13 @@ exports[` should display when the cross-signing data aria-label="Cross-signing" >
  • - Cross-signing + + + @@ -77,7 +83,13 @@ exports[` should display when the cross-signing data aria-label="Cross-signing" > - Cross-signing + + + @@ -149,7 +161,13 @@ exports[` should display when the key storage data are aria-label="Key Storage" > - Key Storage + + + @@ -221,7 +239,13 @@ exports[` should display when the key storage data are aria-label="Key Storage" > - Key Storage + + + diff --git a/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap b/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap index 10915b5222..65510b96e5 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap @@ -7,11 +7,11 @@ exports[` should render a single device - signed by owner 1`] = ` >
    • -
      User ID: @alice:example.com -
      should render a single device - signed by owner 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
      -
      + +
    • -
      Device ID: SIGNED -
      should render a single device - signed by owner 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
      -
      + +
    • Displayname: @@ -95,11 +95,11 @@ exports[` should render a single device - signed by owner 1`] = ` Device keys
      • -
        ed25519: an_ed25519_public_key -
        should render a single device - signed by owner 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
        -
        + +
      • -
        curve25519: a_curve25519_public_key -
        should render a single device - signed by owner 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
        -
        + +
    • @@ -171,11 +171,11 @@ exports[` should render a single device - unsigned 1`] = ` >
      • -
        User ID: @alice:example.com -
        should render a single device - unsigned 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
        -
        + +
      • -
        Device ID: UNSIGNED -
        should render a single device - unsigned 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
        -
        + +
      • Displayname: @@ -259,11 +259,11 @@ exports[` should render a single device - unsigned 1`] = ` Device keys
        • -
          ed25519: an_ed25519_public_key -
          should render a single device - unsigned 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
          -
          + +
        • -
          curve25519: a_curve25519_public_key -
          should render a single device - unsigned 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
          -
          + +
      • @@ -335,11 +335,11 @@ exports[` should render a single device - verified by cross-signing 1`] >
        • -
          User ID: @alice:example.com -
          should render a single device - verified by cross-signing 1`] d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
          -
          + +
        • -
          Device ID: VERIFIED -
          should render a single device - verified by cross-signing 1`] d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
          -
          + +
        • Displayname: @@ -425,11 +425,11 @@ exports[` should render a single device - verified by cross-signing 1`] Device keys
          • -
            ed25519: an_ed25519_public_key -
            should render a single device - verified by cross-signing 1`] d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            -
            + +
          • -
            curve25519: a_curve25519_public_key -
            should render a single device - verified by cross-signing 1`] d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            -
            + +
        • @@ -501,11 +501,11 @@ exports[` should render a single user 1`] = ` >
          • -
            User ID: @alice:example.com -
            should render a single user 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            -
            + +
          • Membership: join diff --git a/apps/web/test/unit-tests/components/views/elements/__snapshots__/LearnMore-test.tsx.snap b/apps/web/test/unit-tests/components/views/elements/__snapshots__/LearnMore-test.tsx.snap index 4191518f03..01fb8177b2 100644 --- a/apps/web/test/unit-tests/components/views/elements/__snapshots__/LearnMore-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/elements/__snapshots__/LearnMore-test.tsx.snap @@ -2,13 +2,13 @@ exports[` renders button 1`] = `
            - +
            `; diff --git a/apps/web/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap b/apps/web/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap index 2cd8c984e3..4742e588e2 100644 --- a/apps/web/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap @@ -59,7 +59,7 @@ exports[` renders formatted m.text correctly linkification is not -
            renders formatted m.text correctly linkification is not d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            + @@ -278,7 +278,7 @@ exports[` renders formatted m.text correctly pills do not appear -
            renders formatted m.text correctly pills do not appear d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            + @@ -482,7 +482,7 @@ num_sqrt = num ** /> -
            -
            + diff --git a/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap b/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap index e03696b45c..c997e478e5 100644 --- a/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap @@ -99,11 +99,11 @@ exports[` with crypto enabled renders 1`] = `

            -

            customUserIdentifier -
            with crypto enabled renders 1`] = ` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            -
            + +

            with crypto enabled should render a deactivate button for

            -

            customUserIdentifier -
            with crypto enabled should render a deactivate button for d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            -
            + +

            renders custom user identifiers in the header 1`

            -

            customUserIdentifier -
            renders custom user identifiers in the header 1` d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z" /> -
            -
            + +

            await new Promise((resolve) => window.setTimeout(resolve)); describe("", () => { - const getComponent = () => - render( - - - , - ); + const getComponent = () => render(); // get component, wait for async data and force a render const getComponentAndWait = async () => { diff --git a/apps/web/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap b/apps/web/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap index d971d9001b..357f441198 100644 --- a/apps/web/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap @@ -42,74 +42,74 @@ exports[` main notification switches renders only enable notifi
            -
            + +
            +
            - -
            -
            -
            -
            - + class="_ui_udcm8_34" + />
            + +
            +
            +
            +
            +
            - -
            -
            -
            -
            - + class="_ui_udcm8_34" + />
            - +
            + +
            +
            `; diff --git a/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap b/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap index fdd0dbe0b0..fde728da77 100644 --- a/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap @@ -57,13 +57,13 @@ HTMLCollection [ class="mx_DeviceSecurityCard_description" > Verify your current session for enhanced secure messaging. - +

            renders device and correct security card when class="mx_DeviceSecurityCard_description" > Verify your current session for enhanced secure messaging. - +

            renders device and correct security card when class="mx_DeviceSecurityCard_description" > Verify your current session for enhanced secure messaging. - +

            displays name edit form on rename button click id="device-rename-description-123" > Please be aware that session names are also visible to people you communicate with. - +
            renders a verified device 1`] = ` class="mx_DeviceSecurityCard_description" > This session is ready for secure messaging. - +

            @@ -175,13 +175,13 @@ exports[` renders device with metadata 1`] = ` class="mx_DeviceSecurityCard_description" > Verify or remove this session for best security and reliability. - +

            @@ -391,13 +391,13 @@ exports[` renders device without metadata 1`] = ` class="mx_DeviceSecurityCard_description" > Verify or remove this session for best security and reliability. - +

            diff --git a/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceVerificationStatusCard-test.tsx.snap b/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceVerificationStatusCard-test.tsx.snap index 0f7b56b998..953bf16969 100644 --- a/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceVerificationStatusCard-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceVerificationStatusCard-test.tsx.snap @@ -34,13 +34,13 @@ exports[` renders a verified device 1`] = ` class="mx_DeviceSecurityCard_description" > This session is ready for secure messaging. - +

            @@ -79,13 +79,13 @@ exports[` renders an unverifiable device 1`] = ` class="mx_DeviceSecurityCard_description" > This session doesn't support encryption and thus can't be verified. - +

            @@ -124,13 +124,13 @@ exports[` renders an unverified device 1`] = ` class="mx_DeviceSecurityCard_description" > Verify or remove this session for best security and reliability. - +

            Consider removing old sessions (90 days or older) you don't use anymore. - +

            @@ -90,13 +90,13 @@ HTMLCollection [ > Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore. - +

            @@ -143,13 +143,13 @@ HTMLCollection [ > For best security, remove any session that you don't recognize or use anymore. - +

            diff --git a/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap b/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap index c0a36f643f..92f8280f73 100644 --- a/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap @@ -57,13 +57,13 @@ exports[` renders both cards when user has both unver class="mx_DeviceSecurityCard_description" > Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore. - +

            renders both cards when user has both unver class="mx_DeviceSecurityCard_description" > Consider removing old sessions (90 days or older) you don't use anymore. - +

            renders inactive devices section when user class="mx_DeviceSecurityCard_description" > Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore. - +

            renders inactive devices section when user class="mx_DeviceSecurityCard_description" > Consider removing old sessions (90 days or older) you don't use anymore. - +

            renders unverified devices section when use class="mx_DeviceSecurityCard_description" > Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore. - +

            renders unverified devices section when use class="mx_DeviceSecurityCard_description" > Consider removing old sessions (90 days or older) you don't use anymore. - +

            ", () => { const screen = render( - - - + , ); await act(waitForUpdate); @@ -109,9 +106,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(async () => { @@ -172,9 +167,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -190,9 +183,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -230,9 +221,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -267,9 +256,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -298,9 +285,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -325,9 +310,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -349,9 +332,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -379,9 +360,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -405,9 +384,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -429,9 +406,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -459,9 +434,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -485,9 +458,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -514,9 +485,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -535,9 +504,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -648,9 +615,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -709,9 +674,7 @@ describe("", () => { const user = userEvent.setup(); const screen = render( - - - + , ); await act(waitForUpdate); @@ -731,9 +694,7 @@ describe("", () => { const { container } = render( - - - + , ); await waitForUpdate(); @@ -760,9 +721,7 @@ describe("", () => { const user = userEvent.setup(); const { container } = render( - - - + , ); await waitForUpdate(); diff --git a/apps/web/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap b/apps/web/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap index dc9f33ced2..ff7f965b4a 100644 --- a/apps/web/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap @@ -2,880 +2,880 @@ exports[` correctly handles the loading/disabled state 1`] = `
            -
            -
            -
            - -
            -
            -
            -
            - -
            -
            -
            -
            -
            - -
            -
            -
            -
            - -
            -
            -
            -
            -
            - -
            -
            -
            -
            - -
            -
            -
            -
            -
            - -
            -
            -
            -
            - -
            -
            -
            -
            -
            -

            - I want to be notified for (Default Setting) -

            -
            -
            -
            - This setting will be applied by default to all your rooms. -
            -
            -
            -
            +
            +
            +
            -
            -
            -
            - People, Mentions and Keywords -
            -
            - +
            +
            +
            +
            +
            +
            +
            +
            -
            -
            -
            - Mentions and Keywords only -
            -
            +
            +
            +
            +
            -

            - Play a sound for -

            -
            -
            - Applied by default to all rooms on all devices. + +
            -
            - -
            -
            -
            - -
            - -
            -
            -
            -
            - -
            -
            - -
            -
            -
            -
            -
            -
            - -
            - -
            -
            -
            -
            - -
            -
            - -
            -
            -
            -
            -
            -
            - -
            - -
            -
            -
            -
            - -
            -
            - -
            + Enable audible notifications for this session + +
            +
            + +
            +
            +

            + I want to be notified for (Default Setting) +

            +
            +
            +
            + This setting will be applied by default to all your rooms.
            +
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - RA -
            -
            +
            - Random +
            + Random +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - EN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - DE -
            -
            +
            - Design +
            + Design +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - MA -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - SA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - SU -
            -
            +
            - Support +
            + Support +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - AN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - OF -
            -
            +
            - Off-topic +
            + Off-topic +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - TE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - TE -
            -
            +
            - Team Beta +
            + Team Beta +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            +
            - Project Y +
            + Project Y +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - WA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - FE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - ID -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - BU -
            -
            +
            - Bugs +
            + Bugs +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - FE -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - RE -
            -
            +
            - Releases +
            + Releases +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - + +
            @@ -2728,66 +2734,71 @@ exports[` > renders Empty story 1`] = `
            - - No chats yet - - - Get started by messaging someone or by creating a room -
            - - + + Start chat + + +
            @@ -2834,20 +2845,25 @@ exports[` > renders EmptyFavouriteFilter story 1`] = `
            - - You don't have favourite chats yet - - - You can add a chat to your favourites in the chat settings - + + You don't have favourite chats yet + + + You can add a chat to your favourites in the chat settings + +
            @@ -2893,24 +2909,29 @@ exports[` > renders EmptyInvitesFilter story 1`] = `
            - - You don't have any unread invites - - + + You don't have any unread invites + + +
            @@ -2956,24 +2977,29 @@ exports[` > renders EmptyLowPriorityFilter story 1`] = `
            - - You don't have any low priority rooms - - + + You don't have any low priority rooms + + +
            @@ -3019,24 +3045,29 @@ exports[` > renders EmptyMentionsFilter story 1`] = `
            - - You don't have any unread mentions - - + + You don't have any unread mentions + + +
            @@ -3082,20 +3113,25 @@ exports[` > renders EmptyPeopleFilter story 1`] = `
            - - You don’t have direct chats with anyone yet - - - You can deselect filters in order to see your other chats - + + You don’t have direct chats with anyone yet + + + You can deselect filters in order to see your other chats + +
            @@ -3141,20 +3177,25 @@ exports[` > renders EmptyRoomsFilter story 1`] = `
            - - You’re not in any room yet - - - You can deselect filters in order to see your other chats - + + You’re not in any room yet + + + You can deselect filters in order to see your other chats + +
            @@ -3200,24 +3241,29 @@ exports[` > renders EmptyUnreadFilter story 1`] = `
            - - Congrats! You don’t have any unread messages - - + + Congrats! You don’t have any unread messages + + +
            @@ -3281,45 +3327,50 @@ exports[` > renders EmptyWithoutCreatePermission story 1`] = `
            - - No chats yet - - - Get started by messaging someone -
            - + + Start chat + +
            @@ -3384,4830 +3435,4836 @@ exports[` > renders LargeFlatList story 1`] = `
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - RA -
            -
            +
            - Random +
            + Random +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - - -
            -
            +
            -
            - EN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - DE -
            -
            +
            - Design +
            + Design +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - MA -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - SA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - SU -
            -
            +
            - Support +
            + Support +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - AN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - OF -
            -
            +
            - Off-topic +
            + Off-topic +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - TE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - TE -
            -
            +
            - Team Beta +
            + Team Beta +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            +
            - Project Y +
            + Project Y +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - WA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - FE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - ID -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - BU -
            -
            +
            - Bugs +
            + Bugs +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - FE -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - RE -
            -
            +
            - Releases +
            + Releases +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - GE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - RA -
            -
            +
            - Random +
            + Random +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - EN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - DE -
            -
            +
            - Design +
            + Design +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - MA -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - SA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - SU -
            -
            +
            - Support +
            + Support +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - AN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - OF -
            -
            +
            - Off-topic +
            + Off-topic +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - TE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - TE -
            -
            +
            - Team Beta +
            + Team Beta +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            +
            - Project Y +
            + Project Y +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - WA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - FE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - ID -
            -
            - - +
            +
            + + +
            + - -
            - + +
            @@ -8274,4824 +8331,4830 @@ exports[` > renders LargeSectionList story 1`] = `
            - -
            -
            -
            -
            - +
            +
            +
            +
            +
            +
            + + +
            + -
            -
            - + +
            - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            -
            +
            + + +
            + -
            - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            -
            +
            + + +
            + -
            - - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            -
            +
            + + +
            + -
            - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            -
            +
            + + +
            + -
            - - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - + + + + + Chats + +
            + +
            - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            -
            +
            + + +
            + -
            - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            -
            +
            + + +
            + -
            - - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + - -
            - - +
            - + + + +
            + + + - - - + + - -
            - - +
            +
            + + +
            + - -
            - + + @@ -13159,8 +13222,13 @@ exports[` > renders Loading story 1`] = `
            + class="Flex-module_flex RoomListView-module_list" + style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;" + > +
            +
            `; @@ -13223,291 +13291,297 @@ exports[` > renders SmallFlatList story 1`] = `
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - RA -
            -
            +
            - Random +
            + Random +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - + + @@ -13574,87 +13648,501 @@ exports[` > renders SmallSectionList story 1`] = `
            - +
            +
            +
            +
            + +
            + GE +
            +
            +
            +
            + General +
            +
            + Last message in General +
            +
            +
            + + +
            + +
            + +
            +
            +
            +
            + + +
            + +
            + +
            +
            +
            +
            + +
            + + + + + +`; + +exports[` > renders Toast story 1`] = ` +
            +
            +
            +
            +
            + + + + +
            +
            +
            +
            +
            +
            -
            -
            +
            + +
            + +
            +
            + + +
            + +
            + +
            +
            + + +
            + +
            + + +
            + + +
            + + + + +
            + + +
            + + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + +
            -
            -
            - -
            -
            +
            +
            + + Section created +
            + +
            @@ -14028,2610 +16831,2616 @@ exports[` > renders WithActiveFilter story 1`] = `
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - RA -
            -
            +
            - Random +
            + Random +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - - -
            -
            +
            -
            - EN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - DE -
            -
            +
            - Design +
            + Design +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - MA -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - SA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - SU -
            -
            +
            - Support +
            + Support +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - AN -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - OF -
            -
            +
            - Off-topic +
            + Off-topic +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - TE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - TE -
            -
            +
            - Team Beta +
            + Team Beta +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - PR -
            -
            +
            - Project Y +
            + Project Y +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - WA -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - FE -
            -
            -
            +
            + + +
            + -
            -
            - -
            -
            -
            +
            -
            - ID -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - BU -
            -
            +
            - Bugs +
            + Bugs +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - -
            -
            -
            +
            -
            - FE -
            -
            - - +
            +
            + + +
            + - -
            - -
            -
            -
            +
            -
            - RE -
            -
            +
            - Releases +
            + Releases +
            -
            -
            - - +
            - + + + +
            + +
            + - -
            - + +
            diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.test.tsx b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.test.tsx index ac13f7874e..bbf0fcc63a 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.test.tsx +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.test.tsx @@ -10,7 +10,7 @@ import { render, screen } from "@test-utils"; import userEvent from "@testing-library/user-event"; import { describe, it, expect, vi } from "vitest"; -import { RoomListItemMoreOptionsMenu } from "./RoomListItemMoreOptionsMenu"; +import { RoomListItemMoreOptionsMenu, MoreOptionContent } from "./RoomListItemMoreOptionsMenu"; import { useMockedViewModel } from "../../../../core/viewmodel"; import type { RoomListItemViewSnapshot } from "./RoomListItemView"; import { defaultSnapshot } from "./default-snapshot"; @@ -26,6 +26,7 @@ describe("", () => { onCopyRoomLink: vi.fn(), onLeaveRoom: vi.fn(), onSetRoomNotifState: vi.fn(), + onCreateSection: vi.fn(), }; const renderMenu = (overrides: Partial = {}): ReturnType => { @@ -224,4 +225,19 @@ describe("", () => { expect(mockCallbacks.onLeaveRoom).toHaveBeenCalled(); }); + + it("should call onCreateSection when new section is clicked", async () => { + const user = userEvent.setup(); + // We need to render the MoreOptionContent directly here as radix is kind of messing in the test env + const TestComponent = (): JSX.Element => { + const vm = useMockedViewModel(defaultSnapshot, mockCallbacks); + return ; + }; + render(); + + const newSection = screen.getByRole("menuitem", { name: "New section" }); + await user.click(newSection); + + expect(mockCallbacks.onCreateSection).toHaveBeenCalled(); + }); }); diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.tsx b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.tsx index 8a6286e01c..e03504ad01 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.tsx +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemMoreOptionsMenu.tsx @@ -6,7 +6,7 @@ */ import React, { useState, type JSX } from "react"; -import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem } from "@vector-im/compound-web"; +import { IconButton, Menu, MenuItem, Separator, SubMenu, ToggleMenuItem } from "@vector-im/compound-web"; import { MarkAsReadIcon, MarkAsUnreadIcon, @@ -16,6 +16,7 @@ import { LinkIcon, LeaveIcon, OverflowHorizontalIcon, + ArrowRightIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../../core/i18n/i18n"; @@ -106,6 +107,7 @@ export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element { onSelect={vm.onToggleLowPriority} onClick={(evt) => evt.stopPropagation()} /> + {snapshot.canInvite && ( )} + {snapshot.canMoveToSection && ( + + } + > + + + )} ", () => { onCopyRoomLink: vi.fn(), onLeaveRoom: vi.fn(), onSetRoomNotifState: vi.fn(), + onCreateSection: vi.fn(), }; const renderMenu = (roomNotifState: RoomNotifState = RoomNotifState.AllMessages): ReturnType => { diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemView.stories.tsx b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemView.stories.tsx index c6c7986ffb..165d38d267 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemView.stories.tsx +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/RoomListItemView.stories.tsx @@ -38,6 +38,7 @@ const RoomListItemWrapperImpl = ({ onCopyRoomLink, onLeaveRoom, onSetRoomNotifState, + onCreateSection, isSelected, isFocused, onFocus, @@ -56,6 +57,7 @@ const RoomListItemWrapperImpl = ({ onCopyRoomLink, onLeaveRoom, onSetRoomNotifState, + onCreateSection, }); return ( void; /** Called when setting the room notification state */ onSetRoomNotifState: (state: RoomNotifState) => void; + /** Called when creating a new section */ + onCreateSection: () => void; } /** diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/default-snapshot.ts b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/default-snapshot.ts index 2ec961ff98..f01243eb09 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/default-snapshot.ts +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/default-snapshot.ts @@ -36,4 +36,5 @@ export const defaultSnapshot: RoomListItemViewSnapshot = { canMarkAsRead: false, canMarkAsUnread: true, roomNotifState: RoomNotifState.AllMessages, + canMoveToSection: true, }; diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/mocked-actions.ts b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/mocked-actions.ts index bda12ac114..806e2e76ff 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/mocked-actions.ts +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView/mocked-actions.ts @@ -19,4 +19,5 @@ export const mockedActions: RoomListItemViewActions = { onCopyRoomLink: fn(), onLeaveRoom: fn(), onSetRoomNotifState: fn(), + onCreateSection: fn(), }; diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.module.css b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.module.css index c444c8c1cd..e42f1fbaca 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.module.css +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.module.css @@ -9,6 +9,5 @@ * Room list container styles */ .roomList { - height: 100%; width: 100%; } diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.stories.tsx b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.stories.tsx index a5048b5c73..b1c312cee0 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.stories.tsx +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.stories.tsx @@ -34,6 +34,7 @@ const RoomListWrapperImpl = ({ getRoomItemViewModel, getSectionHeaderViewModel, updateVisibleRooms, + closeToast, renderAvatar: renderAvatarProp, ...rest }: RoomListStoryProps): JSX.Element => { @@ -44,6 +45,7 @@ const RoomListWrapperImpl = ({ getRoomItemViewModel, getSectionHeaderViewModel, updateVisibleRooms, + closeToast, }); return ( @@ -82,6 +84,7 @@ const meta = { updateVisibleRooms: fn(), renderAvatar, isFlatList: true, + closeToast: fn(), }, parameters: { design: { diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx index ad6b105e05..180723ba44 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx @@ -21,6 +21,7 @@ import type { RoomListViewSnapshot, RoomListViewModel } from "../RoomListView"; import { GroupedVirtualizedList } from "../../core/VirtualizedList"; import { RoomListSectionHeaderView } from "./RoomListSectionHeaderView"; import { RoomListItemAccessibilityWrapper } from "./RoomListItemAccessibilityWrapper"; +import styles from "./VirtualizedRoomListView.module.css"; /** * Filter key type - opaque string type for filter identifiers @@ -350,6 +351,7 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual rangeChanged, onKeyDown, increaseViewportBy, + className: styles.roomList, }; if (isFlatList) { diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/__snapshots__/VirtualizedRoomListView.test.tsx.snap b/packages/shared-components/src/room-list/VirtualizedRoomListView/__snapshots__/VirtualizedRoomListView.test.tsx.snap index 6b64e96ad3..03e092feff 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/__snapshots__/VirtualizedRoomListView.test.tsx.snap +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/__snapshots__/VirtualizedRoomListView.test.tsx.snap @@ -10,6 +10,7 @@ exports[` > renders Default story 1`] = ` >
            ): JSX.Element => { + return ( +
              + +
            + ); +}; + +const meta = { + title: "Timeline/ReadMarker", + component: ReadMarkerWrapper, + tags: ["autodocs"], + args: { + eventId: "$event", + kind: "current", + showLine: true, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Current: Story = {}; + +export const HiddenCurrent: Story = { + args: { + showLine: false, + }, +}; + +export const Ghost: Story = { + args: { + kind: "ghost", + }, +}; diff --git a/packages/shared-components/src/room/timeline/ReadMarker/ReadMarker.test.tsx b/packages/shared-components/src/room/timeline/ReadMarker/ReadMarker.test.tsx new file mode 100644 index 0000000000..37d8ad186f --- /dev/null +++ b/packages/shared-components/src/room/timeline/ReadMarker/ReadMarker.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Element Creations 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 { composeStories } from "@storybook/react-vite"; +import { fireEvent, render, screen } from "@test-utils"; +import React from "react"; +import { describe, expect, it, vi } from "vitest"; + +import { ReadMarker } from "./ReadMarker"; +import * as stories from "./ReadMarker.stories"; + +const { Current, HiddenCurrent, Ghost } = composeStories(stories); + +describe("ReadMarker", () => { + it("renders the current read marker", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders the hidden current read marker without a line", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + expect(container.querySelector("hr")).toBeNull(); + }); + + it("renders the ghost read marker", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("applies custom className to the list item", () => { + render( +
              + +
            , + ); + + const item = screen.getByRole("listitem"); + expect(item).toHaveClass("custom-read-marker", "compatibility-class"); + expect(item).toHaveAttribute("data-scroll-tokens", "$event"); + }); + + it("wires ghost marker actions", () => { + const onGhostLineRef = vi.fn(); + const onGhostTransitionEnd = vi.fn(); + + render( +
              + +
            , + ); + + const line = screen.getByRole("separator"); + fireEvent.transitionEnd(line); + + expect(onGhostLineRef).toHaveBeenCalled(); + expect(onGhostTransitionEnd).toHaveBeenCalledTimes(1); + }); + + it("wires the current marker ref", () => { + const onCurrentMarkerRef = vi.fn(); + + render( +
              + +
            , + ); + + expect(onCurrentMarkerRef).toHaveBeenCalled(); + }); +}); diff --git a/packages/shared-components/src/room/timeline/ReadMarker/ReadMarker.tsx b/packages/shared-components/src/room/timeline/ReadMarker/ReadMarker.tsx new file mode 100644 index 0000000000..48396b14cd --- /dev/null +++ b/packages/shared-components/src/room/timeline/ReadMarker/ReadMarker.tsx @@ -0,0 +1,80 @@ +/* + * Copyright 2026 Element Creations 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 React, { type JSX, type RefCallback, type TransitionEventHandler } from "react"; +import classNames from "classnames"; + +import styles from "./ReadMarker.module.css"; + +export type ReadMarkerKind = "current" | "ghost"; + +export interface ReadMarkerProps { + /** + * The event ID this marker is associated with. + */ + eventId: string; + /** + * Whether this is the active read marker or a ghost marker transitioning out. + */ + kind: ReadMarkerKind; + /** + * Whether the visible line should be rendered for the active marker. + * Hidden active markers still render the host `
          • ` to preserve layout calculations. + */ + showLine?: boolean; + /** + * Ref callback for the active read marker `
          • `. + */ + onCurrentMarkerRef?: RefCallback; + /** + * Ref callback for the ghost marker `
            `. + */ + onGhostLineRef?: RefCallback; + /** + * Transition-end handler for the ghost marker `
            `. + */ + onGhostTransitionEnd?: TransitionEventHandler; + /** + * Optional CSS className for the outer list item. + */ + className?: string; +} + +export function ReadMarker({ + eventId, + kind, + showLine = true, + onCurrentMarkerRef, + onGhostLineRef, + onGhostTransitionEnd, + className, +}: Readonly): JSX.Element { + let line: JSX.Element | null = null; + + if (kind === "ghost") { + line = ( +
            + ); + } else if (showLine) { + line =
            ; + } + + return ( +
          • + {line} +
          • + ); +} diff --git a/packages/shared-components/src/room/timeline/ReadMarker/__snapshots__/ReadMarker.test.tsx.snap b/packages/shared-components/src/room/timeline/ReadMarker/__snapshots__/ReadMarker.test.tsx.snap new file mode 100644 index 0000000000..1edd576858 --- /dev/null +++ b/packages/shared-components/src/room/timeline/ReadMarker/__snapshots__/ReadMarker.test.tsx.snap @@ -0,0 +1,42 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ReadMarker > renders the current read marker 1`] = ` +
            +
              +
            • +
              +
            • +
            +
            +`; + +exports[`ReadMarker > renders the ghost read marker 1`] = ` +
            +
              +
            • +
              +
            • +
            +
            +`; + +exports[`ReadMarker > renders the hidden current read marker without a line 1`] = ` +
            +
              +
            • +
            +
            +`; diff --git a/packages/shared-components/src/room/timeline/ReadMarker/index.tsx b/packages/shared-components/src/room/timeline/ReadMarker/index.tsx new file mode 100644 index 0000000000..b172c3a943 --- /dev/null +++ b/packages/shared-components/src/room/timeline/ReadMarker/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright 2026 Element Creations 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. + */ + +export { ReadMarker, type ReadMarkerKind, type ReadMarkerProps } from "./ReadMarker"; diff --git a/packages/shared-components/src/room/timeline/ReadMarkerView/.gitkeep b/packages/shared-components/src/room/timeline/ReadMarkerView/.gitkeep deleted file mode 100644 index 8b13789179..0000000000 --- a/packages/shared-components/src/room/timeline/ReadMarkerView/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/.gitkeep b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/.gitkeep deleted file mode 100644 index 8b13789179..0000000000 --- a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.module.css b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.module.css new file mode 100644 index 0000000000..05d56d3987 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.module.css @@ -0,0 +1,144 @@ +/* + * Copyright 2026 Element Creations 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. + */ + +.root { + display: flex; + flex-direction: column; + gap: var(--cpd-space-2x); + min-width: var(--cpd-space-0x); +} + +.link { + display: block; + width: fit-content; + color: inherit; + text-decoration: none; +} + +.thumbnailContainer { + position: relative; + overflow: hidden; + contain: paint; + border-radius: var(--MBody-border-radius); +} + +.placeholder { + position: absolute; + inset: var(--cpd-space-0x); + display: flex; + align-items: center; + justify-content: center; + background-color: var(--cpd-color-bg-canvas-default); + z-index: 1; +} + +.placeholderBlurhash { + background-color: transparent; +} + +.blurhash { + width: 100%; + height: 100%; +} + +.blurhash > canvas { + width: 100%; + height: 100%; + animation: blurhashPulse 1.75s infinite cubic-bezier(0.4, 0, 0.6, 1); +} + +.mediaContent { + position: relative; + max-width: 100%; + max-height: 100%; +} + +.image { + display: block; + width: 100%; + height: 100%; +} + +.banner { + position: absolute; + bottom: var(--cpd-space-2x); + left: var(--cpd-space-2x); + max-width: min(100%, 350px); + overflow: hidden; + padding: var(--cpd-space-1x); + border-radius: var(--MBody-border-radius); + background-color: rgb(0 0 0 / 0.6); + color: #fff; + text-overflow: ellipsis; + white-space: nowrap; + font: var(--cpd-font-body-sm-regular); + user-select: none; + pointer-events: none; +} + +.gifLabel { + position: absolute; + display: block; + top: var(--cpd-space-0x); + left: 14px; /* Preserve the original GIF badge offset from _MImageBody.pcss. */ + padding: 5px; /* Preserve the original GIF badge padding from _MImageBody.pcss. */ + border-radius: 5px; /* Preserve the original GIF badge corner radius from _MImageBody.pcss. */ + background: rgba(0, 0, 0, 0.7); + border: 2px solid rgba(0, 0, 0, 0.2); + color: rgba(255, 255, 255, 1); + pointer-events: none; +} + +.hiddenButton { + border: none; + width: 100%; + height: 100%; + padding: var(--cpd-space-0x); + inset: var(--cpd-space-0x); + display: flex; + align-items: center; + justify-content: center; + text-align: center; + cursor: pointer; + background-color: var(--cpd-color-bg-subtle-secondary); +} + +.hiddenButton:hover, +.hiddenButton:focus-visible { + background-color: var(--cpd-color-bg-canvas-default); +} + +.hiddenButtonContent { + display: flex; + color: var(--cpd-color-text-action-accent); +} + +.hiddenButtonContent > svg { + margin-top: auto; + margin-bottom: auto; +} + +.error { + display: block; + color: var(--cpd-color-text-critical-primary); +} + +.errorIcon { + margin-right: var(--cpd-space-1x); + vertical-align: text-top; +} + +@keyframes blurhashPulse { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.65; + } +} diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.stories.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.stories.tsx new file mode 100644 index 0000000000..3b6c66cf38 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.stories.tsx @@ -0,0 +1,157 @@ +/* + * Copyright 2026 Element Creations 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 React, { type ReactNode } from "react"; +import { expect, fn, userEvent, within } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useMockedViewModel } from "../../../../../core/viewmodel/useMockedViewModel"; +import { withViewDocs } from "../../../../../../.storybook/withViewDocs"; +import { + ImageBodyView, + ImageBodyViewPlaceholder, + ImageBodyViewState, + type ImageBodyViewActions, + type ImageBodyViewSnapshot, +} from "./ImageBodyView"; + +const imageSrc = new URL("../../../../../../static/image-body/install-spinner.png", import.meta.url).href; +const thumbnailSrc = new URL("../../../../../../static/image-body/install-spinner.png", import.meta.url).href; +const animatedGifSrc = new URL("../../../../../../static/image-body/install-spinner.gif", import.meta.url).href; +const demoBlurhash = "LEHV6nWB2yk8pyo0adR*.7kCMdnj"; +const imageBodyViewStateOptions = [ImageBodyViewState.ERROR, ImageBodyViewState.HIDDEN, ImageBodyViewState.READY]; +const imageBodyViewPlaceholderOptions = [ + ImageBodyViewPlaceholder.NONE, + ImageBodyViewPlaceholder.SPINNER, + ImageBodyViewPlaceholder.BLURHASH, +]; + +type ImageBodyViewProps = ImageBodyViewSnapshot & + ImageBodyViewActions & { + className?: string; + children?: ReactNode; + }; + +const ImageBodyViewWrapperImpl = ({ + onLinkClick, + onHiddenButtonClick, + onImageLoad, + onImageError, + className, + children, + ...snapshotProps +}: ImageBodyViewProps): ReactNode => { + const vm = useMockedViewModel(snapshotProps, { + onLinkClick: onLinkClick ?? fn(), + onHiddenButtonClick: onHiddenButtonClick ?? fn(), + onImageLoad: onImageLoad ?? fn(), + onImageError: onImageError ?? fn(), + }); + + return ( + + {children} + + ); +}; + +const ImageBodyViewWrapper = withViewDocs(ImageBodyViewWrapperImpl, ImageBodyView); + +const meta = { + title: "MessageBody/ImageBodyView", + component: ImageBodyViewWrapper, + tags: ["autodocs"], + argTypes: { + state: { + options: imageBodyViewStateOptions, + control: { type: "select" }, + }, + placeholder: { + options: imageBodyViewPlaceholderOptions, + control: { type: "select" }, + }, + className: { control: "text" }, + }, + args: { + state: ImageBodyViewState.READY, + alt: "Element logo", + hiddenButtonLabel: "Show image", + errorLabel: "Unable to show image due to error", + src: imageSrc, + thumbnailSrc, + showAnimatedContentOnHover: false, + placeholder: ImageBodyViewPlaceholder.NONE, + blurhash: demoBlurhash, + maxWidth: 320, + maxHeight: 320, + aspectRatio: "1 / 1", + isSvg: false, + gifLabel: undefined, + bannerLabel: "install-spinner.png", + tooltipLabel: undefined, + linkUrl: imageSrc, + linkTarget: undefined, + className: undefined, + children:
            File body slot
            , + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Hidden: Story = { + args: { + state: ImageBodyViewState.HIDDEN, + linkUrl: undefined, + tooltipLabel: undefined, + }, +}; + +export const LoadingWithSpinner: Story = { + args: { + placeholder: ImageBodyViewPlaceholder.SPINNER, + }, +}; + +export const LoadingWithBlurhash: Story = { + args: { + placeholder: ImageBodyViewPlaceholder.BLURHASH, + }, +}; + +export const AnimatedPreview: Story = { + args: { + src: animatedGifSrc, + thumbnailSrc, + linkUrl: animatedGifSrc, + showAnimatedContentOnHover: true, + gifLabel: "GIF", + }, +}; + +export const ErrorState: Story = { + args: { + state: ImageBodyViewState.ERROR, + linkUrl: undefined, + children: undefined, + }, +}; + +export const WithTooltip: Story = { + args: { + tooltipLabel: "Tooltip image name", + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.hover(canvas.getByRole("img", { name: "Element logo" })); + await expect( + within(canvasElement.ownerDocument.body).findByText("Tooltip image name"), + ).resolves.toBeInTheDocument(); + }, +}; diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.test.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.test.tsx new file mode 100644 index 0000000000..2eaeeba41f --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.test.tsx @@ -0,0 +1,181 @@ +/* + * Copyright 2026 Element Creations 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 React from "react"; +import { composeStories } from "@storybook/react-vite"; +import { fireEvent, render, screen } from "@test-utils"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; + +import { MockViewModel } from "../../../../../core/viewmodel/MockViewModel"; +import * as stories from "./ImageBodyView.stories"; +import { + ImageBodyView, + ImageBodyViewPlaceholder, + ImageBodyViewState, + type ImageBodyViewActions, + type ImageBodyViewSnapshot, +} from "./ImageBodyView"; + +const { Default, Hidden, LoadingWithSpinner, LoadingWithBlurhash, AnimatedPreview, ErrorState } = + composeStories(stories); + +class TestImageBodyViewModel extends MockViewModel implements ImageBodyViewActions { + public onLinkClick?: ImageBodyViewActions["onLinkClick"]; + public onHiddenButtonClick?: ImageBodyViewActions["onHiddenButtonClick"]; + public onImageLoad?: ImageBodyViewActions["onImageLoad"]; + public onImageError?: ImageBodyViewActions["onImageError"]; + + public constructor(snapshot: ImageBodyViewSnapshot, actions: ImageBodyViewActions = {}) { + super(snapshot); + this.onLinkClick = actions.onLinkClick; + this.onHiddenButtonClick = actions.onHiddenButtonClick; + this.onImageLoad = actions.onImageLoad; + this.onImageError = actions.onImageError; + } +} + +describe("ImageBodyView", () => { + it.each([ + ["default", Default], + ["hidden", Hidden], + ["loading-with-spinner", LoadingWithSpinner], + ["loading-with-blurhash", LoadingWithBlurhash], + ["animated-preview", AnimatedPreview], + ["error", ErrorState], + ])("matches snapshot for %s story", (_name, Story) => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders the hidden preview button and invokes the click handler", async () => { + const user = userEvent.setup(); + const onHiddenButtonClick = vi.fn(); + const vm = new TestImageBodyViewModel( + { + state: ImageBodyViewState.HIDDEN, + hiddenButtonLabel: "Show image", + maxWidth: 320, + maxHeight: 240, + aspectRatio: "4 / 3", + }, + { onHiddenButtonClick }, + ); + + render(); + + await user.click(screen.getByRole("button", { name: "Show image" })); + expect(onHiddenButtonClick).toHaveBeenCalledTimes(1); + }); + + it("renders an error label when the media cannot be displayed", () => { + const vm = new TestImageBodyViewModel({ + state: ImageBodyViewState.ERROR, + errorLabel: "Error decrypting image", + }); + + render(); + + expect(screen.getByText("Error decrypting image")).toBeInTheDocument(); + expect(screen.queryByRole("img")).not.toBeInTheDocument(); + }); + + it("renders a link wrapper and forwards the click handler", () => { + const onLinkClick = vi.fn(); + const vm = new TestImageBodyViewModel( + { + state: ImageBodyViewState.READY, + alt: "Linked image", + src: "https://example.org/full.png", + thumbnailSrc: "https://example.org/thumb.png", + linkUrl: "https://example.org/full.png", + linkTarget: "_blank", + maxWidth: 320, + maxHeight: 240, + aspectRatio: "4 / 3", + }, + { onLinkClick }, + ); + + render(); + + const link = screen.getByRole("link"); + expect(link).toHaveAttribute("href", "https://example.org/full.png"); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute("rel", "noreferrer noopener"); + + fireEvent.click(link); + expect(onLinkClick).toHaveBeenCalledTimes(1); + }); + + it("swaps to the full source on hover for animated previews", async () => { + const user = userEvent.setup(); + const vm = new TestImageBodyViewModel({ + state: ImageBodyViewState.READY, + alt: "Animated image", + src: "https://example.org/full.gif", + thumbnailSrc: "https://example.org/thumb.png", + showAnimatedContentOnHover: true, + gifLabel: "GIF", + maxWidth: 320, + maxHeight: 240, + aspectRatio: "4 / 3", + }); + + render(); + + const image = screen.getByRole("img", { name: "Animated image" }) as HTMLImageElement; + expect(image).toHaveAttribute("src", "https://example.org/thumb.png"); + expect(screen.getByText("GIF")).toBeInTheDocument(); + + await user.hover(image); + expect(image).toHaveAttribute("src", "https://example.org/full.gif"); + expect(screen.queryByText("GIF")).not.toBeInTheDocument(); + }); + + it("renders the configured placeholder state", () => { + const vm = new TestImageBodyViewModel({ + state: ImageBodyViewState.READY, + alt: "Loading image", + src: "https://example.org/full.png", + placeholder: ImageBodyViewPlaceholder.SPINNER, + maxWidth: 320, + maxHeight: 240, + aspectRatio: "4 / 3", + }); + + render(); + + expect(screen.getByRole("progressbar")).toBeInTheDocument(); + }); + + it("invokes image load and error handlers", () => { + const onImageLoad = vi.fn(); + const onImageError = vi.fn(); + const vm = new TestImageBodyViewModel( + { + state: ImageBodyViewState.READY, + alt: "Loaded image", + src: "https://example.org/full.png", + thumbnailSrc: "https://example.org/thumb.png", + maxWidth: 320, + maxHeight: 240, + aspectRatio: "4 / 3", + }, + { onImageLoad, onImageError }, + ); + + render(); + + const image = screen.getByRole("img", { name: "Loaded image" }); + fireEvent.load(image); + fireEvent.error(image); + + expect(onImageLoad).toHaveBeenCalledTimes(1); + expect(onImageError).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.tsx new file mode 100644 index 0000000000..7a4ea96acb --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/ImageBodyView.tsx @@ -0,0 +1,354 @@ +/* + * Copyright 2026 Element Creations 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 React, { + type CSSProperties, + type HTMLAttributeAnchorTarget, + type JSX, + type MouseEventHandler, + type PropsWithChildren, + type ReactEventHandler, + useState, +} from "react"; +import classNames from "classnames"; +import { Blurhash } from "react-blurhash"; +import { ImageErrorIcon, VisibilityOnIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; +import { InlineSpinner, Tooltip } from "@vector-im/compound-web"; + +import { useI18n } from "../../../../../core/i18n/i18nContext"; +import { type ViewModel, useViewModel } from "../../../../../core/viewmodel"; +import styles from "./ImageBodyView.module.css"; + +/** + * High-level rendering state for the shared image body view. + */ +export const enum ImageBodyViewState { + ERROR = "ERROR", + HIDDEN = "HIDDEN", + READY = "READY", +} + +/** + * Placeholder variant shown over the media frame while the image is still settling. + */ +export const enum ImageBodyViewPlaceholder { + NONE = "NONE", + SPINNER = "SPINNER", + BLURHASH = "BLURHASH", +} + +export interface ImageBodyViewSnapshot { + /** + * Controls whether the component renders an error state, a hidden-preview state, + * or a visible image frame. + */ + state: ImageBodyViewState; + /** + * Image alt text. + */ + alt?: string; + /** + * Label shown when media processing fails. + */ + errorLabel?: string; + /** + * Label used by the hidden-media reveal button. + */ + hiddenButtonLabel?: string; + /** + * Full-resolution image source. + */ + src?: string; + /** + * Thumbnail/static preview image source. + * Falls back to `src` when omitted. + */ + thumbnailSrc?: string; + /** + * Whether hovering or focusing the link should swap to the full-resolution image. + */ + showAnimatedContentOnHover?: boolean; + /** + * Which placeholder to render over the image frame. + */ + placeholder?: ImageBodyViewPlaceholder; + /** + * Blurhash string used when `placeholder` is `BLURHASH`. + */ + blurhash?: string; + /** + * Maximum rendered width for the media frame. + */ + maxWidth?: number; + /** + * Maximum rendered height for the media frame. + */ + maxHeight?: number; + /** + * Aspect ratio reserved for the media frame. + */ + aspectRatio?: CSSProperties["aspectRatio"]; + /** + * Whether the displayed image is an SVG and should therefore use explicit width sizing. + */ + isSvg?: boolean; + /** + * Optional badge shown for animated images when not hovered/focused. + */ + gifLabel?: string; + /** + * Optional overlay banner shown while hovered/focused. + */ + bannerLabel?: string; + /** + * Optional tooltip shown on the media frame. + */ + tooltipLabel?: string; + /** + * Optional link target for the media frame. + */ + linkUrl?: string; + /** + * Optional anchor target applied when `linkUrl` is provided. + */ + linkTarget?: HTMLAttributeAnchorTarget; +} + +export interface ImageBodyViewActions { + /** + * Invoked when the linked image is activated. + */ + onLinkClick?: MouseEventHandler; + /** + * Invoked when the user chooses to reveal hidden media. + */ + onHiddenButtonClick?: MouseEventHandler; + /** + * Invoked when the visible image loads. + */ + onImageLoad?: ReactEventHandler; + /** + * Invoked when the visible image fails to load. + */ + onImageError?: ReactEventHandler; +} + +export type ImageBodyViewModel = ViewModel; + +interface ImageBodyViewProps { + /** + * The view model for the component. + */ + vm: ImageBodyViewModel; + /** + * Optional host CSS class. + */ + className?: string; + /** + * Optional supplemental content rendered after the media frame. + */ + children?: PropsWithChildren["children"]; +} + +function renderPlaceholder({ + placeholder, + blurhash, + maxWidth, + maxHeight, + loadingLabel, +}: Pick & { + loadingLabel: string; +}): JSX.Element | null { + switch (placeholder) { + case ImageBodyViewPlaceholder.BLURHASH: + if (!blurhash) { + return ; + } + + return ( + + ); + + case ImageBodyViewPlaceholder.SPINNER: + return ; + + case ImageBodyViewPlaceholder.NONE: + default: + return null; + } +} + +/** + * Renders the body of an image message with ready, hidden, and error states. + * + * The media frame supports thumbnail fallbacks, optional loading placeholders, + * animated-content preview on hover/focus, and optional tooltip/banner labels. + * Supplemental content such as a file body row can be rendered after the image + * through `children`. + * + * @example + * ```tsx + * + *
            File body slot
            + *
            + * ``` + */ +export function ImageBodyView({ vm, className, children }: Readonly): JSX.Element { + const { translate: _t } = useI18n(); + const { + state, + alt, + errorLabel, + hiddenButtonLabel, + src, + thumbnailSrc, + showAnimatedContentOnHover, + placeholder = ImageBodyViewPlaceholder.NONE, + blurhash, + maxWidth, + maxHeight, + aspectRatio, + isSvg, + gifLabel, + bannerLabel, + tooltipLabel, + linkUrl, + linkTarget, + } = useViewModel(vm); + + const [hover, setHover] = useState(false); + const [focus, setFocus] = useState(false); + const hoverOrFocus = hover || focus; + + const rootClassName = classNames(className, styles.root); + + if (state === ImageBodyViewState.ERROR) { + return ( + + + {errorLabel} + + ); + } + + const resolvedThumbnailSrc = thumbnailSrc ?? src; + const resolvedImageSrc = hoverOrFocus && showAnimatedContentOnHover && src ? src : resolvedThumbnailSrc; + + // Reserve the media box on the container itself so the timeline doesn't jump + // while the image element or loading state is still settling. + const resolvedWidth = maxWidth === undefined ? undefined : `min(100%, ${maxWidth}px)`; + const containerStyle: CSSProperties = { + width: resolvedWidth, + maxWidth, + maxHeight, + aspectRatio, + }; + const mediaStyle: CSSProperties | undefined = isSvg + ? { + width: resolvedWidth, + maxWidth, + maxHeight, + } + : undefined; + + const placeholderNode = renderPlaceholder({ + placeholder, + blurhash, + maxWidth, + maxHeight, + loadingLabel: _t("common|loading"), + }); + const showPlaceholder = placeholderNode !== null; + + const media = + state === ImageBodyViewState.HIDDEN ? ( +
            + +
            + ) : resolvedImageSrc ? ( + {alt} setHover(true)} + onMouseLeave={(): void => setHover(false)} + /> + ) : null; + + const banner = + state === ImageBodyViewState.READY && bannerLabel && hoverOrFocus ? ( + {bannerLabel} + ) : null; + + const gifBadge = + state === ImageBodyViewState.READY && gifLabel && !hoverOrFocus ? ( +

            {gifLabel}

            + ) : null; + + let frame = ( +
            + {showPlaceholder && ( +
            + {placeholderNode} +
            + )} + +
            + {media} + {gifBadge} + {banner} +
            +
            + ); + + if (tooltipLabel) { + frame = ( + + {frame} + + ); + } + + if (state === ImageBodyViewState.READY && linkUrl) { + frame = ( + setFocus(true)} + onBlur={(): void => setFocus(false)} + > + {frame} + + ); + } + + return ( +
            + {frame} + {children} +
            + ); +} diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/__snapshots__/ImageBodyView.test.tsx.snap b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/__snapshots__/ImageBodyView.test.tsx.snap new file mode 100644 index 0000000000..e01e97e094 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/__snapshots__/ImageBodyView.test.tsx.snap @@ -0,0 +1,238 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ImageBodyView > matches snapshot for animated-preview story 1`] = ` +
            +
            + +
            +
            + Element logo +

            + GIF +

            +
            +
            +
            +
            + File body slot +
            +
            +
            +`; + +exports[`ImageBodyView > matches snapshot for default story 1`] = ` +
            +
            + +
            +
            + Element logo +
            +
            +
            +
            + File body slot +
            +
            +
            +`; + +exports[`ImageBodyView > matches snapshot for error story 1`] = ` +
            + + + + + + Unable to show image due to error + +
            +`; + +exports[`ImageBodyView > matches snapshot for hidden story 1`] = ` +
            +
            +
            +
            +
            + +
            +
            +
            +
            + File body slot +
            +
            +
            +`; + +exports[`ImageBodyView > matches snapshot for loading-with-blurhash story 1`] = ` +
            +
            + +
            +
            +
            + +
            +
            +
            + Element logo +
            +
            +
            +
            + File body slot +
            +
            +
            +`; + +exports[`ImageBodyView > matches snapshot for loading-with-spinner story 1`] = ` +
            +
            + +
            +
            + + + +
            +
            + Element logo +
            +
            +
            +
            + File body slot +
            +
            +
            +`; diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/index.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/index.tsx new file mode 100644 index 0000000000..a71a80c047 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageBodyView/index.tsx @@ -0,0 +1,15 @@ +/* + * Copyright 2026 Element Creations 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. + */ + +export { + ImageBodyView, + ImageBodyViewPlaceholder, + ImageBodyViewState, + type ImageBodyViewActions, + type ImageBodyViewModel, + type ImageBodyViewSnapshot, +} from "./ImageBodyView"; diff --git a/packages/shared-components/static/image-body/install-spinner.gif b/packages/shared-components/static/image-body/install-spinner.gif new file mode 100644 index 0000000000..ce6c17844a Binary files /dev/null and b/packages/shared-components/static/image-body/install-spinner.gif differ diff --git a/packages/shared-components/static/image-body/install-spinner.png b/packages/shared-components/static/image-body/install-spinner.png new file mode 100644 index 0000000000..75c3f48ad8 Binary files /dev/null and b/packages/shared-components/static/image-body/install-spinner.png differ diff --git a/packages/shared-components/vitest.config.ts b/packages/shared-components/vitest.config.ts index 5e4d793477..1fa6c1b3a7 100644 --- a/packages/shared-components/vitest.config.ts +++ b/packages/shared-components/vitest.config.ts @@ -150,7 +150,11 @@ export default defineConfig({ ], }, optimizeDeps: { - include: ["vite-plugin-node-polyfills/shims/buffer", "vite-plugin-node-polyfills/shims/process"], + include: [ + "vite-plugin-node-polyfills/shims/buffer", + "vite-plugin-node-polyfills/shims/process", + "@vector-im/compound-design-tokens/assets/web/icons", + ], }, resolve: { alias: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 545060d5bd..66831ca077 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,13 +53,16 @@ overrides: ajv@>=8 <8.18.0: 8.18.0 axios@>1.0.0 <1.15.0: 1.15.0 picomatch@>=4.0.0 <4.0.4: 4.0.4 - esbuild@<=0.24.2: '>=0.25.0' + esbuild@<=0.24.2: 0.27.4 + esbuild@~0.27.0: 0.27.4 minimatch@>=10.0.0 <10.2.3: '>=10.2.3' lodash@4.17.23: 4.18.1 follow-redirects@1.15.11: 1.16.0 '@actions/github@6.0.1': 8.0.1 + dompurify@>=3.0.0 <=3.3.3: 3.4.0 + protobufjs@>=7.0.0 <7.5.5: 7.5.5 -packageExtensionsChecksum: sha256-OFePh8Fn8A1ZEWgJF51vTMPT/HEVyXQyj6YGIztPb0w= +packageExtensionsChecksum: sha256-EMEi1vcyzQthk7O/0AcntvnHgJaKCoFBlzp6iX/qNYk= patchedDependencies: '@matrix-org/react-sdk-module-api': @@ -117,10 +120,10 @@ importers: version: 0.6.0 '@nx-tools/nx-container': specifier: ^7.2.1 - version: 7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(@nx/js@22.5.3(@babel/traverse@7.29.0)(nx@22.5.4))(dotenv@17.4.0)(nx@22.5.4)(tslib@2.8.1) + version: 7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(@nx/js@22.5.3(@babel/traverse@7.29.0)(nx@22.6.5))(dotenv@17.4.0)(nx@22.6.5)(tslib@2.8.1) '@nx/jest': specifier: ^22.5.0 - version: 22.5.3(@babel/traverse@7.29.0)(@types/node@18.19.130)(babel-plugin-macros@3.1.0)(nx@22.5.4)(typescript@5.9.3) + version: 22.5.3(@babel/traverse@7.29.0)(@types/node@18.19.130)(babel-plugin-macros@3.1.0)(nx@22.6.5)(typescript@5.9.3) '@playwright/test': specifier: 'catalog:' version: 1.59.1 @@ -152,8 +155,8 @@ importers: specifier: ^1.2.6 version: 1.2.8 nx: - specifier: 22.5.4 - version: 22.5.4 + specifier: 22.6.5 + version: 22.6.5 prettier: specifier: 3.8.1 version: 3.8.1 @@ -439,7 +442,7 @@ importers: version: 1.0.3 matrix-js-sdk: specifier: github:matrix-org/matrix-js-sdk#develop - version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8bc3d96f6b2733106906b47ff0ff4356e88d024f + version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/eb7acfb810d2989c0fe61c490083ff9d72c44013 matrix-widget-api: specifier: ^1.17.0 version: 1.17.0 @@ -498,8 +501,8 @@ importers: specifier: ^1.6.3 version: 1.6.3 sanitize-html: - specifier: 2.17.2 - version: 2.17.2 + specifier: 2.17.3 + version: 2.17.3 tar-js: specifier: ^0.3.0 version: 0.3.0 @@ -1037,6 +1040,9 @@ importers: matrix-web-i18n: specifier: 'catalog:' version: 3.6.0 + react-blurhash: + specifier: ^0.3.0 + version: 0.3.0(patch_hash=58bc7f075478017ce27bcc252e8509876390db106246bd5b0a7446642cc4b505)(blurhash@2.0.5)(react@19.2.4) react-merge-refs: specifier: ^3.0.2 version: 3.0.2(react@19.2.4) @@ -3274,8 +3280,8 @@ packages: cpu: [arm64] os: [darwin] - '@nx/nx-darwin-arm64@22.5.4': - resolution: {integrity: sha512-Ib9znwSLQZSZ/9hhg5ODplpNhE/RhGVXzdfRj6YonTuWSj/kH3dLMio+4JEkjRdTQVm06cDW0KdwSgnwovqMGg==} + '@nx/nx-darwin-arm64@22.6.5': + resolution: {integrity: sha512-qT77Omkg5xQuL2+pDbneX2tI+XW5ZeayMylu7UUgK8OhTrAkJLKjpuYRH4xT5XBipxbDtlxmO3aLS3Ib1pKzJQ==} cpu: [arm64] os: [darwin] @@ -3284,8 +3290,8 @@ packages: cpu: [x64] os: [darwin] - '@nx/nx-darwin-x64@22.5.4': - resolution: {integrity: sha512-DjyXuQMc93MPU2XdRsJYjzbv1tgCzMi+zm7O0gc4x3h+ECFjKkjzQBg67pqGdhE3TV27MAlVRKrgHStyK9iigg==} + '@nx/nx-darwin-x64@22.6.5': + resolution: {integrity: sha512-9jICxb7vfJ56y/7Yuh3b/n1QJqWxO9xnXKYEs6SO8xPoW/KomVckILGc1C6RQSs6/3ixVJC7k1Dh1wm5tKPFrg==} cpu: [x64] os: [darwin] @@ -3294,8 +3300,8 @@ packages: cpu: [x64] os: [freebsd] - '@nx/nx-freebsd-x64@22.5.4': - resolution: {integrity: sha512-DhxdP8AhIfN0yCtFhZQcbp32MVN3L7UiTotYqqnOgwW922NRGSd5e+KEAWiJVrIO6TdgnI7prxpg1hfQQK0WDw==} + '@nx/nx-freebsd-x64@22.6.5': + resolution: {integrity: sha512-6B1wEKpqz5dI3AGMqttAVnA6M3DB/besAtuGyQiymK9ROlta1iuWgCcIYwcCQyhLn2Rx7vqj447KKcgCa8HlVw==} cpu: [x64] os: [freebsd] @@ -3304,8 +3310,8 @@ packages: cpu: [arm] os: [linux] - '@nx/nx-linux-arm-gnueabihf@22.5.4': - resolution: {integrity: sha512-pv1x1afTaLAOxPxVhQneLeXgjclp11f9ORxR7jA4E86bSgc9OL92dLSCkXtLQzqPNOej6SZ2fO+PPHVMZwtaPQ==} + '@nx/nx-linux-arm-gnueabihf@22.6.5': + resolution: {integrity: sha512-xV50B8mnDPboct7JkAHftajI02s+8FszA8WTzhore+YGR+lEKHTLpucwGEaQuMlSdLplH7pQix4B4uK5pcMhZw==} cpu: [arm] os: [linux] @@ -3315,8 +3321,8 @@ packages: os: [linux] libc: [glibc] - '@nx/nx-linux-arm64-gnu@22.5.4': - resolution: {integrity: sha512-mPji9PzleWPvXpmFDKaXpTymRgZkk/hW8JHGhvEZpKHHXMYgTGWC+BqOEM2A4dYC4bu4fi9RrteL7aouRRWJoQ==} + '@nx/nx-linux-arm64-gnu@22.6.5': + resolution: {integrity: sha512-2JkWuMGj+HpW6oPAvU5VdAx1afTnEbiM10Y3YOrl3fipWV4BiP5VDx762QTrfCraP4hl6yqTgvTe7F9xaby+jQ==} cpu: [arm64] os: [linux] libc: [glibc] @@ -3327,8 +3333,8 @@ packages: os: [linux] libc: [musl] - '@nx/nx-linux-arm64-musl@22.5.4': - resolution: {integrity: sha512-hF/HvEhbCjcFpTgY7RbP1tUTbp0M1adZq4ckyW8mwhDWQ/MDsc8FnOHwCO3Bzy9ZeJM0zQUES6/m0Onz8geaEA==} + '@nx/nx-linux-arm64-musl@22.6.5': + resolution: {integrity: sha512-Z/zMqFClnEyqDXouJKEPoWVhMQIif5F0YuECWBYjd3ZLwQsXGTItoh+6Wm3XF/nGMA2uLOHyTq/X7iFXQY3RzA==} cpu: [arm64] os: [linux] libc: [musl] @@ -3339,8 +3345,8 @@ packages: os: [linux] libc: [glibc] - '@nx/nx-linux-x64-gnu@22.5.4': - resolution: {integrity: sha512-1+vicSYEOtc7CNMoRCjo59no4gFe8w2nGIT127wk1yeW3EJzRVNlOA7Deu10NUUbzLeOvHc8EFOaU7clT+F7XQ==} + '@nx/nx-linux-x64-gnu@22.6.5': + resolution: {integrity: sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==} cpu: [x64] os: [linux] libc: [glibc] @@ -3351,8 +3357,8 @@ packages: os: [linux] libc: [musl] - '@nx/nx-linux-x64-musl@22.5.4': - resolution: {integrity: sha512-/KjndxVB14yU0SJOhqADHOWoTy4Y45h5RjW3cxcXlPSJZz7ar1FnlLne1rWMMMUttepc8ku+3T//SGKi2eu+Nw==} + '@nx/nx-linux-x64-musl@22.6.5': + resolution: {integrity: sha512-RVOe2qcwhoIx6mxQURPjUfAW5SEOmT2gdhewvdcvX9ICq1hj5B2VarmkhTg0qroO7xiyqOqwq26mCzoV2I3NgQ==} cpu: [x64] os: [linux] libc: [musl] @@ -3362,8 +3368,8 @@ packages: cpu: [arm64] os: [win32] - '@nx/nx-win32-arm64-msvc@22.5.4': - resolution: {integrity: sha512-CrYt9FwhjOI6ZNy/G6YHLJmZuXCFJ24BCxugPXiZ7knDx7eGrr7owGgfht4SSiK3KCX40CvWCBJfqR4ZSgaSUA==} + '@nx/nx-win32-arm64-msvc@22.6.5': + resolution: {integrity: sha512-ZqurqI8VuYnsr2Kn4K4t+Gx6j/BZdf6qz/6Tv4A7XQQ6oNYVQgTqoNEFj+CCkVaIe6aIdCWpousFLqs+ZgBqYQ==} cpu: [arm64] os: [win32] @@ -3372,8 +3378,8 @@ packages: cpu: [x64] os: [win32] - '@nx/nx-win32-x64-msvc@22.5.4': - resolution: {integrity: sha512-g5YByv4XsYwsYZvFe24A9bvfhZA+mwtIQt6qZtEVduZTT1hfhIsq0LXGHhkGoFLYwRMXSracWOqkalY0KT4IQw==} + '@nx/nx-win32-x64-msvc@22.6.5': + resolution: {integrity: sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==} cpu: [x64] os: [win32] @@ -4996,7 +5002,7 @@ packages: '@storybook/csf-plugin@10.2.19': resolution: {integrity: sha512-BpjYIOdyQn/Rm6MjUAc5Gl8HlARZrskD/OhUNShiOh2fznb523dHjiE5mbU1kKM/+L1uvRlEqqih40rTx+xCrg==} peerDependencies: - esbuild: '>=0.25.0' + esbuild: 0.27.4 rollup: '*' storybook: ^10.2.19 vite: '*' @@ -7714,8 +7720,8 @@ packages: resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==} engines: {node: '>=20.19.0'} - dompurify@3.3.3: - resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} + dompurify@3.4.0: + resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -7768,6 +7774,11 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + ejs@5.0.1: + resolution: {integrity: sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==} + engines: {node: '>=0.12.18'} + hasBin: true + electron-builder-squirrel-windows@26.8.2: resolution: {integrity: sha512-kXhajX6DzdIQcTlctVTKoG1oO39JhWcTG0lH7ZEJ4FzPaKJy7KFNfNJUd5BoEmLjv5GlrRZpEOYnniD+LcwNJA==} @@ -9883,8 +9894,8 @@ packages: matrix-events-sdk@0.0.1: resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8bc3d96f6b2733106906b47ff0ff4356e88d024f: - resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8bc3d96f6b2733106906b47ff0ff4356e88d024f} + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/eb7acfb810d2989c0fe61c490083ff9d72c44013: + resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/eb7acfb810d2989c0fe61c490083ff9d72c44013} version: 41.3.0 engines: {node: '>=22.0.0'} @@ -10325,8 +10336,8 @@ packages: '@swc/core': optional: true - nx@22.5.4: - resolution: {integrity: sha512-L8wL7uCjnmpyvq4r2mN9s+oriUE4lY+mX9VgOpjj0ucRd5nzaEaBQppVs0zQGkbKC0BnHS8PGtnAglspd5Gh1Q==} + nx@22.6.5: + resolution: {integrity: sha512-VRKhDAt684dXNSz9MNjE7MekkCfQF41P2PSx5jEWQjDEP1Z4jFZbyeygWs5ZyOroG7/n0MoWAJTe6ftvIcBOAg==} hasBin: true peerDependencies: '@swc-node/register': ^1.11.1 @@ -11205,8 +11216,8 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - protobufjs@7.5.4: - resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + protobufjs@7.5.5: + resolution: {integrity: sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==} engines: {node: '>=12.0.0'} protocol-buffers-schema@3.6.1: @@ -11734,8 +11745,8 @@ packages: sanitize-filename@1.6.3: resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} - sanitize-html@2.17.2: - resolution: {integrity: sha512-EnffJUl46VE9uvZ0XeWzObHLurClLlT12gsOk1cHyP2Ol1P0BnBnsXmShlBmWVJM+dKieQI68R0tsPY5m/B+Jg==} + sanitize-html@2.17.3: + resolution: {integrity: sha512-Kn4srCAo2+wZyvCNKCSyB2g8RQ8IkX/gQs2uqoSRNu5t9I2qvUyAVvRDiFUVAiX3N3PNuwStY0eNr+ooBHVWEg==} sax@1.6.0: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} @@ -12956,7 +12967,7 @@ packages: peerDependencies: '@types/node': 18.19.130 '@vitejs/devtools': ^0.1.0 - esbuild: ^0.27.0 || ^0.28.0 + esbuild: 0.27.4 jiti: '>=1.21.0' less: ^4.0.0 sass: ^1.70.0 @@ -15207,14 +15218,14 @@ snapshots: dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.4 + protobufjs: 7.5.5 yargs: 17.7.2 '@grpc/proto-loader@0.8.0': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.4 + protobufjs: 7.5.5 yargs: 17.7.2 '@hapi/address@5.1.1': @@ -15897,10 +15908,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nx-tools/ci-context@7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1)': + '@nx-tools/ci-context@7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1)': dependencies: '@actions/github': 8.0.1 - '@nx-tools/core': 7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1) + '@nx-tools/core': 7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1) '@octokit/openapi-types': 22.2.0 properties-file: 3.6.4 std-env: 3.10.0 @@ -15908,11 +15919,11 @@ snapshots: transitivePeerDependencies: - '@nx/devkit' - '@nx-tools/container-metadata@7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1)': + '@nx-tools/container-metadata@7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1)': dependencies: - '@nx-tools/ci-context': 7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1) - '@nx-tools/core': 7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1) - '@nx/devkit': 22.5.3(nx@22.5.4) + '@nx-tools/ci-context': 7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1) + '@nx-tools/core': 7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1) + '@nx/devkit': 22.5.3(nx@22.6.5) '@renovatebot/pep440': 4.2.1 csv-parse: 5.6.0 handlebars: 4.7.9 @@ -15920,26 +15931,26 @@ snapshots: semver: 7.7.4 tslib: 2.8.1 - '@nx-tools/core@7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1)': + '@nx-tools/core@7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1)': dependencies: '@actions/io': 1.1.3 - '@nx/devkit': 22.5.3(nx@22.5.4) + '@nx/devkit': 22.5.3(nx@22.6.5) csv-parse: 5.6.0 std-env: 3.10.0 tinyexec: 1.0.2 tinyrainbow: 3.0.3 tslib: 2.8.1 - '@nx-tools/nx-container@7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(@nx/js@22.5.3(@babel/traverse@7.29.0)(nx@22.5.4))(dotenv@17.4.0)(nx@22.5.4)(tslib@2.8.1)': + '@nx-tools/nx-container@7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(@nx/js@22.5.3(@babel/traverse@7.29.0)(nx@22.6.5))(dotenv@17.4.0)(nx@22.6.5)(tslib@2.8.1)': dependencies: - '@nx-tools/container-metadata': 7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1) - '@nx-tools/core': 7.2.1(@nx/devkit@22.5.3(nx@22.5.4))(tslib@2.8.1) - '@nx/devkit': 22.5.3(nx@22.5.4) - '@nx/js': 22.5.3(@babel/traverse@7.29.0)(nx@22.5.4) + '@nx-tools/container-metadata': 7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1) + '@nx-tools/core': 7.2.1(@nx/devkit@22.5.3(nx@22.6.5))(tslib@2.8.1) + '@nx/devkit': 22.5.3(nx@22.6.5) + '@nx/js': 22.5.3(@babel/traverse@7.29.0)(nx@22.6.5) csv-parse: 5.6.0 dotenv: 17.4.0 handlebars: 4.7.9 - nx: 22.5.4 + nx: 22.6.5 semver: 7.7.4 tmp: 0.2.5 tslib: 2.8.1 @@ -15955,23 +15966,23 @@ snapshots: tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/devkit@22.5.3(nx@22.5.4)': + '@nx/devkit@22.5.3(nx@22.6.5)': dependencies: '@zkochan/js-yaml': 0.0.7 ejs: 3.1.10 enquirer: 2.3.6 minimatch: 10.2.5 - nx: 22.5.4 + nx: 22.6.5 semver: 7.7.4 tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/jest@22.5.3(@babel/traverse@7.29.0)(@types/node@18.19.130)(babel-plugin-macros@3.1.0)(nx@22.5.4)(typescript@5.9.3)': + '@nx/jest@22.5.3(@babel/traverse@7.29.0)(@types/node@18.19.130)(babel-plugin-macros@3.1.0)(nx@22.6.5)(typescript@5.9.3)': dependencies: '@jest/reporters': 30.3.0 '@jest/test-result': 30.3.0 - '@nx/devkit': 22.5.3(nx@22.5.4) - '@nx/js': 22.5.3(@babel/traverse@7.29.0)(nx@22.5.4) + '@nx/devkit': 22.5.3(nx@22.6.5) + '@nx/js': 22.5.3(@babel/traverse@7.29.0)(nx@22.6.5) '@phenomnomnominal/tsquery': 6.1.4(typescript@5.9.3) identity-obj-proxy: 3.0.0 jest-config: 30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0) @@ -15998,7 +16009,7 @@ snapshots: - typescript - verdaccio - '@nx/js@22.5.3(@babel/traverse@7.29.0)(nx@22.5.4)': + '@nx/js@22.5.3(@babel/traverse@7.29.0)(nx@22.6.5)': dependencies: '@babel/core': 7.29.0 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) @@ -16007,7 +16018,7 @@ snapshots: '@babel/preset-env': 7.29.0(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@babel/runtime': 7.28.6 - '@nx/devkit': 22.5.3(nx@22.5.4) + '@nx/devkit': 22.5.3(nx@22.6.5) '@nx/workspace': 22.5.3 '@zkochan/js-yaml': 0.0.7 babel-plugin-const-enum: 1.2.0(@babel/core@7.29.0) @@ -16037,61 +16048,61 @@ snapshots: '@nx/nx-darwin-arm64@22.5.3': optional: true - '@nx/nx-darwin-arm64@22.5.4': + '@nx/nx-darwin-arm64@22.6.5': optional: true '@nx/nx-darwin-x64@22.5.3': optional: true - '@nx/nx-darwin-x64@22.5.4': + '@nx/nx-darwin-x64@22.6.5': optional: true '@nx/nx-freebsd-x64@22.5.3': optional: true - '@nx/nx-freebsd-x64@22.5.4': + '@nx/nx-freebsd-x64@22.6.5': optional: true '@nx/nx-linux-arm-gnueabihf@22.5.3': optional: true - '@nx/nx-linux-arm-gnueabihf@22.5.4': + '@nx/nx-linux-arm-gnueabihf@22.6.5': optional: true '@nx/nx-linux-arm64-gnu@22.5.3': optional: true - '@nx/nx-linux-arm64-gnu@22.5.4': + '@nx/nx-linux-arm64-gnu@22.6.5': optional: true '@nx/nx-linux-arm64-musl@22.5.3': optional: true - '@nx/nx-linux-arm64-musl@22.5.4': + '@nx/nx-linux-arm64-musl@22.6.5': optional: true '@nx/nx-linux-x64-gnu@22.5.3': optional: true - '@nx/nx-linux-x64-gnu@22.5.4': + '@nx/nx-linux-x64-gnu@22.6.5': optional: true '@nx/nx-linux-x64-musl@22.5.3': optional: true - '@nx/nx-linux-x64-musl@22.5.4': + '@nx/nx-linux-x64-musl@22.6.5': optional: true '@nx/nx-win32-arm64-msvc@22.5.3': optional: true - '@nx/nx-win32-arm64-msvc@22.5.4': + '@nx/nx-win32-arm64-msvc@22.6.5': optional: true '@nx/nx-win32-x64-msvc@22.5.3': optional: true - '@nx/nx-win32-x64-msvc@22.5.4': + '@nx/nx-win32-x64-msvc@22.6.5': optional: true '@nx/workspace@22.5.3': @@ -16439,7 +16450,7 @@ snapshots: '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) - protobufjs: 7.5.4 + protobufjs: 7.5.5 '@opentelemetry/redis-common@0.38.2': {} @@ -20841,7 +20852,7 @@ snapshots: '@grpc/grpc-js': 1.14.3 '@grpc/proto-loader': 0.7.15 docker-modem: 5.0.7 - protobufjs: 7.5.4 + protobufjs: 7.5.5 tar-fs: 2.1.4 uuid: 10.0.0 transitivePeerDependencies: @@ -20904,7 +20915,7 @@ snapshots: dependencies: domelementtype: 3.0.0 - dompurify@3.3.3: + dompurify@3.4.0: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -20961,6 +20972,8 @@ snapshots: dependencies: jake: 10.9.4 + ejs@5.0.1: {} + electron-builder-squirrel-windows@26.8.2(dmg-builder@26.8.2): dependencies: app-builder-lib: 26.8.2(patch_hash=2dfb3fcdfe573cca6c248cecf63ddea5c8fa0276859695fba6c9664d0ff285d6)(dmg-builder@26.8.2)(electron-builder-squirrel-windows@26.8.2) @@ -23572,7 +23585,7 @@ snapshots: matrix-events-sdk@0.0.1: {} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8bc3d96f6b2733106906b47ff0ff4356e88d024f: + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/eb7acfb810d2989c0fe61c490083ff9d72c44013: dependencies: '@babel/runtime': 7.28.6 '@matrix-org/matrix-sdk-crypto-wasm': 18.0.0 @@ -23685,7 +23698,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 dayjs: 1.11.20 - dompurify: 3.3.3 + dompurify: 3.4.0 katex: 0.16.45 khroma: 2.1.0 lodash-es: 4.18.1 @@ -24119,7 +24132,7 @@ snapshots: transitivePeerDependencies: - debug - nx@22.5.4: + nx@22.6.5: dependencies: '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 @@ -24131,7 +24144,7 @@ snapshots: cliui: 8.0.1 dotenv: 16.4.7 dotenv-expand: 11.0.7 - ejs: 3.1.10 + ejs: 5.0.1 enquirer: 2.3.6 figures: 3.2.0 flat: 5.0.2 @@ -24141,13 +24154,13 @@ snapshots: jsonc-parser: 3.2.0 lines-and-columns: 2.0.3 minimatch: 10.2.4 - node-machine-id: 1.1.12 npm-run-path: 4.0.1 open: 8.4.2 ora: 5.3.0 picocolors: 1.1.1 resolve.exports: 2.0.3 semver: 7.7.4 + smol-toml: 1.6.1 string-width: 4.2.3 tar-stream: 2.2.0 tmp: 0.2.5 @@ -24158,16 +24171,16 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nx/nx-darwin-arm64': 22.5.4 - '@nx/nx-darwin-x64': 22.5.4 - '@nx/nx-freebsd-x64': 22.5.4 - '@nx/nx-linux-arm-gnueabihf': 22.5.4 - '@nx/nx-linux-arm64-gnu': 22.5.4 - '@nx/nx-linux-arm64-musl': 22.5.4 - '@nx/nx-linux-x64-gnu': 22.5.4 - '@nx/nx-linux-x64-musl': 22.5.4 - '@nx/nx-win32-arm64-msvc': 22.5.4 - '@nx/nx-win32-x64-msvc': 22.5.4 + '@nx/nx-darwin-arm64': 22.6.5 + '@nx/nx-darwin-x64': 22.6.5 + '@nx/nx-freebsd-x64': 22.6.5 + '@nx/nx-linux-arm-gnueabihf': 22.6.5 + '@nx/nx-linux-arm64-gnu': 22.6.5 + '@nx/nx-linux-arm64-musl': 22.6.5 + '@nx/nx-linux-x64-gnu': 22.6.5 + '@nx/nx-linux-x64-musl': 22.6.5 + '@nx/nx-win32-arm64-msvc': 22.6.5 + '@nx/nx-win32-x64-msvc': 22.6.5 transitivePeerDependencies: - debug @@ -25086,7 +25099,7 @@ snapshots: '@posthog/core': 1.24.6 '@posthog/types': 1.364.7 core-js: 3.48.0 - dompurify: 3.3.3 + dompurify: 3.4.0 fflate: 0.4.8 preact: 10.28.3 query-selector-shadow-dom: 1.0.1 @@ -25162,7 +25175,7 @@ snapshots: property-information@7.1.0: {} - protobufjs@7.5.4: + protobufjs@7.5.5: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 @@ -25793,7 +25806,7 @@ snapshots: dependencies: truncate-utf8-bytes: 1.0.2 - sanitize-html@2.17.2: + sanitize-html@2.17.3: dependencies: deepmerge: 4.3.1 escape-string-regexp: 4.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 047f97ff4a..2aae851aea 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -27,4 +27,8 @@ packageExtensions: dependencies: # Fix missing type dependency "@types/picomatch": 4.0.2 + "@joshwooding/vite-plugin-react-docgen-typescript": + peerDependencies: + # Silence warning + vite: "^8.0.0" ignorePatchFailures: false diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh index 1299815bbb..b326c0e6b4 100755 --- a/scripts/fetchdep.sh +++ b/scripts/fetchdep.sh @@ -27,7 +27,10 @@ clone() { then echo "Trying to use $org/$repo#$branch" # Disable auth prompts: https://serverfault.com/a/665959 - GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0 + if GIT_TERMINAL_PROMPT=0 git clone "https://github.com/$org/$repo.git" "$repo" --branch "$branch" --depth 1; then + git -C "$repo" log -1 + exit 0 + fi fi } @@ -39,7 +42,7 @@ getPRInfo() { apiEndpoint="https://api.github.com/repos/$PR_ORG/$PR_REPO/pulls/$number" - head=$(curl $apiEndpoint | jq -r '.head.label') + head=$(curl "$apiEndpoint" | jq -r '.head.label') fi } @@ -47,16 +50,17 @@ getPRInfo() { # GH API for more info - "fork:branch". Some give us this directly. if [ -n "$PR_NUMBER" ]; then # GitHub - getPRInfo $PR_NUMBER + getPRInfo "$PR_NUMBER" elif [ -n "$REVIEW_ID" ]; then # Netlify - getPRInfo $REVIEW_ID + getPRInfo "$REVIEW_ID" fi # for forks, $head will be in the format "fork:branch", so we split it by ":" # into an array. On non-forks, this has the effect of splitting into a single # element array given ":" shouldn't appear in the head - it'll just be the # branch name. Based on the results, we clone. +# shellcheck disable=SC2206 BRANCH_ARRAY=(${head//:/ }) TRY_ORG=$deforg TRY_BRANCH=${BRANCH_ARRAY[0]} @@ -67,13 +71,13 @@ if [[ "$head" == *":"* ]]; then fi TRY_BRANCH=${BRANCH_ARRAY[1]} fi -clone ${TRY_ORG} $defrepo ${TRY_BRANCH} +clone "$TRY_ORG" "$defrepo" "$TRY_BRANCH" # For merge queue runs we need to extract the temporary branch name # the ref_name will look like `gh-readonly-queue//pr--` if [[ "$GITHUB_EVENT_NAME" == "merge_group" ]]; then withoutPrefix=${GITHUB_REF_NAME#gh-readonly-queue/} - clone $deforg $defrepo ${withoutPrefix%%/pr-*} + clone "$deforg" "$defrepo" "${withoutPrefix%%/pr-*}" fi # Try the target branch of the push or PR, or the branch that was pushed to @@ -83,10 +87,10 @@ if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then base_or_branch=${GITHUB_REF} fi if [ -n "$base_or_branch" ]; then - clone $deforg $defrepo $base_or_branch + clone "$deforg" "$defrepo" "$base_or_branch" fi # Try HEAD which is the branch name in Netlify (not BRANCH which is pull/xxxx/head for PR builds) -clone $deforg $defrepo $HEAD +clone "$deforg" "$defrepo" "$HEAD" # Use the default branch as the last resort. -clone $deforg $defrepo $defbranch +clone "$deforg" "$defrepo" "$defbranch" diff --git a/scripts/layered.sh b/scripts/layered.sh index 6e5c91be29..912ef56a32 100755 --- a/scripts/layered.sh +++ b/scripts/layered.sh @@ -14,7 +14,7 @@ set -ex # for the primary repo (element-web in this case). # Install dependencies -pnpm install --frozen-lockfile +pnpm install --frozen-lockfile $@ # Pass appropriate repo to fetchdep.sh export PR_ORG=element-hq @@ -25,16 +25,16 @@ js_sdk_dep=$(jq -r '.dependencies["matrix-js-sdk"]' < $(pnpm -w root)/../apps/we # Set up the js-sdk first (unless package.json pins a specific version) if [ "$js_sdk_dep" = "github:matrix-org/matrix-js-sdk#develop" ]; then scripts/fetchdep.sh matrix-org matrix-js-sdk develop - pushd matrix-js-sdk - [ -n "$JS_SDK_GITHUB_BASE_REF" ] && git fetch --depth 1 origin $JS_SDK_GITHUB_BASE_REF && git checkout $JS_SDK_GITHUB_BASE_REF - pnpm link - pnpm install --frozen-lockfile - popd - # Link into into element-web - pnpm link matrix-js-sdk + if [ -n "$JS_SDK_GITHUB_BASE_REF" ]; then + git -C matrix-js-sdk fetch --depth 1 origin $JS_SDK_GITHUB_BASE_REF + git -C matrix-js-sdk checkout $JS_SDK_GITHUB_BASE_REF + fi + pnpm -C matrix-js-sdk install --frozen-lockfile --ignore-scripts + + # Link into into element-web & the monorepo + pnpm -C apps/web link ./matrix-js-sdk + pnpm link ./matrix-js-sdk else echo "Skipping matrix-js-sdk fetch and link as package.json pins $js_sdk_dep" fi - -pnpm install --frozen-lockfile $@ diff --git a/sonar-project.properties b/sonar-project.properties index 0729966d8b..7f08ea24b2 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,7 +7,23 @@ sonar.organization=element-hq sonar.sources=. sonar.tests=apps/web/test,apps/web/playwright,apps/desktop/playwright,packages sonar.test.inclusions=apps/web/test/*,apps/web/playwright/*,apps/desktop/playwright/*,packages/*/src/**/*.test.*,packages/*/src/test/**/* -sonar.exclusions=apps/web/__mocks__,docs,apps/web/element.io,apps/web/nginx,apps/web/src/vector/modernizr.cjs +sonar.exclusions=\ + apps/web/__mocks__,\ + docs,\ + apps/web/element.io,\ + apps/web/nginx,\ + apps/web/src/vector/modernizr.cjs,\ + **/*.webm,\ + **/*.ogg,\ + **/*.mp3,\ + **/*.woff2,\ + **/*.ttf,\ + **/*.webp,\ + **/*.jpg,\ + **/*.apng,\ + **/*.ico,\ + **/*.png,\ + **/*.gif sonar.cpd.exclusions=**/src/i18n/strings/*.json sonar.javascript.lcov.reportPaths=apps/web/coverage/lcov.info,packages/shared-components/coverage/lcov.info
    {_t("devtools|crypto|session")}
    {_t("devtools|crypto|device_id")}
    + Cross-signing +
    + Cross-signing +
    + Key Storage +
    + Key Storage +