Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ef02fa39e | |||
| 68cc500705 | |||
| 1ec05298cf | |||
| eaa34e6dfa | |||
| fefe093ce0 | |||
| ee4277fd95 | |||
| a15efcc6d0 | |||
| b7a2e8c64e | |||
| 93a8b67ed0 | |||
| 3b08b5c582 | |||
| f236c26356 | |||
| b8ad0b93db | |||
| 3f021472c2 | |||
| 1c42173e5b | |||
| 0b91be5a78 | |||
| c91a24f17b | |||
| 20de09e80f | |||
| 52748d6d35 | |||
| 92a6db5912 | |||
| d9a4858b1d | |||
| fbac316991 | |||
| b9638695b7 | |||
| e2dad68169 | |||
| 466f60ead5 | |||
| 28464b4d12 | |||
| 420576f4ae | |||
| e811cf0f84 | |||
| 7e5a3a530d | |||
| f9778fcbd3 | |||
| 575b3b5400 | |||
| cac682247c | |||
| 82b270616f | |||
| 6f0cd7621b | |||
| f5c6477ef7 | |||
| 1eb07ba750 | |||
| 45ed3c500b | |||
| 7f408bd6cf | |||
| 9cc80c5f36 | |||
| bdee36ca7c | |||
| 9640c330e5 | |||
| 33764d39ba | |||
| 775332b179 | |||
| b2a5f4d58b | |||
| 04800c15af | |||
| fb060721dc | |||
| f079224dd6 | |||
| 5e8024009c | |||
| 2e6cf8734b | |||
| d9be851965 | |||
| 2db10037f2 | |||
| 133951b663 | |||
| 06f70d1d7c | |||
| 7ad6b4b411 | |||
| 239527996a | |||
| 795780da66 | |||
| ce741f055c | |||
| 7d72775af9 | |||
| 540d71f49c | |||
| 220e93596a | |||
| 2c90eee2dd | |||
| edd4eab195 | |||
| 15c409491d | |||
| d060b77e8f | |||
| 6e9d168dd2 | |||
| cb7e8f4910 | |||
| a5af6cf23f | |||
| 9db08a4574 | |||
| 2f817f32ce | |||
| 13696af194 | |||
| 4ee04d0661 | |||
| 7f057faaad | |||
| d7a58216ae | |||
| 4dd128fd18 | |||
| 126b216d44 | |||
| b8ecc0e07e | |||
| 4668c15ea1 | |||
| 867a6850e4 | |||
| adc5ee22cc | |||
| aa6509e01c | |||
| 770ff42496 | |||
| db352ef876 | |||
| acca876697 | |||
| d9e7948920 | |||
| d7feaa0b2a | |||
| ebd7cdb09d | |||
| df091a7d3e | |||
| fea10b3c19 |
@@ -25,7 +25,10 @@
|
||||
|
||||
# Ignore translations as those will be updated by GHA for Localazy download
|
||||
/src/i18n/strings
|
||||
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
||||
/packages/shared-components/src/i18n/strings
|
||||
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
||||
/packages/shared-components/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
||||
|
||||
# Ignore the synapse & mas plugins as this is updated by GHA for docker image updating
|
||||
/playwright/testcontainers/synapse.ts
|
||||
/playwright/testcontainers/mas.ts
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have read through [review guidelines](../docs/review.md) and [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
- [ ] I have read through [review guidelines](https://github.com/element-hq/element-web/blob/develop/docs/review.md) and [CONTRIBUTING.md](https://github.com/element-hq/element-web/blob/develop/CONTRIBUTING.md).
|
||||
- [ ] Tests written for new code (and old code if feasible).
|
||||
- [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation.
|
||||
- [ ] Linter and other CI checks pass.
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: webapp-${{ matrix.image }}
|
||||
path: webapp
|
||||
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
|
||||
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
|
||||
|
||||
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: element-web.deb
|
||||
path: element-web.deb
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: webapp
|
||||
path: dist/develop.tar.gz
|
||||
@@ -104,7 +104,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).)*$
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload).)*$
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -32,25 +32,10 @@ jobs:
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||
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@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and load
|
||||
id: test-build
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
@@ -102,12 +87,64 @@ jobs:
|
||||
images: |
|
||||
vectorim/element-web
|
||||
ghcr.io/element-hq/element-web
|
||||
oci-push.vpn.infra.element.io/element-web
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
flavor: |
|
||||
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||
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@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Connect to Tailscale
|
||||
uses: tailscale/github-action@53acf823325fe9ca47f4cdaa951f90b4b0de5bb9 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||
audience: ${{ secrets.TS_AUDIENCE }}
|
||||
tags: tag:github-actions
|
||||
|
||||
- name: Compute vault jwt role name
|
||||
id: vault-jwt-role
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Get team registry token
|
||||
id: import-secrets
|
||||
uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
url: https://vault.infra.ci.i.element.dev
|
||||
role: ${{ steps.vault-jwt-role.outputs.role_name }}
|
||||
path: service-management/github-actions
|
||||
jwtGithubAudience: https://vault.infra.ci.i.element.dev
|
||||
method: jwt
|
||||
secrets: |
|
||||
services/web-repositories/secret/data/oci.element.io username | OCI_USERNAME ;
|
||||
services/web-repositories/secret/data/oci.element.io password | OCI_PASSWORD ;
|
||||
|
||||
- name: Login to oci.element.io Registry
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: oci-push.vpn.infra.element.io
|
||||
username: ${{ steps.import-secrets.outputs.OCI_USERNAME }}
|
||||
password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
@@ -139,16 +176,3 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: vectorim/element-web
|
||||
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
repository: element-hq/element-web-pro
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
event-type: image-built
|
||||
# Stable way to determine the :version
|
||||
client-payload: |-
|
||||
{
|
||||
"base-ref": "${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
actions: read
|
||||
steps:
|
||||
- name: Download HTML report
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: webapp
|
||||
path: webapp
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
repository: element-hq/element-web
|
||||
|
||||
- name: 📥 Download artifact
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: webapp
|
||||
path: webapp
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }}
|
||||
path: blob-report
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
if: inputs.skip != true
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
pattern: all-blob-reports-*
|
||||
path: all-blob-reports
|
||||
@@ -228,7 +228,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
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: html-report
|
||||
path: playwright-report
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
Exercise caution. Use test accounts.
|
||||
|
||||
- name: 📥 Download artifact
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
branch: actions/playwright-image-updates
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
run: "sudo apt-get install -y tree"
|
||||
|
||||
- name: Download Diffs
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
|
||||
- name: Upload received images & diffs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: received-images
|
||||
path: packages/shared-components/playwright/shared-component-received
|
||||
|
||||
@@ -41,8 +41,8 @@ jobs:
|
||||
- name: Typecheck Shared Components
|
||||
run: "yarn --cwd packages/shared-components run lint:types"
|
||||
|
||||
i18n_lint:
|
||||
name: "i18n Check"
|
||||
i18n_lint_ew:
|
||||
name: "i18n Check (Element Web)"
|
||||
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
|
||||
permissions:
|
||||
pull-requests: read
|
||||
@@ -59,6 +59,15 @@ jobs:
|
||||
devtools|settings|elementCallUrl
|
||||
labs|sliding_sync_description
|
||||
|
||||
i18n_lint_shared_components:
|
||||
name: "i18n Check (Shared Components)"
|
||||
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
|
||||
permissions:
|
||||
pull-requests: read
|
||||
with:
|
||||
path: "packages/shared-components"
|
||||
hardcoded-words: "Element"
|
||||
|
||||
rethemendex_lint:
|
||||
name: "Rethemendex Check"
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
|
||||
- name: Jest Cache
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
|
||||
with:
|
||||
path: /tmp/jest_cache
|
||||
key: ${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifact
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: coverage-${{ matrix.runner }}
|
||||
path: |
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
run: "yarn install"
|
||||
|
||||
- name: Jest Cache
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
|
||||
with:
|
||||
path: /tmp/jest_cache
|
||||
key: ${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifact
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: coverage-sharedcomponents
|
||||
path: |
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
name: Move PRs asking for design review to the design board
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: octokit/graphql-action@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0
|
||||
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
|
||||
id: find_team_members
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
TEAM: "design"
|
||||
- uses: octokit/graphql-action@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0
|
||||
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
|
||||
id: add_to_project
|
||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||
with:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
name: Move PRs asking for design review to the design board
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: octokit/graphql-action@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0
|
||||
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
|
||||
id: find_team_members
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
TEAM: "product"
|
||||
- uses: octokit/graphql-action@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0
|
||||
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
|
||||
id: add_to_project
|
||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||
with:
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
run: "yarn update:jitsi"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
branch: actions/jitsi-update
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
Changes in [1.12.9](https://github.com/element-hq/element-web/releases/tag/v1.12.9) (2026-01-27)
|
||||
================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Allow local log downloads when a rageshake URL is not configured. ([#31716](https://github.com/element-hq/element-web/pull/31716)). Contributed by @Half-Shot.
|
||||
* Improve icon rendering accessibility ([#31776](https://github.com/element-hq/element-web/pull/31776)). Contributed by @t3chguy.
|
||||
* Show "Bob shared this message" on messages shared via MSC4268 ([#31684](https://github.com/element-hq/element-web/pull/31684)). Contributed by @richvdh.
|
||||
* Update the way we render icons for accessibility ([#31731](https://github.com/element-hq/element-web/pull/31731)). Contributed by @t3chguy.
|
||||
* Switch from css masks to rendering svg ([#31681](https://github.com/element-hq/element-web/pull/31681)). Contributed by @t3chguy.
|
||||
* Support for stable MSC4191 account management action parameter ([#31701](https://github.com/element-hq/element-web/pull/31701)). Contributed by @hughns.
|
||||
* Support for stable m.oauth UIA stage from MSC4312 ([#31704](https://github.com/element-hq/element-web/pull/31704)). Contributed by @hughns.
|
||||
* Switch to Compound icons to replace old icons ([#31667](https://github.com/element-hq/element-web/pull/31667)). Contributed by @t3chguy.
|
||||
* Switch from svg masks to svg rendering in more places ([#31652](https://github.com/element-hq/element-web/pull/31652)). Contributed by @t3chguy.
|
||||
* Switch from svg masks to svg rendering in more places ([#31650](https://github.com/element-hq/element-web/pull/31650)). Contributed by @t3chguy.
|
||||
* Update notification icons using Compound icons ([#31671](https://github.com/element-hq/element-web/pull/31671)). Contributed by @t3chguy.
|
||||
* Memoise ListView context ([#31668](https://github.com/element-hq/element-web/pull/31668)). Contributed by @t3chguy.
|
||||
* Switch emoji picker to use emoji for header icons ([#31645](https://github.com/element-hq/element-web/pull/31645)). Contributed by @t3chguy.
|
||||
* Replace icons with Compound alternatives ([#31642](https://github.com/element-hq/element-web/pull/31642)). Contributed by @t3chguy.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix avatar decorations in thread activity centre not being atop avatar ([#31789](https://github.com/element-hq/element-web/pull/31789)). Contributed by @t3chguy.
|
||||
* Fix room settings roles tab getting confused if power level change fails ([#31768](https://github.com/element-hq/element-web/pull/31768)). Contributed by @t3chguy.
|
||||
* Custom themes now import highlights in css ([#31758](https://github.com/element-hq/element-web/pull/31758)). Contributed by @Philldomd.
|
||||
* Use correct translation for url preview settings ([#31740](https://github.com/element-hq/element-web/pull/31740)). Contributed by @florianduros.
|
||||
* Fix error shown if accepting a 3pid invite ([#31735](https://github.com/element-hq/element-web/pull/31735)). Contributed by @dbkr.
|
||||
* Ensure correct focus configuration for Element Call before allowing users to call. ([#31490](https://github.com/element-hq/element-web/pull/31490)). Contributed by @Half-Shot.
|
||||
* Fix emoji font in emoji picker header buttons ([#31679](https://github.com/element-hq/element-web/pull/31679)). Contributed by @t3chguy.
|
||||
* fix flaky test by waiting for chat panel before counting messages ([#31633](https://github.com/element-hq/element-web/pull/31633)). Contributed by @BillCarsonFr.
|
||||
|
||||
|
||||
Changes in [1.12.8](https://github.com/element-hq/element-web/releases/tag/v1.12.8) (2026-01-13)
|
||||
================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
@@ -184,6 +184,16 @@ Please ensure your changes match the cosmetic style of the existing project,
|
||||
and **_never_** mix cosmetic and functional changes in the same commit, as it
|
||||
makes it horribly hard to review otherwise.
|
||||
|
||||
## Shared Components
|
||||
|
||||
When creating new UI components, consider whether they should be added to the shared components package (`packages/shared-components`) rather than directly in the main `src/` directory. Components should be placed in shared components if they:
|
||||
|
||||
- Are reusable across different parts of the application
|
||||
- Could potentially be used by other Element projects (Element Desktop, Aurora, Element modules...)
|
||||
- Follow established patterns and don't have tight coupling to specific application logic
|
||||
|
||||
For more details, see the [shared components README](./packages/shared-components/README.md).
|
||||
|
||||
## Attribution
|
||||
|
||||
Everyone who contributes anything to Matrix is welcome to be listed in the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.20-labs@sha256:dbcde2ebc4abc8bb5c3c499b9c9a6876842bf5da243951cd2697f921a7aeb6a9
|
||||
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:5583cbe5d3347db372d9a9100eba272b548ca1f53246b9b769334bcd0eef2c26 AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:32bde4fc7635942cafb9681e5479a0ba4b2d53b279e44a67ba9303a71fecd706 AS builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
@@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
|
||||
RUN cp /src/config.sample.json /src/webapp/config.json
|
||||
|
||||
# App
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:a6bec37058b9047ece799c01d98dc6d5aa0542b6583cc69f187652f91331a752
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:2c49851f9b34ef35567dc3cbbb56d06d1f56dbb764e75eeb4a599223ee64819c
|
||||
|
||||
# Need root user to install packages & manipulate the usr directory
|
||||
USER root
|
||||
|
||||
@@ -194,6 +194,17 @@ To add a new translation, head to the [translating doc](docs/translating.md).
|
||||
|
||||
For a developer guide, see the [translating dev doc](docs/translating-dev.md).
|
||||
|
||||
# Extending Element Web with Modules
|
||||
|
||||
Element Web supports a module system that allows you to extend or modify functionality at runtime. Modules are loaded dynamically and provide a safe, predictable API for customization.
|
||||
|
||||
## What are modules?
|
||||
|
||||
Modules are extensions that can add or modify Element Web's functionality. They are:
|
||||
|
||||
- Built using the [`@element-hq/element-web-module-api`](https://github.com/element-hq/element-modules/tree/main/packages/element-web-module-api)
|
||||
- Loaded in EW via [config.json](docs/config.md#modules)
|
||||
|
||||
# Triaging issues
|
||||
|
||||
Issues are triaged by community members and the Web App Team, following the [triage process](https://github.com/element-hq/element-meta/wiki/Triage-process).
|
||||
|
||||
@@ -272,6 +272,8 @@ Inheriting all the rules of TypeScript, the following additionally apply:
|
||||
18. Components should serve a single, or near-single, purpose.
|
||||
19. Prefer to derive information from component properties rather than establish state.
|
||||
20. Do not use `React.Component::forceUpdate`.
|
||||
21. Prefer to use [compound typography components](https://compound.element.io/?path=/docs/compound-web_typography--docs) instead of raw HTML elements for text. This ensures consistent font usage and letter spacing across the app.
|
||||
22. If you can't use 21, don't forget to apply the correct CSS classes for font and letter spacing.
|
||||
|
||||
## Stylesheets (\*.pcss = PostCSS + Plugins)
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@ General description of the pattern can be found [here](https://en.wikipedia.org/
|
||||
|
||||
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
|
||||
|
||||
### Practical guidelines for MVVM in element-web
|
||||
## Why are we using MVVM?
|
||||
|
||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||
|
||||
## Practical guidelines for MVVM in element-web
|
||||
|
||||
A first documentation and implementation of MVVM was done in [MVVM-v1.md](MVVM-v1.md). This v1 version is now deprecated and this document describes the current implementation.
|
||||
|
||||
@@ -19,12 +25,12 @@ This is anywhere your data or business logic comes from. If your view model is a
|
||||
#### View
|
||||
|
||||
1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/packages/shared-components). Develop it in storybook!
|
||||
2. Views are simple react components (eg: `FooView`).
|
||||
3. Views use [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) internally where the view model is the external store.
|
||||
2. Views are simple react components (eg: `FooView`) with very little state and logic.
|
||||
3. Views must call `useViewModel` hook with the corresponding view model passed in as argument. This allows the view to re-render when something has changed in the view model. This entire mechanism is powered by [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore).
|
||||
4. Views should define the interface of the view model they expect:
|
||||
|
||||
```tsx
|
||||
// Snapshot is the return type of your view model
|
||||
// Snapshot is the data that your view-model provides which is rendered by the view.
|
||||
interface FooViewSnapshot {
|
||||
value: string;
|
||||
}
|
||||
@@ -34,16 +40,16 @@ This is anywhere your data or business logic comes from. If your view model is a
|
||||
doSomething: () => void;
|
||||
}
|
||||
|
||||
// ViewModel is a type defining the methods needed for `useSyncExternalStore`
|
||||
// ViewModel is an object (usually a class) that implements both the interfaces listed above.
|
||||
// https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/ViewModel.ts
|
||||
type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions;
|
||||
|
||||
interface FooViewProps {
|
||||
// Ideally the view only depends on the view model i.e you don't expect any other props here.
|
||||
vm: FooViewModel;
|
||||
}
|
||||
|
||||
function FooView({ vm }: FooViewProps) {
|
||||
// useViewModel is a helper function that uses useSyncExternalStore under the hood
|
||||
const { value } = useViewModel(vm);
|
||||
return (
|
||||
<button type="button" onClick={() => vm.doSomething()}>
|
||||
@@ -82,8 +88,131 @@ This is anywhere your data or business logic comes from. If your view model is a
|
||||
|
||||
4. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/audio/AudioPlayerViewModel.ts)
|
||||
|
||||
### Benefits
|
||||
### `useViewModel` hook
|
||||
|
||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||
Your view must call this hook with the view-model as argument. Think of this as your view subscribing to the view model.<br>
|
||||
This hook returns the snapshot from your view-model.
|
||||
|
||||
## Disposables and helper hooks
|
||||
|
||||
Disposables provide a mechanism for tracking and releases resources. This is necessary for avoiding memory leaks.
|
||||
|
||||
### Lifecycle of a view model
|
||||
|
||||
The lifecycle of a given view model is from its creation (usually through the constructor i.e `new FooViewModel(prop1, prop2)`) to the time the `dispose` method on it is called (eg: `fooViewModel.dispose()`). It is the responsibility of whoever creates the view model to call the dispose method when the view model is no longer necessary.
|
||||
|
||||
Disposable work by tracking anything that needs to be disposed of and then sequentially disposing them when `viewModel.dispose()` is called.
|
||||
|
||||
### How to use disposables
|
||||
|
||||
Consider the following scenarios:
|
||||
|
||||
#### Scenario 1: Your view model listens to some event on an event emitter <br>
|
||||
|
||||
In the example given below, how do you ensure that the listener on `props.emitter` is removed when the view model is disposed?
|
||||
|
||||
```ts
|
||||
class FooViewModel ... {
|
||||
constructor(props: Props) {
|
||||
...
|
||||
props.emitter.on("my-event", this.doSomething());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use disposables to remove the listener when the view-model is disposed:
|
||||
|
||||
```ts
|
||||
class FooViewModel ... {
|
||||
constructor(props: Props) {
|
||||
...
|
||||
this.disposables.trackListener(props.emitter, "my-event", this.doSomething());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario 2: Your view model creates sub view models <br>
|
||||
|
||||
```ts
|
||||
class FooViewModel ... {
|
||||
constructor(props: Props) {
|
||||
...
|
||||
this.barViewModel = new BarViewModel(...);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here, we want to ensure that when `FooViewModel.dispose()` is called, it also disposes any sub view models (in this case `BarViewModel`):
|
||||
|
||||
```ts
|
||||
class FooViewModel ... {
|
||||
constructor(props: Props) {
|
||||
...
|
||||
this.barViewModel = this.disposables.track(new BarViewModel(...));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario 3: Tracking and disposing arbitrary resources <br>
|
||||
|
||||
A disposable is:
|
||||
|
||||
- a function
|
||||
- an object with `dispose` method (like a view model)
|
||||
|
||||
You can therefore use disposables to track any resource that must be eventually relinquished, eg:
|
||||
|
||||
```ts
|
||||
class Call {
|
||||
....
|
||||
public endCall();
|
||||
public stopConnections();
|
||||
}
|
||||
|
||||
class CallViewModel {
|
||||
...
|
||||
constructor(props: Props) {
|
||||
const call = new Call();
|
||||
// When the view model is disposed, the following call methods will be called
|
||||
this.disposables.track(() => {
|
||||
call.endCall();
|
||||
call.stopConnections();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Disposing view models from non-MVVMed react components
|
||||
|
||||
While we eventually want all our UI code to use MVVM, the current reality is that most of the existing code is just normal react components. We follow a bottoms up approach when it comes to moving code over to MVVM i.e we deal with child components before dealing with parent components.
|
||||
|
||||
This means that you need to dispose child view models from the non-MVVMed parent component.
|
||||
|
||||
#### Class component:
|
||||
|
||||
Create the view model in `componentDidMount()` and dispose the view model in `componentWillUnmount()`:
|
||||
|
||||
```ts
|
||||
class FooComponent extends Component {
|
||||
componentDidMount() {
|
||||
this.barViewModel = new BarViewModel(...);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.barViewModel.dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Functional Component:
|
||||
|
||||
Use the `useCreateAutoDisposedViewModel` hook:
|
||||
|
||||
```ts
|
||||
export function FooComponent(props) {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new BarViewModel(...));
|
||||
return <BarView vm={vm}>;
|
||||
}
|
||||
```
|
||||
|
||||
This hook will call the `dispose` method on the view model when `FooComponent` is unmounted.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# Build
|
||||
|
||||
- [Customisations](customisations.md)
|
||||
- [Modules](modules.md)
|
||||
- [Deprecated Modules](deprecated-modules.md)
|
||||
- [Native Node modules](native-node-modules.md)
|
||||
|
||||
# Contribution
|
||||
@@ -40,6 +40,8 @@
|
||||
- [Feature flags](feature-flags.md)
|
||||
- [OIDC and delegated authentication](oidc.md)
|
||||
- [Release Process](release.md)
|
||||
- [MVVM](MVVM.md)
|
||||
- [Settings](settings.md)
|
||||
|
||||
# Deep dive
|
||||
|
||||
|
||||
@@ -407,6 +407,7 @@ If you run your own rageshake server to collect bug reports, the following optio
|
||||
1. `bug_report_endpoint_url`: URL for where to submit rageshake logs to. Rageshakes include feedback submissions and bug reports. When
|
||||
not present in the config, the app will disable all rageshake functionality. Set to `https://rageshakes.element.io/api/submit` to submit
|
||||
rageshakes to us, or use your own rageshake server.
|
||||
You may also set the value to `"local"` if you wish to only store logs locally, in order to download them for debugging.
|
||||
2. `uisi_autorageshake_app`: If a user has enabled the "automatically send debug logs on decryption errors" flag, this option will be sent
|
||||
alongside the rageshake so the rageshake server can filter them by app name. By default, this will be `element-auto-uisi`
|
||||
(in contrast to other rageshakes submitted by the app, which use `element-web`).
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Module system
|
||||
# Deprecated Module system
|
||||
|
||||
> [!CAUTION]
|
||||
> DEPRECATED. Use [Element web module api](https://github.com/element-hq/element-modules/tree/main/packages/element-web-module-api) instead.
|
||||
|
||||
The module system in Element Web is a way to add or modify functionality of Element Web itself, bundled at compile time
|
||||
for the app. This means that modules are loaded as part of the `yarn build` process but have an effect on user experience
|
||||
@@ -11,7 +11,7 @@ import { env } from "process";
|
||||
import type { Config } from "jest";
|
||||
|
||||
const config: Config = {
|
||||
testEnvironment: "jsdom",
|
||||
testEnvironment: "jest-fixed-jsdom",
|
||||
testEnvironmentOptions: {
|
||||
url: "http://localhost/",
|
||||
// This is needed to be able to load dual CJS/ESM WASM packages e.g. rust crypto & matrix-wywiwyg
|
||||
@@ -39,11 +39,10 @@ const config: Config = {
|
||||
"workers/(.+)Factory": "<rootDir>/__mocks__/workerFactoryMock.js",
|
||||
"^!!raw-loader!.*": "jest-raw-loader",
|
||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
||||
"counterpart": "<rootDir>/node_modules/counterpart",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp)).+$",
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp|matrix-web-i18n)).+$",
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
"<rootDir>/src/**/*.{js,ts,tsx}",
|
||||
|
||||
@@ -43,7 +43,8 @@ export default {
|
||||
// Embedded into webapp
|
||||
"@element-hq/element-call-embedded",
|
||||
// Transitive dep of jest
|
||||
"jsdom",
|
||||
"@jest/globals",
|
||||
"vitest-environment-jest-fixed-jsdom",
|
||||
|
||||
// Used by matrix-js-sdk, which means we have to include them as a
|
||||
// dependency so that // we can run `tsc` (since we import the typescript
|
||||
|
||||
@@ -18,6 +18,18 @@
|
||||
"file": "element-web.json",
|
||||
"excludes": ["src/i18n/strings/en_EN.json"],
|
||||
"lang": "${autodetectLang}"
|
||||
},
|
||||
{
|
||||
"pattern": "packages/shared-components/src/i18n/strings/en_EN.json",
|
||||
"file": "shared-components.json",
|
||||
"lang": "inherited"
|
||||
},
|
||||
{
|
||||
"group": "existing",
|
||||
"pattern": "packages/shared-components/src/i18n/strings/*.json",
|
||||
"file": "shared-components.json",
|
||||
"excludes": ["packages/shared-components/src/i18n/strings/en_EN.json"],
|
||||
"lang": "${autodetectLang}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -27,6 +39,10 @@
|
||||
{
|
||||
"conditions": "equals: ${file}, element-web.json",
|
||||
"output": "src/i18n/strings/${langLsrUnderscore}.json"
|
||||
},
|
||||
{
|
||||
"conditions": "equals: ${file}, shared-components.json",
|
||||
"output": "packages/shared-components/src/i18n/strings/${langLsrUnderscore}.json"
|
||||
}
|
||||
],
|
||||
"includeSourceLang": "${includeSourceLang|false}",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.12.8",
|
||||
"version": "1.12.9",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@@ -29,9 +29,9 @@
|
||||
"UserFriendlyError"
|
||||
],
|
||||
"scripts": {
|
||||
"i18n": "matrix-gen-i18n src res packages/shared-components/src && yarn i18n:sort && yarn i18n:lint",
|
||||
"i18n:sort": "jq --sort-keys '.' src/i18n/strings/en_EN.json > src/i18n/strings/en_EN.json.tmp && mv src/i18n/strings/en_EN.json.tmp src/i18n/strings/en_EN.json",
|
||||
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null",
|
||||
"i18n": "matrix-gen-i18n src res && yarn i18n:sort && yarn i18n:lint",
|
||||
"i18n:sort": "matrix-sort-i18n src/i18n/strings/en_EN.json && yarn --cwd packages/shared-components i18n:sort",
|
||||
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null && yarn --cwd packages/shared-components i18n:lint",
|
||||
"i18n:diff": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && yarn i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
||||
"make-component": "node scripts/make-react-component.js",
|
||||
"rethemendex": "./res/css/rethemendex.sh",
|
||||
@@ -69,12 +69,12 @@
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/pretty-format/react-is": "19.2.1",
|
||||
"**/pretty-format/react-is": "19.2.3",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"oidc-client-ts": "3.4.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"caniuse-lite": "1.0.30001759",
|
||||
"caniuse-lite": "1.0.30001762",
|
||||
"testcontainers": "^11.0.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
||||
@@ -85,15 +85,15 @@
|
||||
"@element-hq/web-shared-components": "link:packages/shared-components",
|
||||
"@fontsource/fira-code": "^5",
|
||||
"@fontsource/inter": "^5",
|
||||
"@formatjs/intl-segmenter": "^11.5.7",
|
||||
"@formatjs/intl-segmenter": "^12.0.0",
|
||||
"@matrix-org/analytics-events": "^0.30.0",
|
||||
"@matrix-org/emojibase-bindings": "^1.5.0",
|
||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^10.0.0",
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@vector-im/compound-design-tokens": "6.4.3",
|
||||
"@vector-im/compound-web": "^8.3.4",
|
||||
"@vector-im/compound-design-tokens": "6.8.0",
|
||||
"@vector-im/compound-web": "^8.3.5",
|
||||
"@vector-im/matrix-wysiwyg": "2.40.0",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
@@ -129,7 +129,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"maplibre-gl": "^5.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-js-sdk": "40.0.0",
|
||||
"matrix-js-sdk": "40.1.0",
|
||||
"matrix-widget-api": "^1.15.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"mime": "^4.0.4",
|
||||
@@ -137,7 +137,7 @@
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"posthog-js": "1.302.2",
|
||||
"posthog-js": "1.313.0",
|
||||
"qrcode": "1.5.4",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "^19.0.0",
|
||||
@@ -181,7 +181,8 @@
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@casualbot/jest-sonar-reporter": "2.5.0",
|
||||
"@element-hq/element-call-embedded": "0.16.3",
|
||||
"@element-hq/element-web-playwright-common": "^2.0.0",
|
||||
"@element-hq/element-web-playwright-common": "2.2.3",
|
||||
"@fetch-mock/jest": "^0.2.20",
|
||||
"@peculiar/webcrypto": "^1.4.3",
|
||||
"@playwright/test": "1.57.0",
|
||||
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
||||
@@ -210,7 +211,6 @@
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/modernizr": "^3.5.3",
|
||||
"@types/node": "18",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/pako": "^2.0.0",
|
||||
"@types/qrcode": "^1.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
@@ -231,7 +231,6 @@
|
||||
"chokidar": "^5.0.0",
|
||||
"concurrently": "^9.0.0",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"core-js": "^3.38.1",
|
||||
"cronstrue": "^3.0.0",
|
||||
"css-loader": "^7.0.0",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
@@ -250,25 +249,23 @@
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"express": "^5.0.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
"fetch-mock": "9.11.0",
|
||||
"fetch-mock-jest": "^1.5.1",
|
||||
"file-loader": "^6.0.0",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"husky": "^9.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^30.0.0",
|
||||
"jest-canvas-mock": "^2.5.2",
|
||||
"jest-environment-jsdom": "^30.0.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"jest-fixed-jsdom": "^0.0.11",
|
||||
"jest-mock": "^30.0.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"knip": "^5.36.2",
|
||||
"lint-staged": "^16.0.0",
|
||||
"matrix-web-i18n": "^3.2.1",
|
||||
"matrix-web-i18n": "3.5.2",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"minimist": "^1.2.6",
|
||||
"modernizr": "^3.12.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"patch-package": "^8.0.0",
|
||||
"playwright-core": "^1.51.0",
|
||||
"postcss": "8.4.46",
|
||||
@@ -294,7 +291,7 @@
|
||||
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"testcontainers": "^11.0.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript": "5.9.3",
|
||||
"util": "^0.12.5",
|
||||
"web-streams-polyfill": "^4.0.0",
|
||||
"webpack": "^5.89.0",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Even though this (at time of writing) is identical Element Web's
|
||||
// .prettierrc.js, shared components needs its own because otherwise
|
||||
// this refers to element web's copy of eslint-plugin-matrix-org which
|
||||
// would require element-web's modules to be installed.
|
||||
module.exports = require("eslint-plugin-matrix-org/.prettierrc.js");
|
||||
@@ -10,16 +10,14 @@ import { WithTooltip, IconButton, TooltipLinkList } from "storybook/internal/com
|
||||
import React from "react";
|
||||
import { GlobeIcon } from "@storybook/icons";
|
||||
|
||||
// We can't import `shared/i18n.tsx` directly here.
|
||||
// The storybook addon doesn't seem to benefit the vite config of storybook and we can't resolve the alias in i18n.tsx.
|
||||
import json from "../../../webapp/i18n/languages.json";
|
||||
const languages = Object.keys(json).filter((lang) => lang !== "default");
|
||||
const languages = JSON.parse(process.env.STORYBOOK_LANGUAGES);
|
||||
|
||||
/**
|
||||
* Returns the title of a language in the user's locale.
|
||||
*/
|
||||
function languageTitle(language: string): string {
|
||||
return new Intl.DisplayNames([language], { type: "language", style: "short" }).of(language) || language;
|
||||
const normalisedLang = language.toLowerCase().replace("_", "-");
|
||||
return new Intl.DisplayNames([normalisedLang], { type: "language", style: "short" }).of(normalisedLang) || language;
|
||||
}
|
||||
|
||||
export const languageAddon: Addon = {
|
||||
|
||||
@@ -7,12 +7,15 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { nodePolyfills } from "vite-plugin-node-polyfills";
|
||||
import { mergeConfig } from "vite";
|
||||
|
||||
// Get a list of available languages so the language selector can display them at runtime
|
||||
const languages = fs.readdirSync("src/i18n/strings").map((f) => f.slice(0, -5));
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
staticDirs: ["../../../webapp"],
|
||||
addons: ["@storybook/addon-docs", "@storybook/addon-designs", "@storybook/addon-a11y"],
|
||||
framework: "@storybook/react-vite",
|
||||
core: {
|
||||
@@ -29,8 +32,42 @@ const config: StorybookConfig = {
|
||||
$webapp: path.resolve("../../webapp"),
|
||||
},
|
||||
},
|
||||
// Needed for counterpart to work
|
||||
plugins: [nodePolyfills({ include: ["process", "util"] })],
|
||||
plugins: [
|
||||
// Needed for counterpart to work
|
||||
nodePolyfills({ include: ["process", "util"] }),
|
||||
{
|
||||
name: "language-middleware",
|
||||
configureServer(server) {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
if (req.url === "/i18n/languages.json") {
|
||||
// Dynamically generate a languages.json file based on what files are available
|
||||
const langJson: Record<string, string> = {};
|
||||
for (const lang of languages) {
|
||||
const normalizedLanguage = lang.toLowerCase().replace("_", "-");
|
||||
const languageParts = normalizedLanguage.split("-");
|
||||
if (languageParts.length === 2 && languageParts[0] === languageParts[1]) {
|
||||
langJson[languageParts[0]] = `${lang}.json`;
|
||||
} else {
|
||||
langJson[normalizedLanguage] = `${lang}.json`;
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(JSON.stringify(langJson));
|
||||
} else if (req.url?.startsWith("/i18n/")) {
|
||||
// Serve the individual language files, which annoyingly can't be a simple
|
||||
// static dir because the directory structure in src doesn't match what
|
||||
// the app requests.
|
||||
const langFile = req.url.split("/").pop();
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
fs.createReadStream(`src/i18n/strings/${langFile}`).pipe(res);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
server: {
|
||||
allowedHosts: ["localhost", ".docker.internal"],
|
||||
},
|
||||
@@ -42,5 +79,9 @@ const config: StorybookConfig = {
|
||||
url: "https://element-hq.github.io/compound-web/",
|
||||
},
|
||||
},
|
||||
env: (config) => ({
|
||||
...config,
|
||||
STORYBOOK_LANGUAGES: JSON.stringify(languages),
|
||||
}),
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -20,7 +20,7 @@ const config: TestRunnerConfig = {
|
||||
|
||||
// If you want to take screenshot of multiple browsers, use
|
||||
// page.context().browser().browserType().name() to get the browser name to prefix the file name
|
||||
const image = await page.screenshot();
|
||||
const image = await page.screenshot({ animations: "disabled" });
|
||||
expect(image).toMatchImageSnapshot({
|
||||
customSnapshotsDir,
|
||||
customSnapshotIdentifier: `${context.id}-${process.platform}`,
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# @element-hq/web-shared-components
|
||||
|
||||
Shared React components library for Element Web, Aurora, Element
|
||||
modules... This package provides opinionated UI components built on top of the
|
||||
[Compound Design System](https://compound.element.io) and [Compound
|
||||
Web](https://github.com/element-hq/compound-web). This is not a design system
|
||||
by itself, but rather a set of larger components.
|
||||
|
||||
## Installation in a new project
|
||||
|
||||
When adding this library to a new project, as well as installing
|
||||
`@element-hq/web-shared-components` as normal, you will also need to add
|
||||
[compound-web](https://github.com/element-hq/compound-web) as a peer
|
||||
dependency:
|
||||
|
||||
```bash
|
||||
yarn add @element-hq/web-shared-components
|
||||
yarn add @vector-im/compound-web
|
||||
```
|
||||
|
||||
(This avoids problems where we end up with different versions of compound-web in the
|
||||
top-level project and web-shared-components).
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Import
|
||||
|
||||
Both JavaScript and CSS can be imported as follows:
|
||||
|
||||
```javascript
|
||||
import { RoomListHeaderView, useViewModel } from "@element-hq/web-shared-components";
|
||||
import "@element-hq/web-shared-components/dist/element-web-shared-components.css";
|
||||
```
|
||||
|
||||
or in CSS file:
|
||||
|
||||
```css
|
||||
@import url("@element-hq/web-shared-components");
|
||||
```
|
||||
|
||||
### Using Components
|
||||
|
||||
There are two kinds of components in this library:
|
||||
|
||||
- _regular_ react component which doesn't follow specific pattern.
|
||||
- _view_ component(MVVM pattern).
|
||||
|
||||
> [!TIP]
|
||||
> These components are available in the project storybook.
|
||||
|
||||
#### Regular Components
|
||||
|
||||
These components can be used directly by passing props. Example:
|
||||
|
||||
```tsx
|
||||
import { Flex } from "@element-hq/web-shared-components";
|
||||
function MyApp() {
|
||||
return <Flex align="center" />;
|
||||
}
|
||||
```
|
||||
|
||||
#### View (MVVM) Components
|
||||
|
||||
These components follow the [MVVM pattern](../../docs/MVVM.md). A ViewModel
|
||||
instance should be provided as a prop.
|
||||
|
||||
Here's a basic example:
|
||||
|
||||
```jsx
|
||||
import { ViewExample } from "@element-hq/web-shared-components";
|
||||
|
||||
function MyApp() {
|
||||
const viewModel = new ViewModelExample();
|
||||
return <ViewExample vm={viewModel} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Utilities
|
||||
|
||||
#### Internationalization
|
||||
|
||||
- `useI18n()` - Hook for translations
|
||||
- `I18nApi` - Internationalization API utilities
|
||||
|
||||
#### Date & Time
|
||||
|
||||
- `DateUtils` - Date formatting and manipulation
|
||||
- `humanize` - Human-readable time formatting
|
||||
|
||||
#### Formatting
|
||||
|
||||
- `FormattingUtils` - Text and data formatting utilities
|
||||
- `numbers` - Number formatting utilities
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- Yarn 1.22.22+
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
yarn install
|
||||
|
||||
# Build the library
|
||||
yarn prepare
|
||||
```
|
||||
|
||||
### Running Storybook
|
||||
|
||||
```bash
|
||||
yarn storybook
|
||||
```
|
||||
|
||||
### Write components
|
||||
|
||||
Most components should be written as [MVVM pattern](../../docs/MVVM.md) view
|
||||
components. See existing components for examples. The exceptions are low level
|
||||
components that don't need a view model.
|
||||
|
||||
### Tests
|
||||
|
||||
Two types of tests are available: unit tests and visual regression tests.
|
||||
|
||||
### Unit Tests
|
||||
|
||||
These tests cover the logic of the components and utilities. Built with Jest
|
||||
and React Testing Library.
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
### Visual Regression Tests
|
||||
|
||||
These tests ensure the UI components render correctly. They need Storybook to
|
||||
be running and they will run in docker using [Playwright](../../playwright.md).
|
||||
|
||||
First run storybook:
|
||||
|
||||
```bash
|
||||
yarn storybook
|
||||
```
|
||||
|
||||
Then, in another terminal, run:
|
||||
|
||||
```bash
|
||||
yarn test:storybook:update
|
||||
```
|
||||
|
||||
Each story will be rendered and a screenshot will be taken and compared to the
|
||||
existing baseline. If there are visual changes or AXE violation, the test will
|
||||
fail.
|
||||
|
||||
### Translations
|
||||
|
||||
First see our [translation guide](../../docs/translation.md) and [translation dev guide](../../docs/translation-dev.md).
|
||||
To generate translation strings for this package, run:
|
||||
|
||||
```bash
|
||||
yarn i18n
|
||||
```
|
||||
@@ -10,7 +10,7 @@ import { env } from "process";
|
||||
import type { Config } from "jest";
|
||||
|
||||
const config: Config = {
|
||||
testEnvironment: "jsdom",
|
||||
testEnvironment: "jest-fixed-jsdom",
|
||||
testEnvironmentOptions: {
|
||||
url: "http://localhost/",
|
||||
},
|
||||
@@ -30,7 +30,7 @@ const config: Config = {
|
||||
"workers/(.+)Factory": "<rootDir>/__mocks__/workerFactoryMock.js",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|@storybook|storybook)).+$",
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|@storybook|storybook|matrix-web-i18n)).+$",
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
"<rootDir>/src/**/*.{js,ts,tsx}",
|
||||
|
||||
@@ -34,8 +34,11 @@
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"i18n": "matrix-gen-i18n src && yarn i18n:sort && yarn i18n:lint",
|
||||
"i18n:sort": "jq --sort-keys '.' src/i18n/strings/en_EN.json > src/i18n/strings/en_EN.json.tmp && mv src/i18n/strings/en_EN.json.tmp src/i18n/strings/en_EN.json",
|
||||
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null",
|
||||
"test": "jest",
|
||||
"prepare": "patch-package && yarn --cwd ../.. build:res && node scripts/gatherTranslationKeys.ts && vite build",
|
||||
"prepare": "patch-package && vite build",
|
||||
"storybook": "storybook dev -p 6007",
|
||||
"build-storybook": "storybook build",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
@@ -54,13 +57,13 @@
|
||||
"classnames": "^2.5.1",
|
||||
"counterpart": "^0.18.6",
|
||||
"lodash": "^4.17.21",
|
||||
"matrix-web-i18n": "^3.4.0",
|
||||
"matrix-web-i18n": "3.5.2",
|
||||
"patch-package": "^8.0.1",
|
||||
"react-merge-refs": "^3.0.2",
|
||||
"temporal-polyfill": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@element-hq/element-web-playwright-common": "^2.0.0",
|
||||
"@element-hq/element-web-playwright-common": "2.2.3",
|
||||
"@playwright/test": "1.57.0",
|
||||
"@storybook/addon-a11y": "^10.0.7",
|
||||
"@storybook/addon-designs": "^11.0.1",
|
||||
@@ -93,6 +96,6 @@
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||
"peerDependencies": {
|
||||
"@vector-im/compound-web": "^8.3.4"
|
||||
"@vector-im/compound-web": "^8.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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.
|
||||
*/
|
||||
|
||||
// Gathers all the translation keys from element-web's en_EN.json into a TypeScript type definition file
|
||||
// that exports a type `TranslationKey` which is a union of all supported translation keys.
|
||||
// This prevents having to import the json file and make typescript do the work as this results in vite-dts
|
||||
// generating an import to the json file in the .d.ts which doesn't work at runtime: this way, the type
|
||||
// gets put into the bundle.
|
||||
// XXX: It should *not* be in the 'src' directory, being a generated file, but if it isn't then the type
|
||||
// bundler won't bundle the types and will leave the file as a relative import, which will break.
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const i18nStringsPath = path.resolve(__dirname, "../../../src/i18n/strings/en_EN.json");
|
||||
const outPath = path.resolve(__dirname, "../src/i18nKeys.d.ts");
|
||||
|
||||
function gatherKeys(obj: any, prefix: string[] = []): string[] {
|
||||
if (typeof obj !== "object" || obj === null) return [];
|
||||
let keys: string[] = [];
|
||||
for (const key of Object.keys(obj)) {
|
||||
const value = obj[key];
|
||||
|
||||
// add the path (for both leaves and intermediates as then we include plurals)
|
||||
keys.push([...prefix, key].join("|"));
|
||||
if (typeof value === "object" && value !== null) {
|
||||
// If the value is an object, recurse
|
||||
keys = keys.concat(gatherKeys(value, [...prefix, key]));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const json = JSON.parse(fs.readFileSync(i18nStringsPath, "utf8"));
|
||||
const keys = gatherKeys(json);
|
||||
const typeDef =
|
||||
"/*\n" +
|
||||
" * Copyright 2025 Element Creations Ltd.\n" +
|
||||
" *\n" +
|
||||
" * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial\n" +
|
||||
" * Please see LICENSE files in the repository root for full details.\n" +
|
||||
" */\n" +
|
||||
"\n" +
|
||||
"// This file is auto-generated by gatherTranslationKeys.ts\n" +
|
||||
"// Do not edit manually.\n\n" +
|
||||
"export type TranslationKey =\n" +
|
||||
keys.map((k) => ` | \"${k}\"`).join("\n") +
|
||||
";\n";
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
||||
fs.writeFileSync(outPath, typeDef, "utf8");
|
||||
console.log(`Wrote ${keys.length} keys to ${outPath}`);
|
||||
}
|
||||
|
||||
if (import.meta.url.startsWith("file:")) {
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
if (process.argv[1] === modulePath) {
|
||||
main();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Copyright 2025 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 TranslationKey as _TranslationKey } from "matrix-web-i18n";
|
||||
|
||||
import type Translations from "../i18n/strings/en_EN.json";
|
||||
|
||||
declare global {
|
||||
type TranslationKey = _TranslationKey<typeof Translations>;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ exports[`AudioPlayerView renders the audio player in default state 1`] = `
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
class="_icon-button_1215g_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -107,7 +107,7 @@ exports[`AudioPlayerView renders the audio player in error state 1`] = `
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="_r_i_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
class="_icon-button_1215g_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -203,7 +203,7 @@ exports[`AudioPlayerView renders the audio player without media name 1`] = `
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="_r_6_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
class="_icon-button_1215g_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -294,7 +294,7 @@ exports[`AudioPlayerView renders the audio player without size 1`] = `
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="_r_c_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
class="_icon-button_1215g_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
||||
@@ -6,7 +6,7 @@ exports[`PlayPauseButton renders the button in default state 1`] = `
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
class="_icon-button_1215g_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -38,7 +38,7 @@ exports[`PlayPauseButton renders the button in playing state 1`] = `
|
||||
aria-disabled="false"
|
||||
aria-label="Pause"
|
||||
aria-labelledby="_r_6_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
class="_icon-button_1215g_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
||||
@@ -12,6 +12,7 @@ import React, {
|
||||
type ReactNode,
|
||||
type PropsWithChildren,
|
||||
useMemo,
|
||||
type HTMLAttributes,
|
||||
} from "react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import CheckCircleIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle";
|
||||
@@ -32,8 +33,6 @@ interface BannerProps {
|
||||
*/
|
||||
avatar?: React.ReactNode;
|
||||
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* Actions presented to the user in the right-hand side of the banner alongside the dismiss button.
|
||||
*/
|
||||
@@ -60,21 +59,21 @@ export function Banner({
|
||||
actions,
|
||||
onClose,
|
||||
...props
|
||||
}: PropsWithChildren<BannerProps>): ReactElement {
|
||||
}: PropsWithChildren<BannerProps & HTMLAttributes<HTMLDivElement>>): ReactElement {
|
||||
const classes = classNames(styles.banner, className);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
const icon = useMemo((): ReactElement => {
|
||||
switch (type) {
|
||||
case "critical":
|
||||
return <ErrorIcon fontSize={24} {...props} />;
|
||||
return <ErrorIcon fontSize={24} />;
|
||||
case "info":
|
||||
return <InfoIcon fontSize={24} {...props} />;
|
||||
return <InfoIcon fontSize={24} />;
|
||||
case "success":
|
||||
return <CheckCircleIcon fontSize={24} {...props} />;
|
||||
return <CheckCircleIcon fontSize={24} />;
|
||||
default:
|
||||
return <InfoIcon fontSize={24} {...props} />;
|
||||
return <InfoIcon fontSize={24} />;
|
||||
}
|
||||
}, [type, props]);
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<div {...props} className={classes} data-type={type}>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Panel posunu zvuku"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Smazat",
|
||||
"dismiss": "Zavřít",
|
||||
"explore_rooms": "Procházet místnosti",
|
||||
"pause": "Pozastavit",
|
||||
"play": "Přehrát",
|
||||
"search": "Hledání"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Otevřít číselník"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "před jedním dnem",
|
||||
"about_hour_ago": "asi před hodinou",
|
||||
"about_minute_ago": "před minutou",
|
||||
"few_seconds_ago": "před pár vteřinami",
|
||||
"in_about_day": "asi za den",
|
||||
"in_about_hour": "asi za hodinu",
|
||||
"in_about_minute": "asi za minutu",
|
||||
"in_few_seconds": "za pár vteřin",
|
||||
"in_n_days": "za %(num)s dní",
|
||||
"in_n_hours": "za %(num)s hodin",
|
||||
"in_n_minutes": "za %(num)s minut",
|
||||
"n_days_ago": "před %(num)s dny",
|
||||
"n_hours_ago": "před %(num)s hodinami",
|
||||
"n_minutes_ago": "před %(num)s minutami"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Audio přehrávač",
|
||||
"error_downloading_audio": "Chyba při stahování audia",
|
||||
"unnamed_audio": "Nepojmenovaný audio soubor"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Bar chwilio sain"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Dileu",
|
||||
"dismiss": "Gwrthod",
|
||||
"explore_rooms": "Archwilio Ystafelloedd",
|
||||
"pause": "Oedi",
|
||||
"play": "Chwarae",
|
||||
"retry": "Ceisio eto",
|
||||
"search": "Chwilio"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Agor y pad deialu"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"delete_all": "Dileu'r cyfan",
|
||||
"exceeded_resource_limit_description": "Cysylltwch â gweinyddwr eich gwasanaeth i barhau i ddefnyddio'r gwasanaeth.",
|
||||
"exceeded_resource_limit_title": "Dyw eich neges heb ei anfon oherwydd bod y gweinydd cartref hwn wedi mynd y tu hwnt i derfyn ei adnoddau.",
|
||||
"failed_to_create_room_title": "Methwyd dechrau sgwrs gyda'r defnyddiwr hwn",
|
||||
"history_visible": "Mae'r ystafell hon wedi'i ffurfweddu fel y gall aelodau newydd ddarllen hanes. <a>Dysgu Mwy</a>",
|
||||
"homeserver_blocked_title": "Dyw eich neges heb ei anfon oherwydd bod y gweinydd cartref hwn wedi'i rwystro gan ei weinyddwr.",
|
||||
"monthly_user_limit_reached_title": "Dyw eich neges heb ei anfon oherwydd bod y gweinydd cartref hwn wedi cyrraedd ei Derfyn Defnyddiwr Gweithredol Misol.",
|
||||
"requires_consent_agreement_title": "Does dim modd i chi anfon unrhyw negeseuon nes i chi adolygu a chytuno â'n telerau ac amodau.",
|
||||
"retry_all": "Ail-geisio popeth",
|
||||
"select_messages_to_retry": "Gallwch ddewis pob neges neu negeseuon unigol i geisio eto neu eu dileu",
|
||||
"server_connectivity_lost_description": "Bydd negeseuon sy'n cael eu hanfon yn cael eu cadw nes bod eich cysylltiad wedi dychwelyd.",
|
||||
"server_connectivity_lost_title": "Mae'r cysylltiad â'r gweinydd wedi'i golli.",
|
||||
"some_messages_not_sent": "Dyw rhai o'ch negeseuon heb eu hanfon"
|
||||
}
|
||||
},
|
||||
"terms": {
|
||||
"tac_button": "Adolygwch y telerau a'r amodau"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "tua diwrnod yn ôl",
|
||||
"about_hour_ago": "tua awr yn ol",
|
||||
"about_minute_ago": "tua munud yn ôl",
|
||||
"few_seconds_ago": "ychydig eiliadau yn ôl",
|
||||
"in_about_day": "tua diwrnod o nawr",
|
||||
"in_about_hour": "tuag awr o hyn",
|
||||
"in_about_minute": "tua munud o nawr",
|
||||
"in_few_seconds": "ychydig eiliadau o nawr",
|
||||
"in_n_days": "%(num)s diwrnod o nawr",
|
||||
"in_n_hours": "%(num)s awr o nawr",
|
||||
"in_n_minutes": "%(num)s munud o nawr",
|
||||
"n_days_ago": "%(num)s diwrnod yn ôl",
|
||||
"n_hours_ago": "%(num)s awr yn ôl",
|
||||
"n_minutes_ago": "%(num)s munud yn ôl"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Chwaraewr sain",
|
||||
"error_downloading_audio": "Gwall wrth llwytho i lawrsain",
|
||||
"unnamed_audio": "Sain dienw"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Progressionsmarkør for lydafspiller"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Slet",
|
||||
"dismiss": "Afvis",
|
||||
"explore_rooms": "Udforsk rum",
|
||||
"pause": "Pausér",
|
||||
"play": "Afspil",
|
||||
"search": "Søg"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "omkring en dag siden",
|
||||
"about_hour_ago": "for omkring en time siden",
|
||||
"about_minute_ago": "for omkring et minut siden",
|
||||
"few_seconds_ago": "for et par sekunder siden",
|
||||
"in_about_day": "om cirka en dag fra nu",
|
||||
"in_about_hour": "omkring en time fra nu",
|
||||
"in_about_minute": "omkring et minut fra nu",
|
||||
"in_few_seconds": "et par sekunder fra nu",
|
||||
"in_n_days": "%(num)s dage fra nu",
|
||||
"in_n_hours": "%(num)s timer fra nu",
|
||||
"in_n_minutes": "%(num)s minutter fra nu",
|
||||
"n_days_ago": "%(num)s dage siden",
|
||||
"n_hours_ago": "%(num)s timer siden",
|
||||
"n_minutes_ago": "%(num)s minutter siden"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Fejl ved download af lyd",
|
||||
"unnamed_audio": "Unavngiven lyd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Audio-Suchleiste"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Löschen",
|
||||
"dismiss": "Ausblenden",
|
||||
"explore_rooms": "Chats erkunden",
|
||||
"pause": "Pausieren",
|
||||
"play": "Abspielen",
|
||||
"retry": "Erneut versuchen",
|
||||
"search": "Suchen"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Wähltastatur öffnen"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"delete_all": "Alle löschen",
|
||||
"exceeded_resource_limit_title": "Deine Nachricht konnte nicht versendet werden, da dein Homeserver ein Ressourcenlimit überschritten hat.",
|
||||
"failed_to_create_room_title": "Es konnte kein Chat mit diesem Nutzer gestartet werden",
|
||||
"history_visible": "Diese Gruppe wurde konfiguriert, neuen Mitgliedern Zugriff auf den vergangenen Nachrichtenverlauf zu gestatten. <a>Mehr erfahren</a>",
|
||||
"homeserver_blocked_title": "Deine Nachricht konnte nicht versendet werden, da der Admin deinen Homeserver gesperrt hat.",
|
||||
"monthly_user_limit_reached_title": "Deine Nachricht konnte nicht versendet werden, da dein Homeserver das monatliche Nutzerlimit erreicht hat.",
|
||||
"requires_consent_agreement_title": "Du kannst erst dann Nachrichten verschicken, wenn du unsere Geschäftsbedingungen gelesen und akzeptiert hast.",
|
||||
"select_messages_to_retry": "Du kannst einzelne oder alle Nachrichten erneut senden oder löschen",
|
||||
"server_connectivity_lost_description": "Nachrichten werden gespeichert und gesendet, wenn die Internetverbindung wiederhergestellt ist.",
|
||||
"server_connectivity_lost_title": "Verbindung zum Server wurde unterbrochen.",
|
||||
"some_messages_not_sent": "Einige Nachrichten konnten nicht gesendet werden"
|
||||
}
|
||||
},
|
||||
"terms": {
|
||||
"tac_button": "Geschäftsbedingungen anzeigen"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "vor etwa einem Tag",
|
||||
"about_hour_ago": "vor etwa einer Stunde",
|
||||
"about_minute_ago": "vor etwa einer Minute",
|
||||
"few_seconds_ago": "vor ein paar Sekunden",
|
||||
"in_about_day": "in etwa einem Tag",
|
||||
"in_about_hour": "in etwa einer Stunde",
|
||||
"in_about_minute": "in etwa einer Minute",
|
||||
"in_few_seconds": "in ein paar Sekunden",
|
||||
"in_n_days": "in %(num)s Tagen",
|
||||
"in_n_hours": "in %(num)s Stunden",
|
||||
"in_n_minutes": "In etwa %(num)s Minuten",
|
||||
"n_days_ago": "vor %(num)s Tagen",
|
||||
"n_hours_ago": "vor %(num)s Stunden",
|
||||
"n_minutes_ago": "vor %(num)s Minuten"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Audio-Player",
|
||||
"error_downloading_audio": "Fehler beim Herunterladen der Audiodatei",
|
||||
"unnamed_audio": "Unbenannte Audiodatei"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "Διαγραφή",
|
||||
"dismiss": "Απόρριψη",
|
||||
"explore_rooms": "Εξερευνήστε αίθουσες",
|
||||
"pause": "Παύση",
|
||||
"play": "Αναπαραγωγή",
|
||||
"search": "Αναζήτηση"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Άνοιγμα πληκτρολογίου κλήσης"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "σχεδόν μία μέρα πριν",
|
||||
"about_hour_ago": "σχεδόν μία ώρα πριν",
|
||||
"about_minute_ago": "σχεδόν ένα λεπτό πριν",
|
||||
"few_seconds_ago": "λίγα δευτερόλεπτα πριν",
|
||||
"in_about_day": "περίπου μια μέρα από τώρα",
|
||||
"in_about_hour": "περίπου μία ώρα από τώρα",
|
||||
"in_about_minute": "περίπου ένα λεπτό από τώρα",
|
||||
"in_few_seconds": "λίγα δευτερόλεπτα από τώρα",
|
||||
"in_n_days": "%(num)s μέρες από τώρα",
|
||||
"in_n_hours": "%(num)s ώρες από τώρα",
|
||||
"in_n_minutes": "%(num)s λεπτά από τώρα",
|
||||
"n_days_ago": "%(num)s μέρες πριν",
|
||||
"n_hours_ago": "%(num)s ώρες πριν",
|
||||
"n_minutes_ago": "%(num)s λεπτά πριν"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Σφάλμα λήψης ήχου",
|
||||
"unnamed_audio": "Ήχος χωρίς όνομα"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Audio seek bar"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Delete",
|
||||
"dismiss": "Dismiss",
|
||||
"explore_rooms": "Explore rooms",
|
||||
"pause": "Pause",
|
||||
"play": "Play",
|
||||
"retry": "Retry",
|
||||
"search": "Search"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Open dial pad"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"delete_all": "Delete all",
|
||||
"exceeded_resource_limit_description": "Please contact your service administrator to continue using the service.",
|
||||
"exceeded_resource_limit_title": "Your message wasn't sent because this homeserver has exceeded a resource limit.",
|
||||
"failed_to_create_room_title": "Could not start a chat with this user",
|
||||
"history_visible": "This room has been configured so that new members can read history. <a>Learn More</a>",
|
||||
"homeserver_blocked_title": "Your message wasn't sent because this homeserver has been blocked by its administrator.",
|
||||
"monthly_user_limit_reached_title": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit.",
|
||||
"requires_consent_agreement_title": "You can't send any messages until you review and agree to our terms and conditions.",
|
||||
"retry_all": "Retry all",
|
||||
"select_messages_to_retry": "You can select all or individual messages to retry or delete",
|
||||
"server_connectivity_lost_description": "Sent messages will be stored until your connection has returned.",
|
||||
"server_connectivity_lost_title": "Connectivity to the server has been lost.",
|
||||
"some_messages_not_sent": "Some of your messages have not been sent"
|
||||
}
|
||||
},
|
||||
"terms": {
|
||||
"tac_button": "Review terms and conditions"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "about a day ago",
|
||||
"about_hour_ago": "about an hour ago",
|
||||
"about_minute_ago": "about a minute ago",
|
||||
"few_seconds_ago": "a few seconds ago",
|
||||
"in_about_day": "about a day from now",
|
||||
"in_about_hour": "about an hour from now",
|
||||
"in_about_minute": "about a minute from now",
|
||||
"in_few_seconds": "a few seconds from now",
|
||||
"in_n_days": "%(num)s days from now",
|
||||
"in_n_hours": "%(num)s hours from now",
|
||||
"in_n_minutes": "%(num)s minutes from now",
|
||||
"n_days_ago": "%(num)s days ago",
|
||||
"n_hours_ago": "%(num)s hours ago",
|
||||
"n_minutes_ago": "%(num)s minutes ago"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Audio player",
|
||||
"error_downloading_audio": "Error downloading audio",
|
||||
"unnamed_audio": "Unnamed audio"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "Forigi",
|
||||
"dismiss": "Rezigni",
|
||||
"explore_rooms": "Esplori ĉambrojn",
|
||||
"pause": "Paŭzigi",
|
||||
"play": "Ludi",
|
||||
"search": "Serĉi"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Malfermi ciferplaton"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "antaŭ ĉirkaŭ tago",
|
||||
"about_hour_ago": "antaŭ ĉirkaŭ horo",
|
||||
"about_minute_ago": "antaŭ ĉirkaŭ minuto",
|
||||
"few_seconds_ago": "antaŭ kelkaj sekundoj",
|
||||
"in_about_day": "ĉirkaŭ tagon de nun",
|
||||
"in_about_hour": "ĉirkaŭ horon de nun",
|
||||
"in_about_minute": "ĉirkaŭ minuton de nun",
|
||||
"in_few_seconds": "kelkajn sekundojn de nun",
|
||||
"in_n_days": "%(num)s tagojn de nun",
|
||||
"in_n_hours": "%(num)s horojn de nun",
|
||||
"in_n_minutes": "%(num)s minutojn de nun",
|
||||
"n_days_ago": "antaŭ %(num)s tagoj",
|
||||
"n_hours_ago": "antaŭ %(num)s horoj",
|
||||
"n_minutes_ago": "antaŭ %(num)s minutoj"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Eraris elŝuto de sondosiero",
|
||||
"unnamed_audio": "Sennoma sondosiero"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Barra de búsqueda de audio"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Borrar",
|
||||
"dismiss": "Omitir",
|
||||
"explore_rooms": "Explorar salas",
|
||||
"pause": "Pausar",
|
||||
"play": "Reproducir",
|
||||
"search": "Buscar"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Abrir teclado numérico"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "hace aprox. un día",
|
||||
"about_hour_ago": "hace aprox. una hora",
|
||||
"about_minute_ago": "hace aproximadamente un minuto",
|
||||
"few_seconds_ago": "hace unos segundos",
|
||||
"in_about_day": "dentro de un día",
|
||||
"in_about_hour": "dentro de una hora",
|
||||
"in_about_minute": "dentro de un minuto",
|
||||
"in_few_seconds": "dentro de unos segundos",
|
||||
"in_n_days": "dentro de %(num)s días",
|
||||
"in_n_hours": "dentro de %(num)s horas",
|
||||
"in_n_minutes": "dentro de %(num)s minutos",
|
||||
"n_days_ago": "hace %(num)s días",
|
||||
"n_hours_ago": "hace %(num)s horas",
|
||||
"n_minutes_ago": "hace %(num)s minutos"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Reproductor de audio",
|
||||
"error_downloading_audio": "Error al descargar el audio",
|
||||
"unnamed_audio": "Audio sin título"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Heli kerimisriba"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Kustuta",
|
||||
"dismiss": "Loobu",
|
||||
"explore_rooms": "Tutvu jututubadega",
|
||||
"pause": "Peata",
|
||||
"play": "Esita",
|
||||
"search": "Otsing"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Ava numbriklahvistik"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"history_visible": "See jututuba on seadistatud sel viisil, et uued liikmed saavad lugeda varasemat ajalugu.<a> Lisateave</a>"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "umbes päev tagasi",
|
||||
"about_hour_ago": "umbes tund aega tagasi",
|
||||
"about_minute_ago": "umbes minut tagasi",
|
||||
"few_seconds_ago": "mõni sekund tagasi",
|
||||
"in_about_day": "umbes päeva pärast",
|
||||
"in_about_hour": "umbes tunni pärast",
|
||||
"in_about_minute": "umbes minuti pärast",
|
||||
"in_few_seconds": "mõne sekundi pärast",
|
||||
"in_n_days": "%(num)s päeva pärast",
|
||||
"in_n_hours": "%(num)s tunni pärast",
|
||||
"in_n_minutes": "%(num)s minuti pärast",
|
||||
"n_days_ago": "%(num)s päeva tagasi",
|
||||
"n_hours_ago": "%(num)s tundi tagasi",
|
||||
"n_minutes_ago": "%(num)s minutit tagasi"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Meediaesitaja",
|
||||
"error_downloading_audio": "Helifaili allalaadimine ei õnnestunud",
|
||||
"unnamed_audio": "Nimetu helifail"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "پاککردن",
|
||||
"dismiss": "نادیده بگیر",
|
||||
"explore_rooms": "جستجو در اتاق ها",
|
||||
"pause": "متوقفکردن",
|
||||
"play": "اجرا کردن",
|
||||
"search": "جستجو"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "باز کردن صفحه شمارهگیری"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "حدود یک روز قبل",
|
||||
"about_hour_ago": "حدود یک ساعت قبل",
|
||||
"about_minute_ago": "حدود یک دقیقه قبل",
|
||||
"few_seconds_ago": "چند ثانیه قبل",
|
||||
"in_about_day": "حدود یک روز دیگر",
|
||||
"in_about_hour": "حدود یک ساعت دیگر",
|
||||
"in_about_minute": "حدود یک دقیقه دیگر",
|
||||
"in_few_seconds": "چند ثانیه دیگر",
|
||||
"in_n_days": "%(num)s روز دیگر",
|
||||
"in_n_hours": "%(num)s ساعت دیگر",
|
||||
"in_n_minutes": "%(num)s دقیقه دیگر",
|
||||
"n_days_ago": "%(num)s روز قبل",
|
||||
"n_hours_ago": "%(num)s ساعت قبل",
|
||||
"n_minutes_ago": "%(num)s دقیقه قبل"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Äänen siirtymispalkki"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Poista",
|
||||
"dismiss": "Hylkää",
|
||||
"explore_rooms": "Selaa huoneita",
|
||||
"pause": "Keskeytä",
|
||||
"play": "Toista",
|
||||
"search": "Haku"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Avaa näppäimistö"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "noin päivä sitten",
|
||||
"about_hour_ago": "noin tunti sitten",
|
||||
"about_minute_ago": "noin minuutti sitten",
|
||||
"few_seconds_ago": "muutama sekunti sitten",
|
||||
"in_about_day": "noin päivä sitten",
|
||||
"in_about_hour": "noin tunti sitten",
|
||||
"in_about_minute": "noin minuutti sitten",
|
||||
"in_few_seconds": "muutama sekunti sitten",
|
||||
"in_n_days": "%(num)s päivää sitten",
|
||||
"in_n_hours": "%(num)s tuntia sitten",
|
||||
"in_n_minutes": "%(num)s minuuttia sitten",
|
||||
"n_days_ago": "%(num)s päivää sitten",
|
||||
"n_hours_ago": "%(num)s tuntia sitten",
|
||||
"n_minutes_ago": "%(num)s minuuttia sitten"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Virhe ääntä ladattaessa",
|
||||
"unnamed_audio": "Nimetön ääni"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Barre de recherche audio"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Supprimer",
|
||||
"dismiss": "Ignorer",
|
||||
"explore_rooms": "Parcourir les salons",
|
||||
"pause": "Pause",
|
||||
"play": "Lecture",
|
||||
"retry": "Réessayer",
|
||||
"search": "Rechercher"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Ouvrir le pavé de numérotation"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"delete_all": "Tout supprimer",
|
||||
"exceeded_resource_limit_description": "Veuillez contacter votre administrateur pour continuer à utiliser le service.",
|
||||
"exceeded_resource_limit_title": "Votre message n'a pas pu être envoyé car ce serveur d'accueil a dépassé sa limite de ressources.",
|
||||
"failed_to_create_room_title": "Impossible de démarrer une discussion avec cet utilisateur",
|
||||
"history_visible": "Ce salon a été configuré afin que les nouveaux membres puissent lire l'historique.<a> En savori plus</a>",
|
||||
"homeserver_blocked_title": "Votre message n'a pas pu être envoyé car ce serveur d'accueil a été bloqué par son administrateur.",
|
||||
"monthly_user_limit_reached_title": "Votre message n'a pas pu être envoyé car ce serveur d'accueil a atteint sa limite mensuelle d'utilisateurs actifs.",
|
||||
"requires_consent_agreement_title": "Vous ne pouvez envoyer aucun message tant que vous n'aurez pas consulté et accepté nos conditions générales.",
|
||||
"retry_all": "Tout réessayer",
|
||||
"select_messages_to_retry": "Vous pouvez choisir de renvoyer ou supprimer tous les messages ou seulement certains",
|
||||
"server_connectivity_lost_description": "Les messages envoyés seront stockés jusqu’à ce que votre connexion revienne.",
|
||||
"server_connectivity_lost_title": "La connexion avec le serveur a été perdue.",
|
||||
"some_messages_not_sent": "Certains de vos messages n’ont pas été envoyés"
|
||||
}
|
||||
},
|
||||
"terms": {
|
||||
"tac_button": "Voir les conditions générales"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "il y a environ un jour",
|
||||
"about_hour_ago": "il y a environ une heure",
|
||||
"about_minute_ago": "il y a environ une minute",
|
||||
"few_seconds_ago": "il y a quelques secondes",
|
||||
"in_about_day": "dans un jour environ",
|
||||
"in_about_hour": "dans une heure environ",
|
||||
"in_about_minute": "dans une minute environ",
|
||||
"in_few_seconds": "dans quelques secondes",
|
||||
"in_n_days": "dans %(num)s jours",
|
||||
"in_n_hours": "dans %(num)s heures",
|
||||
"in_n_minutes": "dans %(num)s minutes",
|
||||
"n_days_ago": "il y a %(num)s jours",
|
||||
"n_hours_ago": "il y a %(num)s heures",
|
||||
"n_minutes_ago": "il y a %(num)s minutes"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Lecteur audio",
|
||||
"error_downloading_audio": "Erreur lors du téléchargement de l’audio",
|
||||
"unnamed_audio": "Audio sans nom"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "Eliminar",
|
||||
"dismiss": "Rexeitar",
|
||||
"explore_rooms": "Explorar salas",
|
||||
"pause": "Deter",
|
||||
"play": "Reproducir",
|
||||
"search": "Busca"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Abrir marcador"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "onte",
|
||||
"about_hour_ago": "fai unha hora",
|
||||
"about_minute_ago": "fai un minuto",
|
||||
"few_seconds_ago": "fai uns segundos",
|
||||
"in_about_day": "foi onte",
|
||||
"in_about_hour": "fará unha hora",
|
||||
"in_about_minute": "haberá un minuto",
|
||||
"in_few_seconds": "hai só uns segundos",
|
||||
"in_n_days": "fará %(num)s días",
|
||||
"in_n_hours": "fará %(num)s horas",
|
||||
"in_n_minutes": "fará %(num)s minutos",
|
||||
"n_days_ago": "fai %(num)s días",
|
||||
"n_hours_ago": "fai %(num)s horas",
|
||||
"n_minutes_ago": "fai %(num)s minutos"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Erro ao descargar o audio",
|
||||
"unnamed_audio": "Audio sen nome"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "מחק",
|
||||
"dismiss": "התעלם",
|
||||
"explore_rooms": "גלה חדרים",
|
||||
"search": "חפש"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "פתח לוח חיוג"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "בערך לפני יום",
|
||||
"about_hour_ago": "בערך לפני כשעה",
|
||||
"about_minute_ago": "לפני בערך דקה",
|
||||
"few_seconds_ago": "לפני מספר שניות",
|
||||
"in_about_day": "בערך בעוד יום מעכשיו",
|
||||
"in_about_hour": "בערך בעוד כשעה",
|
||||
"in_about_minute": "בערך עוד דקה אחת",
|
||||
"in_few_seconds": "בעוד מספר שניות מעכשיו",
|
||||
"in_n_days": "בעוד %(num)s ימים מעכשיו",
|
||||
"in_n_hours": "בעוד %(num)s שעות",
|
||||
"in_n_minutes": "בעוד %(num)s דקות",
|
||||
"n_days_ago": "לפני %(num)s ימים",
|
||||
"n_hours_ago": "לפני %(num)s שעות",
|
||||
"n_minutes_ago": "לפני %(num)s דקות"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Hang keresősávja"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Törlés",
|
||||
"dismiss": "Eltüntetés",
|
||||
"explore_rooms": "Szobák felderítése",
|
||||
"pause": "Szünet",
|
||||
"play": "Lejátszás",
|
||||
"search": "Keresés"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Számlap megnyitása"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "egy napja",
|
||||
"about_hour_ago": "egy órája",
|
||||
"about_minute_ago": "egy perce",
|
||||
"few_seconds_ago": "néhány másodperce",
|
||||
"in_about_day": "egy nap múlva",
|
||||
"in_about_hour": "egy óra múlva",
|
||||
"in_about_minute": "egy perc múlva",
|
||||
"in_few_seconds": "másodpercek múlva",
|
||||
"in_n_days": "%(num)s nap múlva",
|
||||
"in_n_hours": "%(num)s óra múlva",
|
||||
"in_n_minutes": "%(num)s perc múlva",
|
||||
"n_days_ago": "%(num)s nappal ezelőtt",
|
||||
"n_hours_ago": "%(num)s órával ezelőtt",
|
||||
"n_minutes_ago": "%(num)s perccel ezelőtt"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Hanglejátszó",
|
||||
"error_downloading_audio": "Hiba a hang letöltésekor",
|
||||
"unnamed_audio": "Névtelen hang"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Աուդիո որոնման գոտի"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Ջնջել",
|
||||
"dismiss": "Հեռացնել",
|
||||
"explore_rooms": "Փնտրել սենյակներ",
|
||||
"pause": "Դադար",
|
||||
"play": "Միացնել",
|
||||
"search": "Որոնել"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Բացեք թվերի հավաքման վահանակը"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "մոտ մեկ օր առաջ",
|
||||
"about_hour_ago": "մոտ մեկ ժամ առաջ",
|
||||
"about_minute_ago": "մոտ մեկ րոպե առաջ",
|
||||
"few_seconds_ago": "մի քանի վայրկյան առաջ",
|
||||
"in_about_day": "մոտ մեկ օր անց",
|
||||
"in_about_hour": "մոտ մեկ ժամ անց",
|
||||
"in_about_minute": "մոտ մեկ րոպե անց",
|
||||
"in_few_seconds": "մի քանի վայրկյան անց",
|
||||
"in_n_days": "%(num)s օր անց",
|
||||
"in_n_hours": "%(num)s ժամ անց",
|
||||
"in_n_minutes": "%(num)s րոպեներ անց",
|
||||
"n_days_ago": "%(num)s օր առաջ",
|
||||
"n_hours_ago": "%(num)s ժամ առաջ",
|
||||
"n_minutes_ago": "%(num)s րոպե առաջ"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Աուդիո նվագարկիչ",
|
||||
"error_downloading_audio": "Աուդիո ներբեռնման սխալ",
|
||||
"unnamed_audio": "Անանուն աուդիո"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Bilah pencarian audio"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Hapus",
|
||||
"dismiss": "Abaikan",
|
||||
"explore_rooms": "Jelajahi ruangan",
|
||||
"pause": "Jeda",
|
||||
"play": "Mainkan",
|
||||
"search": "Cari"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Buka tombol penyetel"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "1 hari yang lalu",
|
||||
"about_hour_ago": "1 jam yang lalu",
|
||||
"about_minute_ago": "1 menit yang lalu",
|
||||
"few_seconds_ago": "beberapa detik yang lalu",
|
||||
"in_about_day": "1 hari dari sekarang",
|
||||
"in_about_hour": "1 jam dari sekarang",
|
||||
"in_about_minute": "1 menit dari sekarang",
|
||||
"in_few_seconds": "beberapa detik dari sekarang",
|
||||
"in_n_days": "%(num)s hari dari sekarang",
|
||||
"in_n_hours": "%(num)s jam dari sekarang",
|
||||
"in_n_minutes": "%(num)s dari sekarang",
|
||||
"n_days_ago": "%(num)s hari yang lalu",
|
||||
"n_hours_ago": "%(num)s jam yang lalu",
|
||||
"n_minutes_ago": "%(num)s menit yang lalu"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Pemutar audio",
|
||||
"error_downloading_audio": "Terjadi kesalahan mengunduh audio",
|
||||
"unnamed_audio": "Audio tidak dinamai"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "Eyða",
|
||||
"dismiss": "Hunsa",
|
||||
"explore_rooms": "Kanna spjallrásir",
|
||||
"pause": "Bið",
|
||||
"play": "Spila",
|
||||
"search": "Leita"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Opna talnaborð"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "fyrir um degi síðan",
|
||||
"about_hour_ago": "fyrir um klukkustund síðan",
|
||||
"about_minute_ago": "fyrir um það bil mínútu síðan",
|
||||
"few_seconds_ago": "fyrir örfáum sekúndum síðan",
|
||||
"in_about_day": "eftir um það bil einn dag",
|
||||
"in_about_hour": "eftir um það bil klukkustund",
|
||||
"in_about_minute": "eftir um það bil mínútu",
|
||||
"in_few_seconds": "eftir nokkrar sekúndur",
|
||||
"in_n_days": "eftir %(num)s daga",
|
||||
"in_n_hours": "eftir %(num)s klukkustundir",
|
||||
"in_n_minutes": "eftir %(num)s mínútur",
|
||||
"n_days_ago": "fyrir %(num)s dögum síðan",
|
||||
"n_hours_ago": "fyrir %(num)s klukkustundum síðan",
|
||||
"n_minutes_ago": "fyrir %(num)s mínútum síðan"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Villa við að sækja hljóð",
|
||||
"unnamed_audio": "Nafnlaust hljóð"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Barra di ricerca audio"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Elimina",
|
||||
"dismiss": "Chiudi",
|
||||
"explore_rooms": "Esplora stanze",
|
||||
"pause": "Pausa",
|
||||
"play": "Riproduci",
|
||||
"search": "Cerca"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Apri tastierino"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "circa un giorno fa",
|
||||
"about_hour_ago": "circa un'ora fa",
|
||||
"about_minute_ago": "circa un minuto fa",
|
||||
"few_seconds_ago": "pochi secondi fa",
|
||||
"in_about_day": "circa un giorno da adesso",
|
||||
"in_about_hour": "circa un'ora da adesso",
|
||||
"in_about_minute": "circa un minuto da adesso",
|
||||
"in_few_seconds": "pochi secondi da adesso",
|
||||
"in_n_days": "%(num)s giorni da adesso",
|
||||
"in_n_hours": "%(num)s ore da adesso",
|
||||
"in_n_minutes": "%(num)s minuti da adesso",
|
||||
"n_days_ago": "%(num)s giorni fa",
|
||||
"n_hours_ago": "%(num)s ore fa",
|
||||
"n_minutes_ago": "%(num)s minuti fa"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Errore di scaricamento dell'audio",
|
||||
"unnamed_audio": "Audio senza nome"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "削除",
|
||||
"dismiss": "閉じる",
|
||||
"explore_rooms": "ルームを探す",
|
||||
"pause": "一時停止",
|
||||
"play": "再生",
|
||||
"search": "検索"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "ダイヤルパッドを開く"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "約1日前",
|
||||
"about_hour_ago": "約1時間前",
|
||||
"about_minute_ago": "約1分前",
|
||||
"few_seconds_ago": "数秒前",
|
||||
"in_about_day": "今から約1日前",
|
||||
"in_about_hour": "今から約1時間前",
|
||||
"in_about_minute": "今から約1分前",
|
||||
"in_few_seconds": "今から数秒前",
|
||||
"in_n_days": "今から%(num)s日前",
|
||||
"in_n_hours": "今から%(num)s時間前",
|
||||
"in_n_minutes": "今から%(num)s分前",
|
||||
"n_days_ago": "%(num)s日前",
|
||||
"n_hours_ago": "%(num)s時間前",
|
||||
"n_minutes_ago": "%(num)s分前"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "音声をダウンロードする際にエラーが発生しました",
|
||||
"unnamed_audio": "名前のない音声"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "წაშლა",
|
||||
"dismiss": "დახურვა",
|
||||
"explore_rooms": "ოთახების დათავლიერება",
|
||||
"pause": "პაუზა",
|
||||
"play": "დაკვრა",
|
||||
"search": "ძიება"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "დაახლოებით ერთი დღის წინ",
|
||||
"about_hour_ago": "დაახლოებით ერთი საათის წინ",
|
||||
"about_minute_ago": "დაახლოებით ერთი წუთის წინ",
|
||||
"few_seconds_ago": "რამდენიმე წამის წინ",
|
||||
"in_about_day": "დაახლოებით ერთი დღის შემდეგ",
|
||||
"in_about_hour": "დაახლოებით ერთი საათის შემდეგ",
|
||||
"in_about_minute": "დაახლოებით ერთი წუთის შემდეგ",
|
||||
"in_few_seconds": "რამდენიმე წამის შემდეგ",
|
||||
"in_n_days": "%(num)sდღეებიდან",
|
||||
"in_n_hours": "%(num)sსაათის შემდეგ",
|
||||
"in_n_minutes": "%(num)sწუთის შემდეგ",
|
||||
"n_days_ago": "%(num)sდღის წინ",
|
||||
"n_hours_ago": "%(num)sსაათის წინ",
|
||||
"n_minutes_ago": "%(num)sწუთის წინ"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "შეცდომა აუდიოს ჩამოტვირთვისას",
|
||||
"unnamed_audio": "უსახელო აუდიო"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "오디오 탐색 바"
|
||||
},
|
||||
"action": {
|
||||
"delete": "삭제",
|
||||
"dismiss": "버리기",
|
||||
"explore_rooms": "방 검색",
|
||||
"pause": "일시중지",
|
||||
"play": "재생",
|
||||
"retry": "재시도",
|
||||
"search": "찾기"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "다이얼 패드 열기"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"delete_all": "전체 삭제",
|
||||
"exceeded_resource_limit_description": "서비스를 계속 이용하시려면 서비스 관리자에게 문의하십시오.",
|
||||
"exceeded_resource_limit_title": "이 홈 서버가 리소스 한도를 초과하여 귀하의 메시지가 전송되지 않았습니다.",
|
||||
"failed_to_create_room_title": "이 사용자와 채팅을 시작할 수 없습니다",
|
||||
"history_visible": "이 방은 새 멤버가 대화 기록을 볼 수 있도록 설정되었습니다. <a>자세히 알아보기</a>",
|
||||
"homeserver_blocked_title": "귀하의 메시지는 이 홈서버 관리자에 의해 차단되었기 때문에 전송되지 않았습니다.",
|
||||
"monthly_user_limit_reached_title": "해당 홈서버의 월간 활성 사용자 수 제한에 도달하여 메시지가 전송되지 않았습니다.",
|
||||
"requires_consent_agreement_title": "이용약관을 검토하고 동의하기 전까지는 메시지를 보낼 수 없습니다.",
|
||||
"retry_all": "전체 재시도",
|
||||
"select_messages_to_retry": "모든 메시지 또는 개별 메시지를 선택하여 재시도하거나 삭제할 수 있습니다.",
|
||||
"server_connectivity_lost_description": "보낸 메시지는 연결이 복구될 때까지 저장됩니다.",
|
||||
"server_connectivity_lost_title": "서버와의 연결이 끊어졌습니다.",
|
||||
"some_messages_not_sent": "일부 메시지가 전송되지 않았습니다."
|
||||
}
|
||||
},
|
||||
"terms": {
|
||||
"tac_button": "이용 약관을 검토하세요."
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "약 1일 전",
|
||||
"about_hour_ago": "약 1 시간 전",
|
||||
"about_minute_ago": "약 1분 전",
|
||||
"few_seconds_ago": "몇 초 전",
|
||||
"in_about_day": "하루 정도 후",
|
||||
"in_about_hour": "지금부터 한 시간 정도 후에",
|
||||
"in_about_minute": "지금부터 약 1분 후",
|
||||
"in_few_seconds": "몇 초 후",
|
||||
"in_n_days": "지금부터 %(num)s 일 후에",
|
||||
"in_n_hours": "지금부터 %(num)s 시간 후",
|
||||
"in_n_minutes": "지금부터 %(num)s분 후",
|
||||
"n_days_ago": "%(num)s일 전",
|
||||
"n_hours_ago": "%(num)s 시간 전",
|
||||
"n_minutes_ago": "%(num)s분 전"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "오디오 플레이어",
|
||||
"error_downloading_audio": "오디오 다운로드 중 오류 발생",
|
||||
"unnamed_audio": "이름 없는 오디오"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "ລຶບ",
|
||||
"dismiss": "ຍົກເລີກ",
|
||||
"explore_rooms": "ການສຳຫຼວດຫ້ອງ",
|
||||
"pause": "ຢຸດຊົ່ວຄາວ",
|
||||
"play": "ຫຼິ້ນ",
|
||||
"search": "ຊອກຫາ"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "ເປີດແຜ່ນປັດ"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "ປະມານຫນຶ່ງມື້ກ່ອນຫນ້ານີ້",
|
||||
"about_hour_ago": "ປະມານຫນຶ່ງຊົ່ວໂມງກ່ອນຫນ້ານີ້",
|
||||
"about_minute_ago": "ປະມານໜຶ່ງວິນາທີກ່ອນຫນ້ານີ້",
|
||||
"few_seconds_ago": "ສອງສາມວິນາທີກ່ອນຫນ້ານີ້",
|
||||
"in_about_day": "ປະມານນຶ່ງມື້ຈາກນີ້",
|
||||
"in_about_hour": "ປະມານຫນຶ່ງຊົ່ວໂມງຈາກປະຈຸບັນນີ້",
|
||||
"in_about_minute": "ປະມານໜຶ່ງນາທີຕໍ່ຈາກນີ້",
|
||||
"in_few_seconds": "ສອງສາມວິນາທີຕໍ່ຈາກນີ້ໄປ",
|
||||
"in_n_days": "%(num)s ມື້ຕໍ່ຈາກນີ້",
|
||||
"in_n_hours": "%(num)s ຊົ່ວໂມງຈາກປະຈຸບັນນີ້",
|
||||
"in_n_minutes": "%(num)s ນາທີຕໍ່ຈາກນີ້",
|
||||
"n_days_ago": "%(num)sມື້ກ່ອນຫນ້ານີ້",
|
||||
"n_hours_ago": "%(num)s ຊົ່ວໂມງກ່ອນ",
|
||||
"n_minutes_ago": "%(num)s ນາທີກ່ອນ"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "ເກີດຄວາມຜິດພາດໃນການດາວໂຫຼດສຽງ",
|
||||
"unnamed_audio": "ສຽງບໍ່ມີຊື່"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "Ištrinti",
|
||||
"dismiss": "Atmesti",
|
||||
"explore_rooms": "Žvalgyti kambarius",
|
||||
"search": "Ieškoti"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "maždaug prieš dieną",
|
||||
"about_hour_ago": "maždaug prieš valandą",
|
||||
"about_minute_ago": "maždaug prieš minutę",
|
||||
"few_seconds_ago": "prieš kelias sekundes",
|
||||
"in_about_day": "apie dieną nuo dabar",
|
||||
"in_about_hour": "apie valandą nuo dabar",
|
||||
"in_about_minute": "apie minutę nuo dabar",
|
||||
"in_few_seconds": "keletą sekundžių nuo dabar",
|
||||
"in_n_days": "%(num)s dienas(-ų) nuo dabar",
|
||||
"in_n_hours": "%(num)s valandas(-ų) nuo dabar",
|
||||
"in_n_minutes": "%(num)s minutes(-ų) nuo dabar",
|
||||
"n_days_ago": "prieš %(num)s dienas(-ų)",
|
||||
"n_hours_ago": "prieš %(num)s valandas(-ų)",
|
||||
"n_minutes_ago": "prieš %(num)s minutes(-ų)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Audio meklēšanas josla"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Izdzēst",
|
||||
"dismiss": "Atmest",
|
||||
"explore_rooms": "Pārlūkot istabas",
|
||||
"pause": "Pauzēt",
|
||||
"play": "Atskaņot",
|
||||
"search": "Meklēt"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "aptuveni dienu iepriekš",
|
||||
"about_hour_ago": "aptuveni stundu iepriekš",
|
||||
"about_minute_ago": "aptuveni minūti iepriekš",
|
||||
"few_seconds_ago": "pirms dažām sekundēm",
|
||||
"in_about_day": "aptuveni dienu kopš šī brīža",
|
||||
"in_about_hour": "aptuveni stundu kopš šī brīža",
|
||||
"in_about_minute": "aptuveni minūti kopš šī brīža",
|
||||
"in_few_seconds": "dažas sekundes kopš šī brīža",
|
||||
"in_n_days": "%(num)s dienas kopš šī brīža",
|
||||
"in_n_hours": "%(num)s stundas kopš šī brīža",
|
||||
"in_n_minutes": "%(num)s minūtes kopš šī brīža",
|
||||
"n_days_ago": "%(num)s dienas iepriekš",
|
||||
"n_hours_ago": "%(num)s stundas iepriekš",
|
||||
"n_minutes_ago": "%(num)s minūtes iepriekš"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Kļūda skaņas lejupielādēšanā",
|
||||
"unnamed_audio": "Nenosaukts audio"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Audio mitady bar"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Esorina",
|
||||
"dismiss": "Hanario",
|
||||
"explore_rooms": "Tsidiho ny efitrano",
|
||||
"pause": "Mihato",
|
||||
"play": "Milalao",
|
||||
"search": "Karohina"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Sokafy ny dial pad"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "Tokony ho iray andro izay",
|
||||
"about_hour_ago": "Manakaiky adin'iray Teo ho eo",
|
||||
"about_minute_ago": "Misy iray minitra Teo izay",
|
||||
"few_seconds_ago": "Segondra vitsy lasa",
|
||||
"in_about_day": "Anatiny iray andro eo ho eo",
|
||||
"in_about_hour": "Adiny iray eo ho eo",
|
||||
"in_about_minute": "Afaka iray minitra eo ho eo",
|
||||
"in_few_seconds": "Afaka segondra vitsy",
|
||||
"in_n_days": "%(num) s andro manomboka izao",
|
||||
"in_n_hours": "% (num) sAnatiny ora vitsivitsy",
|
||||
"in_n_minutes": "% (Num) sAfaka minitra vitsy",
|
||||
"n_days_ago": "%(num)s Andro vitsivitsy izay",
|
||||
"n_hours_ago": "%(num)sOra maromaro",
|
||||
"n_minutes_ago": "%(Num)s Minitra vitsivitsy izay"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Hadisoana tamin'ny fampidinana feo",
|
||||
"unnamed_audio": "Audio tsy voatonona anarana"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Søkelinje for lyd"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Slett",
|
||||
"dismiss": "Avvis",
|
||||
"explore_rooms": "Se alle rom",
|
||||
"play": "Spill av",
|
||||
"search": "Søk"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Åpne nummerpanelet"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "cirka 1 dag siden",
|
||||
"about_hour_ago": "cirka 1 time siden",
|
||||
"about_minute_ago": "cirka 1 minutt siden",
|
||||
"few_seconds_ago": "noen sekunder siden",
|
||||
"in_about_day": "rundt en dag fra nå",
|
||||
"in_about_hour": "rundt en time fra nå",
|
||||
"in_about_minute": "rundt et minutt fra nå",
|
||||
"in_few_seconds": "om noen sekunder fra nå",
|
||||
"in_n_days": "%(num)s dager fra nå",
|
||||
"in_n_hours": "%(num)s timer fra nå",
|
||||
"in_n_minutes": "%(num)s minutter fra nå",
|
||||
"n_days_ago": "%(num)s dager siden",
|
||||
"n_hours_ago": "%(num)s timer siden",
|
||||
"n_minutes_ago": "%(num)s minutter siden"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Lydavspiller",
|
||||
"error_downloading_audio": "Feil ved nedlasting av lyd",
|
||||
"unnamed_audio": "Ikke navngitt lyd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Audio zoekbalk"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Verwijderen",
|
||||
"dismiss": "Sluiten",
|
||||
"explore_rooms": "Kamers ontdekken",
|
||||
"pause": "Pauze",
|
||||
"play": "Afspelen",
|
||||
"search": "Zoeken"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Kiestoetsen openen"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "ongeveer een dag geleden",
|
||||
"about_hour_ago": "ongeveer een uur geleden",
|
||||
"about_minute_ago": "ongeveer een minuut geleden",
|
||||
"few_seconds_ago": "enige tellen geleden",
|
||||
"in_about_day": "over een dag of zo",
|
||||
"in_about_hour": "over ongeveer een uur",
|
||||
"in_about_minute": "over ongeveer een minuut",
|
||||
"in_few_seconds": "over een paar tellen",
|
||||
"in_n_days": "over %(num)s dagen",
|
||||
"in_n_hours": "over %(num)s uur",
|
||||
"in_n_minutes": "over %(num)s minuten",
|
||||
"n_days_ago": "%(num)s dagen geleden",
|
||||
"n_hours_ago": "%(num)s uur geleden",
|
||||
"n_minutes_ago": "%(num)s minuten geleden"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Fout bij downloaden van audio",
|
||||
"unnamed_audio": "Naamloze audio"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Pasek wyszukiwania audio"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Usuń",
|
||||
"dismiss": "Pomiń",
|
||||
"explore_rooms": "Przeglądaj pokoje",
|
||||
"pause": "Wstrzymaj",
|
||||
"play": "Odtwórz",
|
||||
"search": "Szukaj"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Otwórz klawiaturę numeryczną"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "około dzień temu",
|
||||
"about_hour_ago": "około godziny temu",
|
||||
"about_minute_ago": "około minuty temu",
|
||||
"few_seconds_ago": "kilka sekund temu",
|
||||
"in_about_day": "około dnia od teraz",
|
||||
"in_about_hour": "około godziny od teraz",
|
||||
"in_about_minute": "około minuty od teraz",
|
||||
"in_few_seconds": "za kilka sekund",
|
||||
"in_n_days": "za %(num)s dni",
|
||||
"in_n_hours": "za %(num)s godzin",
|
||||
"in_n_minutes": "za %(num)s minut",
|
||||
"n_days_ago": "%(num)s dni temu",
|
||||
"n_hours_ago": "%(num)s godzin temu",
|
||||
"n_minutes_ago": "%(num)s minut temu"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Odtwarzacz audio",
|
||||
"error_downloading_audio": "Wystąpił błąd w trakcie pobierania audio",
|
||||
"unnamed_audio": "Audio bez nazwy"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Barra de procura de áudio"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Apagar",
|
||||
"dismiss": "Descartar",
|
||||
"explore_rooms": "Explorar rooms",
|
||||
"pause": "Pausar",
|
||||
"play": "Reproduzir",
|
||||
"search": "Pesquisar"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Abre o teclado de marcação"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "há cerca de um dia",
|
||||
"about_hour_ago": "há cerca de uma hora",
|
||||
"about_minute_ago": "há cerca de um minuto",
|
||||
"few_seconds_ago": "há alguns segundos atrás",
|
||||
"in_about_day": "daqui a um dia",
|
||||
"in_about_hour": "daqui a uma hora",
|
||||
"in_about_minute": "daqui a um minuto",
|
||||
"in_few_seconds": "daqui a alguns segundos",
|
||||
"in_n_days": "daqui a %(num)s dias",
|
||||
"in_n_hours": "daqui a %(num)s horas",
|
||||
"in_n_minutes": "daqui a %(num)s minutos",
|
||||
"n_days_ago": "%(num)s dias atrás",
|
||||
"n_hours_ago": "%(num)s horas atrás",
|
||||
"n_minutes_ago": "%(num)s minutos atrás"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Erro ao descarregar áudio",
|
||||
"unnamed_audio": "Áudio sem nome"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Barra de busca de áudio"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Excluir",
|
||||
"dismiss": "Dispensar",
|
||||
"explore_rooms": "Explorar salas",
|
||||
"pause": "Pausar",
|
||||
"play": "Reproduzir",
|
||||
"search": "Buscar"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Abrir o teclado de discagem"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "há aproximadamente um dia",
|
||||
"about_hour_ago": "há aproximadamente uma hora",
|
||||
"about_minute_ago": "há aproximadamente um minuto",
|
||||
"few_seconds_ago": "há alguns segundos",
|
||||
"in_about_day": "dentro de aproximadamente um dia",
|
||||
"in_about_hour": "dentro de aproximadamente uma hora",
|
||||
"in_about_minute": "dentro de aproximadamente um minuto",
|
||||
"in_few_seconds": "dentro de alguns segundos",
|
||||
"in_n_days": "dentro de %(num)s dias",
|
||||
"in_n_hours": "dentro de %(num)s horas",
|
||||
"in_n_minutes": "dentro de %(num)s minutos",
|
||||
"n_days_ago": "há %(num)s dias",
|
||||
"n_hours_ago": "há %(num)s horas",
|
||||
"n_minutes_ago": "há %(num)s minutos"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Reprodutor de Áudio",
|
||||
"error_downloading_audio": "Erro ao baixar o áudio",
|
||||
"unnamed_audio": "Áudio sem nome"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Панель поиска аудио"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Удалить",
|
||||
"dismiss": "Закрыть",
|
||||
"explore_rooms": "Обзор комнат",
|
||||
"pause": "Пауза",
|
||||
"play": "Воспроизведение",
|
||||
"search": "Поиск"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Открыть панель набора номера"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"delete_all": "Удалить всё",
|
||||
"exceeded_resource_limit_description": "Свяжись с администратором сервиса, чтобы продолжить пользоваться услугой.",
|
||||
"exceeded_resource_limit_title": "Ваше сообщение не было отправлено, поскольку на этом домашнем сервере превышен лимит ресурсов.",
|
||||
"failed_to_create_room_title": "Не удалось начать чат с этим пользователем.",
|
||||
"history_visible": "Эта комната оборудована таким образом, чтобы новые участники могли ознакомиться с историей. <a>Подробнее</a>",
|
||||
"homeserver_blocked_title": "Ваше сообщение не было отправлено, потому что этот домашний сервер заблокирован его администратором.",
|
||||
"monthly_user_limit_reached_title": "Ваше сообщение не было отправлено, потому что на этом домашнем сервере достигнут лимит ежемесячных активных пользователей.",
|
||||
"requires_consent_agreement_title": "Вы не сможете отправлять сообщения, пока не ознакомитесь с условиями и положениями и не согласитесь с ними.",
|
||||
"retry_all": "Повторить попытку для всех",
|
||||
"select_messages_to_retry": "Вы можете выбрать все или отдельные сообщения для повторной попытки или удаления",
|
||||
"server_connectivity_lost_description": "Отправленные сообщения будут сохранены, пока соединение не восстановится.",
|
||||
"server_connectivity_lost_title": "Соединение с сервером потеряно",
|
||||
"some_messages_not_sent": "Некоторые сообщения не были отправлены"
|
||||
}
|
||||
},
|
||||
"terms": {
|
||||
"tac_button": "Просмотр условий и положений"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "около суток назад",
|
||||
"about_hour_ago": "около часа назад",
|
||||
"about_minute_ago": "около минуты назад",
|
||||
"few_seconds_ago": "несколько секунд назад",
|
||||
"in_about_day": "примерно через день",
|
||||
"in_about_hour": "примерно через час",
|
||||
"in_about_minute": "примерно через минуту",
|
||||
"in_few_seconds": "несколько секунд назад",
|
||||
"in_n_days": "%(num)s дней спустя",
|
||||
"in_n_hours": "%(num)s часов спустя",
|
||||
"in_n_minutes": "%(num)s минут спустя",
|
||||
"n_days_ago": "%(num)s дней назад",
|
||||
"n_hours_ago": "%(num)s часов назад",
|
||||
"n_minutes_ago": "%(num)s минут назад"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Аудиоплеер",
|
||||
"error_downloading_audio": "Ошибка загрузки аудио",
|
||||
"unnamed_audio": "Безымянное аудио"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Panel vyhľadávania zvuku"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Vymazať",
|
||||
"dismiss": "Zamietnuť",
|
||||
"explore_rooms": "Preskúmať miestnosti",
|
||||
"pause": "Pozastaviť",
|
||||
"play": "Prehrať",
|
||||
"search": "Hľadať"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Otvoriť číselník"
|
||||
},
|
||||
"room": {
|
||||
"status_bar": {
|
||||
"history_visible": "Správy, ktoré odošlete, budú zdieľané s novými členmi pozvanými do tejto miestnosti. <a>Zistiť viac</a>"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "asi pred jedným dňom",
|
||||
"about_hour_ago": "približne pred hodinou",
|
||||
"about_minute_ago": "približne pred minútou",
|
||||
"few_seconds_ago": "pred pár sekundami",
|
||||
"in_about_day": "približne o deň",
|
||||
"in_about_hour": "približne o hodinu",
|
||||
"in_about_minute": "približne o minútu",
|
||||
"in_few_seconds": "o pár sekúnd",
|
||||
"in_n_days": "o %(num)s dní",
|
||||
"in_n_hours": "o %(num)s hodín",
|
||||
"in_n_minutes": "o %(num)s minút",
|
||||
"n_days_ago": "pred %(num)s dňami",
|
||||
"n_hours_ago": "pred %(num)s hodinami",
|
||||
"n_minutes_ago": "pred %(num)s minútami"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Prehrávač zvuku",
|
||||
"error_downloading_audio": "Chyba pri sťahovaní zvuku",
|
||||
"unnamed_audio": "Nepomenovaný zvukový záznam"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "Fshije",
|
||||
"dismiss": "Mos e merr parasysh",
|
||||
"explore_rooms": "Eksploroni dhoma",
|
||||
"pause": "Ndalesë",
|
||||
"play": "Luaje",
|
||||
"search": "Kërkoni"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Hap butona numrash"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "rreth një ditë më parë",
|
||||
"about_hour_ago": "rreth një orë më parë",
|
||||
"about_minute_ago": "rreth një minutë më parë",
|
||||
"few_seconds_ago": "pak sekonda më parë",
|
||||
"in_about_day": "rreth një ditë nga tani",
|
||||
"in_about_hour": "rreth një orë nga tani",
|
||||
"in_about_minute": "rreth një minutë nga tani",
|
||||
"in_few_seconds": "pak sekonda nga tani",
|
||||
"in_n_days": "%(num)s ditë nga tani",
|
||||
"in_n_hours": "%(num)s orë nga tani",
|
||||
"in_n_minutes": "%(num)s minuta nga tani",
|
||||
"n_days_ago": "%(num)s ditë më parë",
|
||||
"n_hours_ago": "%(num)s orë më parë",
|
||||
"n_minutes_ago": "%(num)s minuta më parë"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Gabim në shkarkim audioje",
|
||||
"unnamed_audio": "Audio pa emër"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Förloppsfält för ljud"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Radera",
|
||||
"dismiss": "Avvisa",
|
||||
"explore_rooms": "Utforska rum",
|
||||
"pause": "Pausa",
|
||||
"play": "Spela",
|
||||
"search": "Sök"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Öppna knappsats"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "cirka en dag sedan",
|
||||
"about_hour_ago": "cirka en timme sedan",
|
||||
"about_minute_ago": "cirka en minut sedan",
|
||||
"few_seconds_ago": "några sekunder sedan",
|
||||
"in_about_day": "om cirka en dag",
|
||||
"in_about_hour": "om cirka en timme",
|
||||
"in_about_minute": "om cirka en minut",
|
||||
"in_few_seconds": "om några sekunder",
|
||||
"in_n_days": "om %(num)s dagar",
|
||||
"in_n_hours": "om %(num)s timmar",
|
||||
"in_n_minutes": "om %(num)s minuter",
|
||||
"n_days_ago": "%(num)s dagar sedan",
|
||||
"n_hours_ago": "%(num)s timmar sedan",
|
||||
"n_minutes_ago": "%(num)s minuter sedan"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Ljudspelare",
|
||||
"error_downloading_audio": "Fel vid nedladdning av ljud",
|
||||
"unnamed_audio": "Namnlöst ljud"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Ses arama çubuğu"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Sil",
|
||||
"dismiss": "Kapat",
|
||||
"explore_rooms": "Odaları keşfet",
|
||||
"pause": "Durdur",
|
||||
"play": "Oynat",
|
||||
"search": "Ara"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Arama tuşlarını aç"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "yaklaşık bir gün önce",
|
||||
"about_hour_ago": "yaklaşık bir saat önce",
|
||||
"about_minute_ago": "yaklaşık bir dakika önce",
|
||||
"few_seconds_ago": "bir kaç saniye önce",
|
||||
"in_about_day": "şu andan itibaren yaklaşık bir gün",
|
||||
"in_about_hour": "şu andan itibaren yaklaşık bir saat",
|
||||
"in_about_minute": "şu andan itibaren yaklaşık bir dakika",
|
||||
"in_few_seconds": "şu andan itibaren bir kaç saniye",
|
||||
"in_n_days": "şu andan itibaren %(num)s gün",
|
||||
"in_n_hours": "şu andan itibaren %(num)s saat",
|
||||
"in_n_minutes": "şu andan itibaren %(num)s dakika",
|
||||
"n_days_ago": "%(num)s gün önce",
|
||||
"n_hours_ago": "%(num)s saat önce",
|
||||
"n_minutes_ago": "%(num)s dakika önce"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Ses dosyası indirilirken hata oluştu",
|
||||
"unnamed_audio": "İsimsiz ses"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"a11y": {
|
||||
"seek_bar_label": "Панель гортання аудіо"
|
||||
},
|
||||
"action": {
|
||||
"delete": "Видалити",
|
||||
"dismiss": "Відхилити",
|
||||
"explore_rooms": "Каталог кімнат",
|
||||
"pause": "Призупинити",
|
||||
"play": "Відтворити",
|
||||
"search": "Пошук"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Відкрити номеронабирач"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "близько доби тому",
|
||||
"about_hour_ago": "близько години тому",
|
||||
"about_minute_ago": "близько хвилини тому",
|
||||
"few_seconds_ago": "Декілька секунд тому",
|
||||
"in_about_day": "приблизно через день",
|
||||
"in_about_hour": "приблизно через годину",
|
||||
"in_about_minute": "приблизно через хвилинку",
|
||||
"in_few_seconds": "декілька секунд тому",
|
||||
"in_n_days": "%(num)s днів по тому",
|
||||
"in_n_hours": "%(num)s годин по тому",
|
||||
"in_n_minutes": "%(num)s хвилин по тому",
|
||||
"n_days_ago": "%(num)s днів тому",
|
||||
"n_hours_ago": "%(num)s годин тому",
|
||||
"n_minutes_ago": "%(num)s хвилин тому"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"audio_player": "Звуковий програвач",
|
||||
"error_downloading_audio": "Помилка завантаження аудіо",
|
||||
"unnamed_audio": "Аудіо без назви"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "Xoá",
|
||||
"dismiss": "Bỏ qua",
|
||||
"explore_rooms": "Khám phá các phòng",
|
||||
"pause": "Tạm dừng",
|
||||
"play": "Chạy",
|
||||
"search": "Tìm kiếm"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "Mở bàn phím quay số"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "khoảng một ngày trước",
|
||||
"about_hour_ago": "khoảng một giờ trước",
|
||||
"about_minute_ago": "khoảng một phút trước",
|
||||
"few_seconds_ago": "vài giây trước",
|
||||
"in_about_day": "khoảng một ngày kể từ bây giờ",
|
||||
"in_about_hour": "khoảng một giờ kể từ bây giờ",
|
||||
"in_about_minute": "khoảng một phút kể từ bây giờ",
|
||||
"in_few_seconds": "một vài giây kể từ bây giờ",
|
||||
"in_n_days": "%(num)s ngày kể từ bây giờ",
|
||||
"in_n_hours": "%(num)s giờ kể từ bây giờ",
|
||||
"in_n_minutes": "%(num)s phút kể từ bây giờ",
|
||||
"n_days_ago": "%(num)s ngày trước",
|
||||
"n_hours_ago": "%(num)s giờ trước",
|
||||
"n_minutes_ago": "%(num)s phút trước"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "Lỗi khi tải xuống âm thanh",
|
||||
"unnamed_audio": "Âm thanh không tên"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "删除",
|
||||
"dismiss": "忽略",
|
||||
"explore_rooms": "查找房间",
|
||||
"pause": "暂停",
|
||||
"play": "播放",
|
||||
"search": "搜索"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "打开拨号键盘"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "约一天前",
|
||||
"about_hour_ago": "约一小时前",
|
||||
"about_minute_ago": "约一分钟前",
|
||||
"few_seconds_ago": "数秒前",
|
||||
"in_about_day": "从现在开始约一天",
|
||||
"in_about_hour": "从现在开始约一小时",
|
||||
"in_about_minute": "从现在开始约一分钟",
|
||||
"in_few_seconds": "从现在开始数秒",
|
||||
"in_n_days": "从现在开始%(num)s天",
|
||||
"in_n_hours": "从现在开始%(num)s小时",
|
||||
"in_n_minutes": "从现在开始%(num)s分钟",
|
||||
"n_days_ago": "%(num)s天前",
|
||||
"n_hours_ago": "%(num)s小时前",
|
||||
"n_minutes_ago": "%(num)s分钟前"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "下载音频时出错",
|
||||
"unnamed_audio": "未命名的音频"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"action": {
|
||||
"delete": "刪除",
|
||||
"dismiss": "關閉",
|
||||
"explore_rooms": "探索聊天室",
|
||||
"pause": "暫停",
|
||||
"play": "播放",
|
||||
"search": "搜尋"
|
||||
},
|
||||
"left_panel": {
|
||||
"open_dial_pad": "開啟撥號鍵盤"
|
||||
},
|
||||
"time": {
|
||||
"about_day_ago": "大約一天前",
|
||||
"about_hour_ago": "大約一小時前",
|
||||
"about_minute_ago": "大約一分鐘前",
|
||||
"few_seconds_ago": "數秒前",
|
||||
"in_about_day": "從現在開始大約一天",
|
||||
"in_about_hour": "從現在開始大約一小時",
|
||||
"in_about_minute": "從現在開始大約一分鐘",
|
||||
"in_few_seconds": "從現在開始數秒鐘",
|
||||
"in_n_days": "從現在開始 %(num)s 天",
|
||||
"in_n_hours": "從現在開始 %(num)s 小時",
|
||||
"in_n_minutes": "從現在開始 %(num)s 分鐘",
|
||||
"n_days_ago": "%(num)s 天前",
|
||||
"n_hours_ago": "%(num)s 小時前",
|
||||
"n_minutes_ago": "%(num)s 分鐘前"
|
||||
},
|
||||
"timeline": {
|
||||
"m.audio": {
|
||||
"error_downloading_audio": "下載音訊時發生錯誤",
|
||||
"unnamed_audio": "未命名的音訊"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export * from "./event-tiles/TextualEventView";
|
||||
export * from "./message-body/MediaBody";
|
||||
export * from "./pill-input/Pill";
|
||||
export * from "./pill-input/PillInput";
|
||||
export * from "./room/RoomStatusBar";
|
||||
export * from "./rich-list/RichItem";
|
||||
export * from "./rich-list/RichList";
|
||||
export * from "./room-list/RoomListSearchView";
|
||||
@@ -36,7 +37,3 @@ export * from "./utils/I18nApi";
|
||||
export * from "./viewmodel";
|
||||
export * from "./useMockedViewModel";
|
||||
export * from "./useViewModel";
|
||||
|
||||
// i18n (we must export this directly in order to not confuse the type bundler, it seems,
|
||||
// otherwise it will leave it as a relative import rather than bundling it)
|
||||
export type * from "./i18nKeys.d.ts";
|
||||
|
||||
@@ -61,7 +61,7 @@ export function Pill({ className, children, label, onClick, ...props }: PropsWit
|
||||
aria-label={_t("action|delete")}
|
||||
className="mx_Dialog_nonDialogButton"
|
||||
>
|
||||
<CloseIcon color="var(--cpd-color-icon-tertiary)" />
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -18,7 +18,7 @@ exports[`Pill renders the pill 1`] = `
|
||||
<button
|
||||
aria-describedby="_r_0_"
|
||||
aria-label="Delete"
|
||||
class="_icon-button_1pz9o_8 mx_Dialog_nonDialogButton"
|
||||
class="_icon-button_1215g_8 mx_Dialog_nonDialogButton"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 16px;"
|
||||
@@ -29,7 +29,6 @@ exports[`Pill renders the pill 1`] = `
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.container {
|
||||
color: var(--cpd-color-text-primary);
|
||||
svg {
|
||||
/* Ensure button icons are primary too */
|
||||
color: var(--cpd-color-text-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--cpd-color-text-secondary);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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 Meta, type StoryFn } from "@storybook/react-vite";
|
||||
import React, { type JSX } from "react";
|
||||
import { fn } from "storybook/test";
|
||||
|
||||
import { useMockedViewModel } from "../../useMockedViewModel";
|
||||
import {
|
||||
RoomStatusBarState,
|
||||
RoomStatusBarView,
|
||||
type RoomStatusBarViewActions,
|
||||
type RoomStatusBarViewSnapshot,
|
||||
} from "./RoomStatusBarView";
|
||||
|
||||
type RoomStatusBarProps = RoomStatusBarViewSnapshot & RoomStatusBarViewActions;
|
||||
|
||||
const RoomStatusBarViewWrapper = ({
|
||||
onResendAllClick,
|
||||
onDeleteAllClick,
|
||||
onRetryRoomCreationClick,
|
||||
onTermsAndConditionsClicked,
|
||||
...rest
|
||||
}: RoomStatusBarProps): JSX.Element => {
|
||||
const vm = useMockedViewModel(rest, {
|
||||
onResendAllClick,
|
||||
onDeleteAllClick,
|
||||
onRetryRoomCreationClick,
|
||||
onTermsAndConditionsClicked,
|
||||
});
|
||||
return <RoomStatusBarView vm={vm} />;
|
||||
};
|
||||
|
||||
export default {
|
||||
title: "room/RoomStatusBarView",
|
||||
component: RoomStatusBarViewWrapper,
|
||||
tags: ["autodocs"],
|
||||
argTypes: {},
|
||||
args: {
|
||||
onResendAllClick: fn(),
|
||||
onDeleteAllClick: fn(),
|
||||
onRetryRoomCreationClick: fn(),
|
||||
onTermsAndConditionsClicked: fn(),
|
||||
},
|
||||
} as Meta<typeof RoomStatusBarViewWrapper>;
|
||||
|
||||
const Template: StoryFn<typeof RoomStatusBarViewWrapper> = (args) => <RoomStatusBarViewWrapper {...args} />;
|
||||
|
||||
/**
|
||||
* Rendered when the client has lost connection with the server.
|
||||
*/
|
||||
export const WithConnectionLost = Template.bind({});
|
||||
WithConnectionLost.args = {
|
||||
state: RoomStatusBarState.ConnectionLost,
|
||||
};
|
||||
|
||||
/**
|
||||
* Rendered when the client needs the user to consent to some terms and conditions before
|
||||
* they can perform any room actions.
|
||||
*/
|
||||
export const WithConsentLink = Template.bind({});
|
||||
WithConsentLink.args = {
|
||||
state: RoomStatusBarState.NeedsConsent,
|
||||
consentUri: "#example",
|
||||
};
|
||||
|
||||
/**
|
||||
* Rendered when the server has hit a usage limit and is forbidding the user from performing
|
||||
* any actions in the room. There is an optional parameter to link to an admin to contact.
|
||||
*/
|
||||
export const WithResourceLimit = Template.bind({});
|
||||
WithResourceLimit.args = {
|
||||
state: RoomStatusBarState.ResourceLimited,
|
||||
resourceLimit: "hs_disabled",
|
||||
adminContactHref: "#example",
|
||||
};
|
||||
|
||||
/**
|
||||
* Rendered when the client has some unsent messages in the room, stored locally.
|
||||
*/
|
||||
export const WithUnsentMessages = Template.bind({});
|
||||
WithUnsentMessages.args = {
|
||||
state: RoomStatusBarState.UnsentMessages,
|
||||
isResending: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Rendered when the client has some unsent messages in the room, stored locally and is
|
||||
* trying to send them.
|
||||
*/
|
||||
export const WithUnsentMessagesSending = Template.bind({});
|
||||
WithUnsentMessagesSending.args = {
|
||||
state: RoomStatusBarState.UnsentMessages,
|
||||
isResending: true,
|
||||
};
|
||||
/**
|
||||
* Rendered when a local room has failed to be created.
|
||||
*/
|
||||
export const WithLocalRoomRetry = Template.bind({});
|
||||
WithLocalRoomRetry.args = {
|
||||
state: RoomStatusBarState.LocalRoomFailed,
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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 { render } from "jest-matrix-react";
|
||||
import { composeStories } from "@storybook/react-vite";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import * as stories from "./RoomStatusBarView.stories.tsx";
|
||||
|
||||
const { WithConnectionLost, WithConsentLink, WithResourceLimit, WithUnsentMessages, WithLocalRoomRetry } =
|
||||
composeStories(stories);
|
||||
|
||||
describe("RoomStatusBarView", () => {
|
||||
it("renders connection lost", () => {
|
||||
const { container } = render(<WithConnectionLost />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
it("renders resource limit error", () => {
|
||||
const { container } = render(<WithResourceLimit />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
it("renders consent link", () => {
|
||||
const { container, getByRole } = render(<WithConsentLink />);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
const button = getByRole("link");
|
||||
expect(button.getAttribute("href")).toEqual("#example");
|
||||
});
|
||||
it("renders unsent messages", async () => {
|
||||
const { container } = render(
|
||||
<WithUnsentMessages onDeleteAllClick={jest.fn()} onRetryRoomCreationClick={jest.fn()} />,
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
it("renders unsent messages and deletes all", async () => {
|
||||
const onDeleteAllClick = jest.fn();
|
||||
const { container, getByRole } = render(<WithUnsentMessages onDeleteAllClick={onDeleteAllClick} />);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
const button = getByRole("button", { name: "Delete all" });
|
||||
await userEvent.click(button);
|
||||
expect(onDeleteAllClick).toHaveBeenCalled();
|
||||
});
|
||||
it("renders unsent messages and resends all", async () => {
|
||||
const onResendAllClick = jest.fn();
|
||||
const { container, getByRole } = render(<WithUnsentMessages onResendAllClick={onResendAllClick} />);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
const button = getByRole("button", { name: "Retry all" });
|
||||
await userEvent.click(button);
|
||||
expect(onResendAllClick).toHaveBeenCalled();
|
||||
});
|
||||
it("renders local room error", async () => {
|
||||
const onRetryRoomCreationClick = jest.fn();
|
||||
const { container, getByRole } = render(
|
||||
<WithLocalRoomRetry onRetryRoomCreationClick={onRetryRoomCreationClick} />,
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
const button = getByRole("button", { name: "Retry" });
|
||||
await userEvent.click(button);
|
||||
expect(onRetryRoomCreationClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||