Compare commits

..

2 Commits

Author SHA1 Message Date
Florent Chehab c018feb873 (backend) add file upload feature & tests
For the coming features we will need to store files on the meet side.
(for instance user backgrounds).

This commits adds a new Model to manage files, and the associated
serializers & viewsets. All are tested.

This work was heavily inspired by the work done by our friends at
https://github.com/suitenumerique/drive
It build on the same architecture design (upload directly to S3 but
download goes through our proxy), but model is much much simplier
(no folders, no file sharing, etc.).
2026-03-06 11:21:56 +01:00
Florent Chehab 6eaa782095 (backend) add mimetype detection logic from drive
Reused the logic developed by the team working on drive.
This is usefull for our own upload file backend (that will
come in later commits).

Dockefile was updated to add a required system dependency.

We might want to put this shared logic in a lib.
2026-03-06 11:21:56 +01:00
102 changed files with 914 additions and 4583 deletions
+6 -8
View File
@@ -4,7 +4,7 @@ __pycache__
**/__pycache__
**/*.pyc
venv
**/.venv
.venv
# System-specific files
.DS_Store
@@ -24,15 +24,13 @@ data
.cache
.circleci
.git
.vscode
.iml
.idea
db.sqlite3
.mypy_cache
.pylint.d
**/.idea
**/.vscode
**/.pytest_cache
**/.mypy_cache
**/.ruff_cache
.pytest_cache
# Frontend
**/node_modules
node_modules
+12 -14
View File
@@ -124,17 +124,15 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install the project
run: uv sync --locked --all-extras
cache: "pip"
- name: Install development dependencies
run: pip install --user .[dev]
- name: Check code formatting with ruff
run: uv run ruff format . --diff
run: ~/.local/bin/ruff format . --diff
- name: Lint code with ruff
run: uv run ruff check .
run: ~/.local/bin/ruff check .
- name: Lint code with pylint
run: uv run pylint meet demo core
run: ~/.local/bin/pylint meet demo core
lint-agents:
runs-on: ubuntu-latest
@@ -281,10 +279,10 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install the dependencies
run: uv sync --locked --all-extras
cache: "pip"
- name: Install development dependencies
run: pip install --user .[dev]
- name: Install gettext (required to compile messages)
run: |
@@ -292,10 +290,10 @@ jobs:
sudo apt-get install -y gettext
- name: Generate a MO file from strings extracted from the project
run: uv run python manage.py compilemessages
run: python manage.py compilemessages
- name: Run tests
run: uv run pytest -n 2
run: ~/.local/bin/pytest -n 2
lint-front:
runs-on: ubuntu-latest
-1
View File
@@ -31,7 +31,6 @@ MANIFEST
# Translations # Translations
*.pot
*.mo
# Environments
.env
+5 -32
View File
@@ -8,35 +8,14 @@ and this project adheres to
## [Unreleased]
### Added
- ✨(helm) support celery with our Django backend #1124
- ✨(helm) support ingress for custom background image #1124
- ✨(backend) add authenticated user rate throttling on request-entry #1129
- ✨(backend) expose `is_active` field for Application in Django admin #1133
- ✨(file-upload) disable by default & limit count by user #1141
### Changed
- ♿️(frontend) Caption text size setting for accessibility #1062
- ♿️(frontend) sync html lang attribute with i18n for screen readers #1111
- ♿️(frontend) improve MoreLink a11y and UX on home page #1112
- ♿(frontend) improve chat toast a11y for screen readers #1109
- ♿(frontend) improve ui and aria labels for help article links #1108
- 🌐(frontend) improve German translation #1125
- 🔨(python-env) migrate meet main app to UV #1120
- ♻️(backend) align Application model field with `is_active` convention #1133
- 🔐(backend) avoids revealing the inactive status of an application #1135
- ⚡️(helm) reduce initialDelaySeconds and add periods seconds #1139
## [1.10.0] - 2026-03-05
### Fixed
- 🐛(frontend) fix hand icon and queue position alignment and position #1119
- 🩹(backend) add page_size to pagination for room endpoints #1131
- 🐛(backend) refactor lobby throttling to use participant id #1129
- 🩹(backend) ignore non-recording uploads in storage webhook handler #1142
## [1.10.0] - 2026-03-05
- 🩹(frontend) remove incorrect reference to ProConnect on the prejoin #1080
- ✨(frontend) add Ctrl+Shift+/ to open shortcuts settings #1050
- ♿(frontend) announce selected state to screen readers #1081
- 💄(frontend) truncate long names with ellipsis in reaction overlay #1099
### Changed
@@ -44,18 +23,12 @@ and this project adheres to
- 🦺(backend) strengthen API validation for recording options #1063
- ⚡️(frontend) optimize few performance caveats #1073
- 🔒️(helm) introduce a dedicated Kubernetes Ingress for webhook-livekit #1066
- ⬆️(deps) bump rollup from 4.44.2 to 4.59.0 in /src/frontend #1088
### Fixed
- 🐛(migrations) use settings in migrations #1058
- 💄(frontend) truncate pinned participant name with ellipsis on overflow #1056
- ♿(frontend) prevent focus ring clipping on invite dialog #1078
- ♿(frontend) dynamic tab title when connected to meeting #1060
- 🩹(frontend) remove incorrect reference to ProConnect on the prejoin #1080
- ✨(frontend) add Ctrl+Shift+/ to open shortcuts settings #1050
- ♿(frontend) announce selected state to screen readers #1081
- 💄(frontend) truncate long names with ellipsis in reaction overlay #1099
### Added
+23 -39
View File
@@ -13,28 +13,14 @@ RUN apk update && \
# ---- Back-end builder image ----
FROM base AS back-builder
WORKDIR /builder
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
# Copy required python dependencies
COPY ./src/backend /builder
# Disable Python downloads, because we want to use the system interpreter
# across both images. If using a managed Python version, it needs to be
# copied from the build image into the final image;
ENV UV_PYTHON_DOWNLOADS=0
RUN mkdir /install && \
pip install --prefix=/install .
# install uv
COPY --from=ghcr.io/astral-sh/uv:0.10.9 /uv /uvx /bin/
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=src/backend/uv.lock,target=uv.lock \
--mount=type=bind,source=src/backend/pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --no-dev
COPY src/backend /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
# ---- mails ----
FROM node:20 AS mail-builder
@@ -44,7 +30,7 @@ COPY ./src/mail /mail/app
WORKDIR /mail/app
RUN yarn install --frozen-lockfile && \
yarn build
yarn build
# ---- static link collector ----
@@ -56,17 +42,17 @@ RUN apk add \
libmagic \
rdfind
# Copy installed python dependencies
COPY --from=back-builder /install /usr/local
# Copy Meet application (see .dockerignore)
COPY ./src/backend /app/
WORKDIR /app
# Copy the application from the builder
COPY --from=back-builder /app /app
ENV PATH="/app/.venv/bin:$PATH"
# collectstatic
RUN DJANGO_CONFIGURATION=Build DJANGO_JWT_PRIVATE_SIGNING_KEY=Dummy \
python manage.py collectstatic --noinput
python manage.py collectstatic --noinput
# Replace duplicated file by a symlink to decrease the overall size of the
# final image
@@ -95,17 +81,14 @@ COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
# docker user (see entrypoint).
RUN chmod g=u /etc/passwd
# Copy the application from the builder
COPY --from=back-builder /app /app
# Copy installed python dependencies
COPY --from=back-builder /install /usr/local
# Copy Meet application (see .dockerignore)
COPY ./src/backend /app/
WORKDIR /app
ENV PATH="/app/.venv/bin:$PATH"
# Generate compiled translation messages
RUN DJANGO_CONFIGURATION=Build \
python manage.py compilemessages --ignore=".venv/**/*"
# We wrap commands run in this container by the following entrypoint that
# creates a user on-the-fly with the container user ID (see USER) and root group
# ID.
@@ -120,9 +103,10 @@ USER root:root
# Install psql
RUN apk add postgresql-client
# Install development dependencies
RUN --mount=from=ghcr.io/astral-sh/uv:0.10.9,source=/uv,target=/bin/uv \
uv sync --all-extras --locked
# Uninstall Meet and re-install it in editable mode along with development
# dependencies
RUN pip uninstall -y meet
RUN pip install -e .[dev]
# Restore the un-privileged user running the application
ARG DOCKER_USER
@@ -131,7 +115,7 @@ USER ${DOCKER_USER}
# Target database host (e.g. database engine following docker compose services
# name) & port
ENV DB_HOST=postgresql \
DB_PORT=5432
DB_PORT=5432
# Run django development server
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
+4 -4
View File
@@ -223,7 +223,7 @@ superuser: ## Create an admin superuser with password "admin"
.PHONY: superuser
back-i18n-compile: ## compile the gettext files
@$(MANAGE) compilemessages --ignore=".venv/**/*"
@$(MANAGE) compilemessages --ignore="venv/**/*"
.PHONY: back-i18n-compile
back-i18n-generate: ## create the .pot files used for i18n
@@ -354,13 +354,13 @@ install-external-secrets: ## install the kubernetes secrets from Vaultwarden
.PHONY: build-k8s-cluster
start-tilt: ## start the kubernetes cluster using kind
tilt up --namespace=meet -f ./bin/Tiltfile
tilt up -f ./bin/Tiltfile
.PHONY: build-k8s-cluster
start-tilt-keycloak: ## start the kubernetes cluster using kind, without Pro Connect for authentication, use keycloak
DEV_ENV=dev-keycloak tilt up --namespace=meet -f ./bin/Tiltfile
DEV_ENV=dev-keycloak tilt up -f ./bin/Tiltfile
.PHONY: build-k8s-cluster
start-tilt-dinum: ## start the kubernetes cluster using kind, without Pro Connect for authentication, but with DINUM styles
DEV_ENV=dev-dinum tilt up --namespace=meet -f ./bin/Tiltfile
DEV_ENV=dev-dinum tilt up -f ./bin/Tiltfile
.PHONY: build-k8s-cluster
+4 -4
View File
@@ -18,6 +18,7 @@ docker_build(
'localhost:5001/meet-backend:latest',
context='..',
dockerfile='../Dockerfile',
build_args={'DOCKER_USER': '1001:127'},
only=['./src/backend', './src/mail', './docker'],
target = 'backend-production',
live_update=[
@@ -33,6 +34,7 @@ clean_old_images('localhost:5001/meet-backend')
docker_build(
'localhost:5001/meet-frontend-dinum:latest',
context='..',
build_args={'DOCKER_USER': '1001:127'},
dockerfile='../docker/dinum-frontend/Dockerfile',
only=['./src/frontend', './docker', './.dockerignore'],
target = 'frontend-production',
@@ -57,6 +59,7 @@ clean_old_images('localhost:5001/meet-frontend-generic')
docker_build(
'localhost:5001/meet-summary:latest',
context='../src/summary',
build_args={'DOCKER_USER': '1001:127'},
dockerfile='../src/summary/Dockerfile',
only=['.'],
target = 'production',
@@ -69,6 +72,7 @@ clean_old_images('localhost:5001/meet-summary')
docker_build(
'localhost:5001/meet-agents:latest',
context='../src/agents',
build_args={'DOCKER_USER': '1001:127'},
dockerfile='../src/agents/Dockerfile',
only=['.'],
target = 'production',
@@ -99,11 +103,7 @@ k8s_yaml(local('cd ../src/helm && helmfile -n meet -e ${DEV_ENV:-dev} template .
k8s_resource('minio-bucket', resource_deps=['minio'])
k8s_resource('meet-backend', resource_deps=['postgresql', 'minio', 'redis', 'livekit-livekit-server'])
k8s_resource('meet-celery-backend', resource_deps=['redis'])
k8s_resource('meet-celery-summarize', resource_deps=['redis'])
k8s_resource('meet-celery-transcribe', resource_deps=['redis'])
k8s_resource('meet-backend-migrate', resource_deps=['meet-backend'])
k8s_resource('livekit-livekit-server', resource_deps=['redis'])
k8s_resource('livekit-livekit-server-test-connection', resource_deps=['livekit-livekit-server'])
k8s_resource('keycloak', resource_deps=['kc-postgresql'])
k8s_resource('meet-backend-createsuperuser', resource_deps=['meet-backend-migrate'])
+1 -3
View File
@@ -58,7 +58,7 @@ services:
/usr/bin/mc admin config set meet notify_webhook:meet-webhook endpoint='http://app-dev:8000/api/v1.0/recordings/storage-hook/' auth_token='Bearer password' &&
/usr/bin/mc admin service restart meet --wait --json &&
sleep 15 &&
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put --prefix "recordings" &&
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put &&
exit 0;"
app-dev:
@@ -80,7 +80,6 @@ services:
volumes:
- ./src/backend:/app
- ./data/static:/data/static
- /app/.venv
depends_on:
- postgresql
- mailcatcher
@@ -106,7 +105,6 @@ services:
volumes:
- ./src/backend:/app
- ./data/static:/data/static
- /app/.venv
depends_on:
- app-dev
+3 -3
View File
@@ -68,13 +68,13 @@ backend:
python manage.py createsuperuser --email admin@example.com --password admin
restartPolicy: Never
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
# Exra volume to manage our local custom CA and avoid to set ssl_verify: false
extraVolumeMounts:
- name: certs
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
mountPath: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
subPath: cacert.pem
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
# Exra volume to manage our local custom CA and avoid to set ssl_verify: false
extraVolumes:
- name: certs
configMap:
+2 -2
View File
@@ -87,12 +87,12 @@ If you are using an external service, you need to set `REDIS_URL` environment va
Generate a secure key for `LIVEKIT_API_SECRET` in `env.d/common`.
We provide a minimal recommended config for production environment in `livekit-server.yaml`. Set the previously generated API secret key in the config file.
We provide a minimal recommanded config for production environment in `livekit-server.yaml`. Set the previously generated API secret key in the config file.
To view other customization options, see [config-sample.yaml](https://github.com/livekit/livekit/blob/master/config-sample.yaml)
> [!NOTE]
> In this example, we configured multiplexing on a single UDP port. For better performance, you can configure a range of UDP ports.
> In this example, we configured multiplexing on a single UDP port. For better performances, you can configure a range of UDP ports.
### Meet
+7 -7
View File
@@ -122,11 +122,11 @@ If you haven't run the script **bin/start-kind.sh**, you'll need to manually cre
$ kubectl create namespace meet
```
If you have already run the script, you can skip this step and proceed to the next instruction. NOTE: Before you proceed, and is using the kind method, make sure you download this repo examples/helm directory and its contents to the location where you will be executing the helm command. Helm will look for "examples/helm/<name>values.yaml" from based on the path it is being executed.
If you have already run the script, you can skip this step and proceed to the next instruction. NOTE: Before you proceed, and is using the kind method, make sure you download this repo examples/ directory and its contents to the location where you will be executing the helm command. Helm will look for "examples/<name>values.yaml" from based on the path it is being executed.
```
$ kubectl config set-context --current --namespace=meet
$ helm install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak -f examples/helm/keycloak.values.yaml
$ helm install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak -f examples/keycloak.values.yaml
$ #wait until
$ kubectl get po
NAME READY STATUS RESTARTS AGE
@@ -150,7 +150,7 @@ OIDC_RP_SIGN_ALGO: RS256
OIDC_RP_SCOPES: "openid email"
```
You can find these values in **examples/helm/keycloak.values.yaml**
You can find these values in **examples/keycloak.values.yaml**
### Find livekit server connexion values
@@ -159,7 +159,7 @@ LaSuite Meet use livekit for streaming part so if you have a livekit provider, o
Livekit need a redis (and meet too) so we will start by deploying a redis :
```
$ helm install redis oci://registry-1.docker.io/bitnamicharts/redis -f examples/helm/redis.values.yaml
$ helm install redis oci://registry-1.docker.io/bitnamicharts/redis -f examples/redis.values.yaml
$ kubectl get po
NAME READY STATUS RESTARTS AGE
keycloak-0 1/1 Running 0 26m
@@ -172,7 +172,7 @@ When the redis is ready we can deploy livekit-server.
```
$ helm repo add livekit https://helm.livekit.io
$ helm repo update
$ helm install livekit livekit/livekit-server -f examples/helm/livekit.values.yaml
$ helm install livekit livekit/livekit-server -f examples/livekit.values.yaml
$ kubectl get po
NAME READY STATUS RESTARTS AGE
keycloak-0 1/1 Running 0 30m
@@ -199,7 +199,7 @@ CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
LaSuite Meet uses a postgresql db as backend so if you have a provider, obtain the necessary information to use it. If you do not have, you can install a postgresql testing environment as follows:
```
$ helm install postgresql oci://registry-1.docker.io/bitnamicharts/postgresql -f examples/helm/postgresql.values.yaml
$ helm install postgresql oci://registry-1.docker.io/bitnamicharts/postgresql -f examples/postgresql.values.yaml
$ kubectl get po
NAME READY STATUS RESTARTS AGE
keycloak-0 1/1 Running 0 45m
@@ -226,7 +226,7 @@ Now you are ready to deploy LaSuite Meet without AI. AI required more dependenci
```
$ helm repo add meet https://suitenumerique.github.io/meet/
$ helm repo update
$ helm install meet meet/meet -f examples/helm/meet.values.yaml
$ helm install meet meet/meet -f examples/meet.values.yaml
```
## Test your deployment
-1
View File
@@ -28,7 +28,6 @@ AWS_S3_ENDPOINT_URL=http://minio:9000
AWS_S3_ACCESS_KEY_ID=meet
AWS_S3_SECRET_ACCESS_KEY=password
MEDIA_BASE_URL=http://localhost:8083
FILE_UPLOAD_ENABLED=True
# OIDC
OIDC_OP_JWKS_ENDPOINT=http://nginx:8083/realms/meet/protocol/openid-connect/certs
+2 -35
View File
@@ -197,38 +197,6 @@ def resend_notification(modeladmin, request, queryset): # pylint: disable=unuse
)
@admin.action(description=_("Mark selected recordings as 'Failed to Stop'"))
def mark_as_failed_to_stop(modeladmin, request, queryset):
"""Force selected recordings status to failed_to_stop."""
eligible_statuses = [
models.RecordingStatusChoices.ACTIVE,
models.RecordingStatusChoices.INITIATED,
models.RecordingStatusChoices.STOPPED,
]
eligible = queryset.filter(status__in=eligible_statuses)
skipped = queryset.exclude(status__in=eligible_statuses).count()
updated = eligible.update(status=models.RecordingStatusChoices.FAILED_TO_STOP)
if updated > 0:
modeladmin.message_user(
request,
_("%(count)s recording(s) successfully marked as 'Failed to Stop'.")
% {"count": updated},
level=messages.SUCCESS,
)
if skipped > 0:
modeladmin.message_user(
request,
_("Skipped %(count)s recording(s) with an ineligible status.")
% {"count": skipped},
level=messages.WARNING,
)
@admin.register(models.Recording)
class RecordingAdmin(admin.ModelAdmin):
"""Recording admin interface declaration."""
@@ -256,7 +224,7 @@ class RecordingAdmin(admin.ModelAdmin):
"updated_at",
"worker_id",
)
actions = [resend_notification, mark_as_failed_to_stop]
actions = [resend_notification]
def get_queryset(self, request):
"""Optimize queries by prefetching related access and user data to avoid N+1 queries."""
@@ -308,7 +276,7 @@ class ApplicationAdmin(admin.ModelAdmin):
form = ApplicationAdminForm
list_display = ("id", "name", "client_id", "get_scopes_display", "is_active")
list_display = ("id", "name", "client_id", "get_scopes_display")
fields = [
"name",
"id",
@@ -317,7 +285,6 @@ class ApplicationAdmin(admin.ModelAdmin):
"scopes",
"client_id",
"client_secret",
"is_active",
]
readonly_fields = ["id", "created_at", "updated_at"]
inlines = [ApplicationDomainInline]
-15
View File
@@ -43,21 +43,6 @@ def get_frontend_configuration(request):
"expiration_days": settings.RECORDING_EXPIRATION_DAYS,
"max_duration": settings.RECORDING_MAX_DURATION,
},
"background_image": {
"upload_is_enabled": settings.FILE_UPLOAD_ENABLED,
"max_count_by_user": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
"max_count_by_user"
],
"max_size": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
"max_size"
],
"allowed_extensions": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
"allowed_extensions"
],
"allowed_mimetypes": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
"allowed_mimetypes"
],
},
"telephony": {
"enabled": settings.ROOM_TELEPHONY_ENABLED,
"phone_number": settings.ROOM_TELEPHONY_PHONE_NUMBER
-1
View File
@@ -13,7 +13,6 @@ class FeatureFlag:
"recording": "RECORDING_ENABLE",
"storage_event": "RECORDING_STORAGE_EVENT_ENABLE",
"subtitle": "ROOM_SUBTITLE_ENABLED",
"file_upload": "FILE_UPLOAD_ENABLED",
}
@classmethod
-8
View File
@@ -1,6 +1,5 @@
"""Permission handlers for the Meet core app."""
from django.conf import settings
from django.http import Http404
from rest_framework import permissions
@@ -117,13 +116,6 @@ class FilePermission(IsAuthenticated):
Handling soft deletions specificities
"""
def has_permission(self, request, view):
"""Allow access only to authenticated users."""
if not settings.FILE_UPLOAD_ENABLED:
raise Http404
return super().has_permission(request, view)
def has_object_permission(self, request, view, obj):
"""
Return a 404 on deleted files or if the user is not the owner
+1 -50
View File
@@ -1,9 +1,7 @@
"""Throttling modules for the API."""
from django.conf import settings
from lasuite.drf.throttling import MonitoredThrottleMixin
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
from rest_framework.throttling import AnonRateThrottle
from sentry_sdk import capture_message
@@ -16,58 +14,11 @@ class MonitoredAnonRateThrottle(MonitoredThrottleMixin, AnonRateThrottle):
"""Throttle for the monitored scoped rate throttle."""
class MonitoredUserRateThrottle(MonitoredThrottleMixin, UserRateThrottle):
"""Throttle for the monitored scoped rate throttle."""
class RequestEntryAuthenticatedUserRateThrottle(MonitoredUserRateThrottle):
"""Throttle authenticated user requesting room entry"""
scope = "request_entry"
def get_cache_key(self, request, view):
"""Use the authenticated user ID as the throttle cache key."""
if request.user and not request.user.is_authenticated:
return None # Defer to RequestEntryAnonRateThrottle for anonymous users.
return super().get_cache_key(request, view)
class RequestEntryAnonRateThrottle(MonitoredAnonRateThrottle):
"""Throttle Anonymous user requesting room entry"""
scope = "request_entry"
def get_cache_key(self, request, view):
"""Use the lobby participant cookie ID as the throttle cache key.
Only throttle if a cookie is already set. If no cookie exists yet,
return None to skip throttling — the cookie will be set on the first
response, and throttling will apply from the second request onward.
Keying on the cookie rather than the IP address prevents penalising
multiple users behind the same NAT/proxy, and is consistent with how
LobbyService identifies participants.
Note: as per DRF documentation, application-level throttling is not a
security measure against brute-force or DoS attacks. This throttle exists
solely to guard against accidental hammering from buggy clients.
"""
if request.user and request.user.is_authenticated:
return None # Only throttle unauthenticated requests.
participant_id = request.COOKIES.get(settings.LOBBY_COOKIE_NAME)
if participant_id is None:
return None # No throttling for cookieless requests
return self.cache_format % {
"scope": self.scope,
"ident": participant_id,
}
class CreationCallbackAnonRateThrottle(MonitoredAnonRateThrottle):
"""Throttle Anonymous user requesting room generation callback"""
+16 -38
View File
@@ -1,6 +1,7 @@
"""API endpoints"""
# pylint: disable=too-many-lines
import re
import uuid
from logging import getLogger
from urllib.parse import unquote, urlparse
@@ -11,7 +12,6 @@ from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as django_filters
from rest_framework import (
@@ -33,12 +33,10 @@ from rest_framework import (
from core import enums, models, utils
from core.api.filters import ListFileFilter
from core.enums import MEDIA_STORAGE_URL_PATTERN
from core.recording.enums import FileExtension
from core.recording.event.authentication import StorageEventAuthentication
from core.recording.event.exceptions import (
InvalidBucketError,
InvalidFilepathError,
InvalidFileTypeError,
ParsingEventDataError,
)
@@ -80,6 +78,17 @@ from .feature_flag import FeatureFlag
logger = getLogger(__name__)
FILE_FOLDER = settings.FILE_UPLOAD_PATH
UUID_REGEX = (
r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
)
FILE_EXT_REGEX = r"[\d\w]+"
MEDIA_STORAGE_URL_PATTERN = re.compile(
f"{settings.MEDIA_URL:s}"
rf"(?P<key>{FILE_FOLDER:s}/(?P<pk>{UUID_REGEX:s})/\.{FILE_EXT_REGEX:s})$"
)
class NestedGenericViewSet(viewsets.GenericViewSet):
"""
A generic Viewset aims to be used in a nested route context.
@@ -215,7 +224,6 @@ class RoomViewSet(
API endpoints to access and perform actions on rooms.
"""
pagination_class = Pagination
permission_classes = [permissions.RoomPermissions]
queryset = models.Room.objects.all()
serializer_class = serializers.RoomSerializer
@@ -384,10 +392,7 @@ class RoomViewSet(
methods=["post"],
url_path="request-entry",
permission_classes=[],
throttle_classes=[
throttling.RequestEntryAuthenticatedUserRateThrottle,
throttling.RequestEntryAnonRateThrottle,
],
throttle_classes=[throttling.RequestEntryAnonRateThrottle],
)
def request_entry(self, request, pk=None): # pylint: disable=unused-argument
"""Request entry to a room"""
@@ -762,14 +767,9 @@ class RecordingViewSet(
except InvalidBucketError as e:
raise drf_exceptions.PermissionDenied("Invalid bucket specified") from e
except InvalidFilepathError:
except InvalidFileTypeError as e:
return drf_response.Response(
{"message": "Notification ignored."},
)
except InvalidFileTypeError:
return drf_response.Response(
{"message": "Notification ignored."},
{"message": f"Ignore this file type, {e}"},
)
try:
@@ -944,7 +944,7 @@ class FileViewSet(
filterset_class = ListFileFilter
def get_queryset(self):
"""Get queryset that defaults to the current request user."""
"""Get queryset that defaults to the the current request user."""
user = self.request.user
queryset = super().get_queryset().select_related("creator")
@@ -969,26 +969,6 @@ class FileViewSet(
def perform_create(self, serializer):
"""Set the current user as creator of the newly created file."""
if settings.FILE_UPLOAD_APPLY_RESTRICTIONS:
file_type = serializer.validated_data["type"]
config_for_file_type = settings.FILE_UPLOAD_RESTRICTIONS[file_type]
count = models.File.objects.filter(
creator=self.request.user,
deleted_at__isnull=True,
type=file_type,
).count()
if count >= config_for_file_type["max_count_by_user"]:
logger.info(
"create_item: user reached max files per user for type %s",
file_type,
)
raise serializers.PermissionDenied(
_("You have reached the maximum number of files for this type.")
)
serializer.save(creator=self.request.user)
def perform_destroy(self, instance):
@@ -996,7 +976,6 @@ class FileViewSet(
instance.soft_delete()
@decorators.action(detail=True, methods=["post"], url_path="upload-ended")
@FeatureFlag.require("file_upload")
def upload_ended(self, request, *args, **kwargs):
"""
Check the actual uploaded file and mark it as ready.
@@ -1179,7 +1158,6 @@ class FileViewSet(
return url_params, request.user.id, file
@decorators.action(detail=False, methods=["get"], url_path="media-auth")
@FeatureFlag.require("file_upload")
def media_auth(self, request, *args, **kwargs):
"""
This view is used by an Nginx subrequest to control access to an file's
+1 -7
View File
@@ -14,15 +14,9 @@ FILE_EXT_REGEX = r"[a-zA-Z0-9]{1,10}"
# pylint: disable=line-too-long
RECORDING_STORAGE_URL_PATTERN = re.compile(
f"{settings.MEDIA_URL:s}{settings.RECORDING_OUTPUT_FOLDER}/(?P<recording_id>{UUID_REGEX:s})\.(?P<extension>{FILE_EXT_REGEX:s})"
f"/media/{settings.RECORDING_OUTPUT_FOLDER}/(?P<recording_id>{UUID_REGEX:s}).(?P<extension>{FILE_EXT_REGEX:s})"
)
MEDIA_STORAGE_URL_PATTERN = re.compile(
f"{settings.MEDIA_URL:s}"
rf"(?P<key>{settings.FILE_UPLOAD_PATH:s}/(?P<pk>{UUID_REGEX:s})\.{FILE_EXT_REGEX:s})$"
)
# Django sets `LANGUAGES` by default with all supported languages. We can use it for
# the choice of languages which should not be limited to the few languages active in
# the app.
@@ -203,7 +203,7 @@ class ApplicationJWTAuthentication(BaseJWTAuthentication):
logger.warning("Application not found: %s", client_id)
raise exceptions.AuthenticationFailed("Application not found.") from e
if not application.is_active:
if not application.active:
logger.warning(
"Inactive application attempted authentication: %s", client_id
)
+3 -3
View File
@@ -61,12 +61,12 @@ class ApplicationViewSet(viewsets.ViewSet):
except models.Application.DoesNotExist as e:
raise drf_exceptions.AuthenticationFailed("Invalid credentials") from e
if not application.active:
raise drf_exceptions.AuthenticationFailed("Application is inactive")
if not check_password(client_secret, application.client_secret):
raise drf_exceptions.AuthenticationFailed("Invalid credentials")
if not application.is_active:
raise drf_exceptions.AuthenticationFailed("Application is inactive")
email = serializer.validated_data["scope"]
try:
validate_email(email)
+1 -1
View File
@@ -129,7 +129,7 @@ class ApplicationFactory(factory.django.DjangoModelFactory):
model = models.Application
name = factory.Faker("company")
is_active = True
active = True
client_id = factory.LazyFunction(utils.generate_client_id)
client_secret = factory.LazyFunction(utils.generate_client_secret)
scopes = []
@@ -1,18 +0,0 @@
# Generated by Django 5.2.12 on 2026-03-11 14:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0017_file'),
]
operations = [
migrations.RenameField(
model_name='application',
old_name='active',
new_name='is_active',
),
]
+2 -2
View File
@@ -759,7 +759,7 @@ class Application(BaseModel):
verbose_name=_("Application name"),
help_text=_("Descriptive name for this application."),
)
is_active = models.BooleanField(default=True)
active = models.BooleanField(default=True)
client_id = models.CharField(
max_length=100, unique=True, default=utils.generate_client_id
)
@@ -952,7 +952,7 @@ class File(BaseModel):
_, extension = splitext(self.filename)
# We store only the extension in the storage system to avoid
# leaking Personal Information in logs, etc.
return f"{self.key_base}{extension!s}"
return f"{self.key_base}/{extension!s}"
def get_abilities(self, user):
"""
+1 -3
View File
@@ -9,8 +9,6 @@ from typing import Any, Dict, Optional, Protocol
from django.conf import settings
from django.utils.module_loading import import_string
from core.enums import FILE_EXT_REGEX, UUID_REGEX
from .exceptions import (
InvalidBucketError,
InvalidFilepathError,
@@ -88,7 +86,7 @@ class MinioParser:
# pylint: disable=line-too-long
self._filepath_regex = re.compile(
rf"(?P<url_encoded_folder_path>(?:[^%]+%2F)+)?{settings.RECORDING_OUTPUT_FOLDER}%2F(?P<recording_id>{UUID_REGEX})\.(?P<extension>{FILE_EXT_REGEX})"
r"(?P<url_encoded_folder_path>(?:[^%]+%2F)+)?(?P<recording_id>[0-9a-fA-F\-]{36})\.(?P<extension>[a-zA-Z0-9]+)"
)
@staticmethod
@@ -16,7 +16,7 @@ class RecordingEventsError(Exception):
class RecordingEventsService:
"""Handles recording-related LiveKit webhook events."""
"""Handles recording-related Livekit webhook events."""
@staticmethod
def handle_update(recording, egress_status):
+1 -1
View File
@@ -129,7 +129,7 @@ class LobbyService:
) -> Tuple[LobbyParticipant, Optional[Dict]]:
"""Request entry to a room for a participant.
The usual status transitions are:
This usual status transitions is:
UNKNOWN -> WAITING -> (ACCEPTED | DENIED)
Flow:
+1 -1
View File
@@ -1,4 +1,4 @@
"""Custom template tags for the core application of Meet."""
"""Custom template tags for the core application of People."""
import base64
@@ -118,7 +118,7 @@ def test_api_files_create_file_authenticated_success():
assert policy_parsed.scheme == "http"
assert policy_parsed.netloc == "localhost:9000"
assert policy_parsed.path == f"/meet-media-storage/files/{file.id!s}.png"
assert policy_parsed.path == f"/meet-media-storage/files/{file.id!s}/.png"
query_params = parse_qs(policy_parsed.query)
@@ -174,26 +174,6 @@ def test_api_files_create_file_authenticated_extension_case_insensitive():
assert file.title == "file"
def test_api_files_create_file_disabled(settings):
"""
Creating a file is denied if file upload is disabled
"""
settings.FILE_UPLOAD_ENABLED = False
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/files/",
{
"type": FileTypeChoices.BACKGROUND_IMAGE,
"filename": "file.JPG",
},
format="json",
)
assert response.status_code == 404
assert not File.objects.exists()
def test_api_files_create_file_authenticated_not_checking_extension(settings):
"""
Creating a file with an extension not allowed should not fail when restrictions are disabled.
@@ -259,48 +239,6 @@ def test_api_files_create_file_authenticated_hidden_file_but_checking_extension_
assert response.json() == {"filename": ["This file extension is not allowed."]}
def test_api_files_create_file_too_many(
settings,
):
"""
Creating a file is forbidden if above user limit.
"""
settings.FILE_UPLOAD_APPLY_RESTRICTIONS = True
settings.FILE_UPLOAD_RESTRICTIONS = {
"background_image": {
**settings.FILE_UPLOAD_RESTRICTIONS["background_image"],
"max_count_by_user": 1,
},
}
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/files/",
{
"type": FileTypeChoices.BACKGROUND_IMAGE,
"filename": "1.png",
},
)
assert response.status_code == 201
response = client.post(
"/api/v1.0/files/",
{
"type": FileTypeChoices.BACKGROUND_IMAGE,
"filename": "2.png",
},
)
assert response.status_code == 403
assert response.json() == {
"detail": "You have reached the maximum number of files for this type."
}
assert File.objects.count() == 1
def test_api_files_create_force_id_success():
"""It should be possible to force the item ID when creating a item."""
user = factories.UserFactory()
@@ -39,7 +39,7 @@ def test_api_files_update_anonymous_forbidden():
def test_api_files_update_description_and_title():
"""
Test the description and title of a file can be updated.
Test the description and title of an file can be updated.
"""
user = factories.UserFactory()
@@ -32,7 +32,7 @@ def valid_minio_event():
"s3": {
"bucket": {"name": "test-bucket"},
"object": {
"key": "recordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
"key": "recording%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
"contentType": "audio/ogg",
},
}
@@ -51,7 +51,7 @@ def test_parse_valid_event(minio_parser, valid_minio_event):
"""Test parsing a valid Minio event."""
event = minio_parser.parse(valid_minio_event)
assert isinstance(event, StorageEvent)
assert event.filepath == "recordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg"
assert event.filepath == "recording%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg"
assert event.filetype == "audio/ogg"
assert event.bucket_name == "test-bucket"
assert event.metadata is None
@@ -130,13 +130,11 @@ def test_validate_invalid_filetype(minio_parser):
"invalid_filepath",
[
"invalid_filepath", # totally invalid string
"recordings/46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
"recordings/46d1a121-2426-484d-8fb3-09b5d886f7a8", # missing extension
"recording/46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
"recording/46d1a121-2426-484d-8fb3-09b5d886f7a8", # missing extension
"46d1a121-2426-484d-8fb3-09b5d886f7a8", # missing url_encoded_folder_path and extension
"", # empty string
"46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg", # no folder at all
"uploads%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg", # wrong folder name
"folder%2Fuploads%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg", # nested but no recordings/
"recording%2F46d1a1212426484d8fb309b5d886f7a8.ogg",
],
)
def test_validate_invalid_filepath(invalid_filepath, minio_parser):
@@ -154,7 +152,7 @@ def test_validate_invalid_filepath(invalid_filepath, minio_parser):
def test_validate_valid_event(minio_parser):
"""Test validation with valid event data."""
event = StorageEvent(
filepath="recordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
filepath="recording%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
filetype="audio/ogg",
bucket_name="test-bucket",
metadata=None,
@@ -172,7 +170,7 @@ def test_get_recording_id_success(minio_parser, valid_minio_event):
def test_validate_filepath_with_folder(minio_parser):
"""Test validation of filepath with folder structure."""
event = StorageEvent(
filepath="parent_folder%2Frecordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
filepath="parent_folder%2Ffolder%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
filetype="audio/ogg",
bucket_name="test-bucket",
metadata=None,
@@ -221,7 +219,7 @@ def test_validate_custom_filetypes():
parser = MinioParser(bucket_name="test-bucket", allowed_filetypes={"audio/mp3"})
event = StorageEvent(
filepath="parent_folder%2Frecordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
filepath="parent_folder%2Ffolder%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
filetype="audio/mp3",
bucket_name="test-bucket",
metadata=None,
@@ -14,7 +14,6 @@ from ...factories import RecordingFactory
from ...models import Recording, RecordingStatusChoices
from ...recording.event.exceptions import (
InvalidBucketError,
InvalidFilepathError,
InvalidFileTypeError,
ParsingEventDataError,
)
@@ -133,28 +132,7 @@ def test_save_recording_filetype_error(recording_settings, mock_get_parser):
)
assert response.status_code == 200
assert response.json() == {"message": "Notification ignored."}
def test_save_recording_filepath_error(recording_settings, mock_get_parser):
"""Test handling of unsupported filepath in recording event data."""
mock_parser = mock.Mock()
mock_parser.get_recording_id.side_effect = InvalidFilepathError(
"Invalid filepath structure: parent/folder/recording.jpeg"
)
mock_get_parser.return_value = mock_parser
client = APIClient()
response = client.post(
"/api/v1.0/recordings/storage-hook/",
{"recording_data": "valid-data"},
HTTP_AUTHORIZATION="Bearer testAuthToken",
)
assert response.status_code == 200
assert response.json() == {"message": "Notification ignored."}
assert response.json() == {"message": "Ignore this file type, unsupported '.json'"}
def test_save_recording_unknown_recording(recording_settings, mock_get_parser, client):
@@ -120,39 +120,3 @@ def test_api_rooms_list_authenticated_distinct():
content = response.json()
assert len(content["results"]) == 1
assert content["results"][0]["id"] == str(room.id)
def test_api_rooms_list_pagination_page_size():
"""Users should be able to customize the number of results per page via page_size param."""
user = UserFactory()
client = APIClient()
client.force_login(user)
RoomFactory.create_batch(11, users=[user])
response = client.get("/api/v1.0/rooms/?page_size=2")
assert response.status_code == 200
content = response.json()
assert content["count"] == 11
assert len(content["results"]) == 2
assert content["next"] == "http://testserver/api/v1.0/rooms/?page=2&page_size=2"
assert content["previous"] is None
response = client.get("/api/v1.0/rooms/?page=6&page_size=2")
assert response.status_code == 200
content = response.json()
assert content["count"] == 11
assert len(content["results"]) == 1
assert content["next"] is None
assert content["previous"] == "http://testserver/api/v1.0/rooms/?page=5&page_size=2"
response = client.get("/api/v1.0/rooms/?page_size=3")
assert response.status_code == 200
content = response.json()
assert content["count"] == 11
assert len(content["results"]) == 3
assert content["next"] == "http://testserver/api/v1.0/rooms/?page=2&page_size=3"
assert content["previous"] is None
@@ -631,109 +631,3 @@ def test_list_waiting_participants_empty(settings):
assert response.status_code == 200
assert response.json() == {"participants": []}
@mock.patch.object(utils, "notify_participants", return_value=None)
@mock.patch.object(
utils, "generate_livekit_config", return_value={"token": "test-token"}
)
def test_request_entry_throttling_anonymous_without_cookie(
mock_notify_participants, mock_generate_livekit_config, settings
):
"""Anonymous users without a cookie should not be throttled."""
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
client = APIClient()
settings.LOBBY_COOKIE_NAME = "mocked-cookie"
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["request_entry"] = "1/minute"
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 200
assert response.cookies.get("mocked-cookie") is not None
client.cookies.clear() # Simulate a new cookieless request
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 200
@mock.patch.object(utils, "notify_participants", return_value=None)
@mock.patch.object(
utils, "generate_livekit_config", return_value={"token": "test-token"}
)
def test_request_entry_throttling_anonymous_with_cookie(
mock_notify_participants, mock_generate_livekit_config, settings
):
"""Anonymous users with a cookie should be throttled after exceeding the rate limit."""
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
client = APIClient()
settings.LOBBY_COOKIE_NAME = "mocked-cookie"
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["request_entry"] = "2/minute"
participant_id = str(uuid.uuid4())
client.cookies.load({"mocked-cookie": participant_id})
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 200
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 200
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 429
@mock.patch.object(utils, "notify_participants", return_value=None)
@mock.patch.object(
utils, "generate_livekit_config", return_value={"token": "test-token"}
)
def test_request_entry_throttling_authenticated_user(
mock_notify_participants, mock_generate_livekit_config, settings
):
"""Authenticated users should be throttled."""
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
user = UserFactory()
client = APIClient()
client.force_login(user)
settings.LOBBY_COOKIE_NAME = "mocked-cookie"
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["request_entry"] = "2/minute"
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 200
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 200
response = client.post(
f"/api/v1.0/rooms/{room.id}/request-entry/",
{"username": "test_user"},
)
assert response.status_code == 429
@@ -904,7 +904,7 @@ def test_api_rooms_token_unknown_application(settings):
def test_api_rooms_token_inactive_application(settings):
"""Token for inactive application should be rejected."""
application = ApplicationFactory(is_active=False)
application = ApplicationFactory(active=False)
now = datetime.now(timezone.utc)
payload = {
@@ -23,7 +23,7 @@ def test_api_applications_generate_token_success(settings):
"""Valid credentials should return a JWT token."""
UserFactory(email="User.Family@example.com")
application = ApplicationFactory(
is_active=True,
active=True,
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
)
@@ -79,7 +79,7 @@ def test_api_applications_generate_token_invalid_client_id():
def test_api_applications_generate_token_invalid_client_secret():
"""Invalid client_secret should return 401."""
user = UserFactory(email="user@example.com")
application = ApplicationFactory(is_active=True)
application = ApplicationFactory(active=True)
client = APIClient()
response = client.post(
@@ -100,7 +100,7 @@ def test_api_applications_generate_token_invalid_client_secret():
def test_api_applications_generate_token_inactive_application():
"""Inactive application should return 401."""
user = UserFactory(email="user@example.com")
application = ApplicationFactory(is_active=False)
application = ApplicationFactory(active=False)
plain_secret = "test-secret-123"
application.client_secret = plain_secret
@@ -122,31 +122,9 @@ def test_api_applications_generate_token_inactive_application():
assert "Application is inactive" in str(response.data)
def test_api_applications_generate_token_inactive_application_wrong_secret():
"""An inactive application with a wrong secret should return 401."""
user = UserFactory(email="user@example.com")
application = ApplicationFactory(is_active=False)
client = APIClient()
response = client.post(
"/external-api/v1.0/application/token/",
{
"client_id": application.client_id,
"client_secret": "wrong-secret",
"grant_type": "client_credentials",
"scope": user.email,
},
format="json",
)
assert response.status_code == 401
assert "Invalid credentials" in str(response.data)
assert "inactive" not in str(response.data).lower()
def test_api_applications_generate_token_invalid_email_format():
"""Invalid email format should return 400."""
application = ApplicationFactory(is_active=True)
application = ApplicationFactory(active=True)
plain_secret = "test-secret-123"
application.client_secret = plain_secret
@@ -171,7 +149,7 @@ def test_api_applications_generate_token_invalid_email_format():
def test_api_applications_generate_token_domain_not_authorized():
"""Application without domain authorization should return 403."""
user = UserFactory(email="user@denied.com")
application = ApplicationFactory(is_active=True)
application = ApplicationFactory(active=True)
ApplicationDomainFactory(application=application, domain="allowed.com")
plain_secret = "test-secret-123"
@@ -198,7 +176,7 @@ def test_api_applications_generate_token_domain_authorized():
"""Application with domain authorization should succeed."""
user = UserFactory(email="user@allowed.com")
application = ApplicationFactory(
is_active=True,
active=True,
scopes=[ApplicationScope.ROOMS_LIST],
)
ApplicationDomainFactory(application=application, domain="allowed.com")
@@ -225,7 +203,7 @@ def test_api_applications_generate_token_domain_authorized():
def test_api_applications_generate_token_user_not_found():
"""Non-existent user should return 404."""
application = ApplicationFactory(is_active=True)
application = ApplicationFactory(active=True)
plain_secret = "test-secret-123"
application.client_secret = plain_secret
@@ -253,7 +231,7 @@ def test_api_applications_token_payload_structure(settings):
user = UserFactory(email="user@example.com")
application = ApplicationFactory(
is_active=True,
active=True,
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
)
@@ -306,7 +284,7 @@ def test_api_applications_token_new_user(settings):
assert len(User.objects.all()) == 0
application = ApplicationFactory(
is_active=True,
active=True,
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
)
@@ -364,7 +342,7 @@ def test_api_applications_token_existing_user(settings):
assert len(User.objects.all()) == 1
application = ApplicationFactory(
is_active=True,
active=True,
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
)
@@ -41,7 +41,7 @@ def test_models_application_name_maxlength():
def test_models_application_active_default():
"""An application should be active by default."""
application = Application.objects.create(name="Test App")
assert application.is_active is True
assert application.active is True
def test_models_application_scopes_default():
Binary file not shown.
+96 -115
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
"POT-Creation-Date: 2026-02-26 17:34+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,11 +29,11 @@ msgstr "Berechtigungen"
msgid "Important dates"
msgstr "Wichtige Daten"
#: core/admin.py:132 core/admin.py:275
#: core/admin.py:132 core/admin.py:243
msgid "No owner"
msgstr "Kein Eigentümer"
#: core/admin.py:135 core/admin.py:278
#: core/admin.py:135 core/admin.py:246
msgid "Multiple owners"
msgstr "Mehrere Eigentümer"
@@ -61,136 +61,117 @@ msgstr "Benachrichtigungen für %(count)s Aufnahme(n) erfolgreich gesendet."
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen."
#: core/admin.py:200
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Ausgewählte Aufnahmen als Fehler beim Stoppen markieren"
#: core/admin.py:218
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr "%(count)s Aufnahme(n) erfolgreich als Fehler beim Stoppen markiert."
#: core/admin.py:226
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen."
#: core/admin.py:342
#: core/admin.py:309
msgid "No scopes"
msgstr "Keine Scopes"
#: core/admin.py:344
#: core/admin.py:311
msgid "Scopes"
msgstr "Scopes"
#: core/api/filters.py:25
#: core/api/filters.py:24
msgid "Creator is me"
msgstr "Ersteller bin ich"
#: core/api/serializers.py:88
#: core/api/serializers.py:84
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Sie müssen Administrator oder Eigentümer eines Raums sein, um Zugriffe "
"hinzuzufügen."
#: core/api/serializers.py:516
#: core/api/serializers.py:443
msgid "This file extension is not allowed."
msgstr "Diese Dateiendung ist nicht erlaubt."
#: core/api/serializers.py:533
msgid "You have reached the maximum number of files for this type."
msgstr "Sie haben die maximale Anzahl an Dateien dieses Typs erreicht."
#: core/models.py:37
#: core/models.py:35
msgid "Member"
msgstr "Mitglied"
#: core/models.py:38
#: core/models.py:36
msgid "Administrator"
msgstr "Administrator"
#: core/models.py:39
#: core/models.py:37
msgid "Owner"
msgstr "Eigentümer"
#: core/models.py:55
#: core/models.py:53
msgid "Initiated"
msgstr "Gestartet"
#: core/models.py:56
#: core/models.py:54
msgid "Active"
msgstr "Aktiv"
#: core/models.py:57
#: core/models.py:55
msgid "Stopped"
msgstr "Beendet"
#: core/models.py:58
#: core/models.py:56
msgid "Saved"
msgstr "Gespeichert"
#: core/models.py:59
#: core/models.py:57
msgid "Aborted"
msgstr "Abgebrochen"
#: core/models.py:60
#: core/models.py:58
msgid "Failed to Start"
msgstr "Start fehlgeschlagen"
#: core/models.py:61
#: core/models.py:59
msgid "Failed to Stop"
msgstr "Stopp fehlgeschlagen"
#: core/models.py:62
#: core/models.py:60
msgid "Notification succeeded"
msgstr "Benachrichtigung erfolgreich"
#: core/models.py:89
#: core/models.py:87
msgid "SCREEN_RECORDING"
msgstr "BILDSCHIRMAUFZEICHNUNG"
#: core/models.py:90
#: core/models.py:88
msgid "TRANSCRIPT"
msgstr "TRANSKRIPT"
#: core/models.py:96
#: core/models.py:94
msgid "Public Access"
msgstr "Öffentlicher Zugriff"
#: core/models.py:97
#: core/models.py:95
msgid "Trusted Access"
msgstr "Vertrauenswürdiger Zugriff"
#: core/models.py:98
#: core/models.py:96
msgid "Restricted Access"
msgstr "Eingeschränkter Zugriff"
#: core/models.py:110
#: core/models.py:108
msgid "id"
msgstr "ID"
#: core/models.py:111
#: core/models.py:109
msgid "primary key for the record as UUID"
msgstr "Primärschlüssel des Eintrags als UUID"
#: core/models.py:117
#: core/models.py:115
msgid "created on"
msgstr "erstellt am"
#: core/models.py:118
#: core/models.py:116
msgid "date and time at which a record was created"
msgstr "Datum und Uhrzeit der Erstellung eines Eintrags"
#: core/models.py:123
#: core/models.py:121
msgid "updated on"
msgstr "aktualisiert am"
#: core/models.py:124
#: core/models.py:122
msgid "date and time at which a record was last updated"
msgstr "Datum und Uhrzeit der letzten Aktualisierung eines Eintrags"
#: core/models.py:144
#: core/models.py:142
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -198,11 +179,11 @@ msgstr ""
"Geben Sie einen gültigen Sub ein. Dieser Wert darf nur Buchstaben, Zahlen "
"und die Zeichen @/./+/-/_ enthalten."
#: core/models.py:150
#: core/models.py:148
msgid "sub"
msgstr "Sub"
#: core/models.py:152
#: core/models.py:150
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -210,55 +191,55 @@ msgstr ""
"Optional für ausstehende Benutzer; erforderlich nach Kontoaktivierung. "
"Maximal 255 Zeichen. Nur Buchstaben, Zahlen und @/./+/-/_ Zeichen erlaubt."
#: core/models.py:161
#: core/models.py:159
msgid "identity email address"
msgstr "Identitäts-E-Mail-Adresse"
#: core/models.py:166
#: core/models.py:164
msgid "admin email address"
msgstr "Administrator-E-Mail-Adresse"
#: core/models.py:168
#: core/models.py:166
msgid "full name"
msgstr "Vollständiger Name"
#: core/models.py:170
#: core/models.py:168
msgid "short name"
msgstr "Kurzname"
#: core/models.py:176
#: core/models.py:174
msgid "language"
msgstr "Sprache"
#: core/models.py:177
#: core/models.py:175
msgid "The language in which the user wants to see the interface."
msgstr "Die Sprache, in der der Benutzer die Oberfläche sehen möchte."
#: core/models.py:183
#: core/models.py:181
msgid "The timezone in which the user wants to see times."
msgstr "Die Zeitzone, in der der Benutzer die Zeiten sehen möchte."
#: core/models.py:186
#: core/models.py:184
msgid "device"
msgstr "Gerät"
#: core/models.py:188
#: core/models.py:186
msgid "Whether the user is a device or a real user."
msgstr "Ob es sich um ein Gerät oder einen echten Benutzer handelt."
#: core/models.py:191
#: core/models.py:189
msgid "staff status"
msgstr "Mitarbeiterstatus"
#: core/models.py:193
#: core/models.py:191
msgid "Whether the user can log into this admin site."
msgstr "Ob der Benutzer sich bei dieser Admin-Seite anmelden kann."
#: core/models.py:196
#: core/models.py:194
msgid "active"
msgstr "aktiv"
#: core/models.py:199
#: core/models.py:197
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -266,66 +247,66 @@ msgstr ""
"Ob dieser Benutzer als aktiv behandelt werden soll. Deaktivieren Sie dies "
"anstelle des Löschens des Kontos."
#: core/models.py:212
#: core/models.py:210
msgid "user"
msgstr "Benutzer"
#: core/models.py:213
#: core/models.py:211
msgid "users"
msgstr "Benutzer"
#: core/models.py:272
#: core/models.py:270
msgid "Resource"
msgstr "Ressource"
#: core/models.py:273
#: core/models.py:271
msgid "Resources"
msgstr "Ressourcen"
#: core/models.py:331
#: core/models.py:329
msgid "Resource access"
msgstr "Ressourcenzugriff"
#: core/models.py:332
#: core/models.py:330
msgid "Resource accesses"
msgstr "Ressourcenzugriffe"
#: core/models.py:338
#: core/models.py:336
msgid "Resource access with this User and Resource already exists."
msgstr ""
"Ein Ressourcenzugriff mit diesem Benutzer und dieser Ressource existiert "
"bereits."
#: core/models.py:394
#: core/models.py:392
msgid "Visio room configuration"
msgstr "Visio-Raumkonfiguration"
#: core/models.py:395
#: core/models.py:393
msgid "Values for Visio parameters to configure the room."
msgstr "Werte für Visio-Parameter zur Konfiguration des Raums."
#: core/models.py:402
#: core/models.py:400
msgid "Room PIN code"
msgstr "PIN-Code für den Raum"
#: core/models.py:403
#: core/models.py:401
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Eindeutiger n-stelliger Code, der diesen Raum im Telephonmodus identifiziert."
#: core/models.py:409 core/models.py:563
#: core/models.py:407 core/models.py:561
msgid "Room"
msgstr "Raum"
#: core/models.py:410
#: core/models.py:408
msgid "Rooms"
msgstr "Räume"
#: core/models.py:574
#: core/models.py:572
msgid "Worker ID"
msgstr "Worker-ID"
#: core/models.py:576
#: core/models.py:574
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -334,120 +315,120 @@ msgstr ""
"erhalten, auch wenn der Worker stoppt, was ein einfaches Nachverfolgen "
"ermöglicht."
#: core/models.py:584
#: core/models.py:582
msgid "Recording mode"
msgstr "Aufzeichnungsmodus"
#: core/models.py:585
#: core/models.py:583
msgid "Defines the mode of recording being called."
msgstr "Definiert den aufgerufenen Aufzeichnungsmodus."
#: core/models.py:590 core/models.py:591
#: core/models.py:588 core/models.py:589
msgid "Recording options"
msgstr "Aufnahmeoptionen"
#: core/models.py:597
#: core/models.py:595
msgid "Recording"
msgstr "Aufzeichnung"
#: core/models.py:598
#: core/models.py:596
msgid "Recordings"
msgstr "Aufzeichnungen"
#: core/models.py:706
#: core/models.py:704
msgid "Recording/user relation"
msgstr "Beziehung Aufzeichnung/Benutzer"
#: core/models.py:707
#: core/models.py:705
msgid "Recording/user relations"
msgstr "Beziehungen Aufzeichnung/Benutzer"
#: core/models.py:713
#: core/models.py:711
msgid "This user is already in this recording."
msgstr "Dieser Benutzer ist bereits Teil dieser Aufzeichnung."
#: core/models.py:719
#: core/models.py:717
msgid "This team is already in this recording."
msgstr "Dieses Team ist bereits Teil dieser Aufzeichnung."
#: core/models.py:725
#: core/models.py:723
msgid "Either user or team must be set, not both."
msgstr "Entweder Benutzer oder Team muss festgelegt werden, nicht beides."
#: core/models.py:742
#: core/models.py:740
msgid "Create rooms"
msgstr "Räume erstellen"
#: core/models.py:743
#: core/models.py:741
msgid "List rooms"
msgstr "Räume auflisten"
#: core/models.py:744
#: core/models.py:742
msgid "Retrieve room details"
msgstr "Raumdetails abrufen"
#: core/models.py:745
#: core/models.py:743
msgid "Update rooms"
msgstr "Räume aktualisieren"
#: core/models.py:746
#: core/models.py:744
msgid "Delete rooms"
msgstr "Räume löschen"
#: core/models.py:759
#: core/models.py:757
msgid "Application name"
msgstr "Anwendungsname"
#: core/models.py:760
#: core/models.py:758
msgid "Descriptive name for this application."
msgstr "Beschreibender Name für diese Anwendung."
#: core/models.py:770
#: core/models.py:768
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Beim Speichern gehasht. Jetzt kopieren, wenn dies ein neues Geheimnis ist."
#: core/models.py:781
#: core/models.py:779
msgid "Application"
msgstr "Anwendung"
#: core/models.py:782
#: core/models.py:780
msgid "Applications"
msgstr "Anwendungen"
#: core/models.py:805
#: core/models.py:803
msgid "Enter a valid domain"
msgstr "Geben Sie eine gültige Domain ein"
#: core/models.py:808
#: core/models.py:806
msgid "Domain"
msgstr "Domain"
#: core/models.py:809
#: core/models.py:807
msgid "Email domain this application can act on behalf of."
msgstr "E-Mail-Domain, im Namen der diese Anwendung handeln kann."
#: core/models.py:821
#: core/models.py:819
msgid "Application domain"
msgstr "Anwendungsdomain"
#: core/models.py:822
#: core/models.py:820
msgid "Application domains"
msgstr "Anwendungsdomains"
#: core/models.py:840
#: core/models.py:838
msgid "Pending"
msgstr "Ausstehend"
#: core/models.py:848
#: core/models.py:846
msgid "Ready"
msgstr "Bereit"
#: core/models.py:854
#: core/models.py:852
msgid "Background image"
msgstr "Hintergrundbild"
#: core/models.py:866
#: core/models.py:864
msgid "title"
msgstr "Titel"
@@ -464,11 +445,11 @@ msgstr "Datei"
msgid "Files"
msgstr "Dateien"
#: core/models.py:1000
#: core/models.py:970
msgid "This file is already hard deleted."
msgstr "Diese Datei wurde bereits endgültig gelöscht."
#: core/models.py:1010
#: core/models.py:980
#, fuzzy
#| msgid "To hard delete a file, it must first be soft deleted."
msgid "To hard delete a file, it must first be soft deleted."
@@ -604,18 +585,18 @@ msgstr ""
" Wenn Sie Fragen haben oder Unterstützung benötigen, wenden Sie sich bitte "
"an unser Support-Team unter %(support_email)s. "
#: meet/settings.py:223
#: meet/settings.py:224
msgid "English"
msgstr "Englisch"
#: meet/settings.py:224
#: meet/settings.py:225
msgid "French"
msgstr "Französisch"
#: meet/settings.py:225
#: meet/settings.py:226
msgid "Dutch"
msgstr "Niederländisch"
#: meet/settings.py:226
#: meet/settings.py:227
msgid "German"
msgstr "Deutsch"
Binary file not shown.
+96 -115
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
"POT-Creation-Date: 2026-02-26 17:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,11 +29,11 @@ msgstr "Permissions"
msgid "Important dates"
msgstr "Important dates"
#: core/admin.py:132 core/admin.py:275
#: core/admin.py:132 core/admin.py:243
msgid "No owner"
msgstr "No owner"
#: core/admin.py:135 core/admin.py:278
#: core/admin.py:135 core/admin.py:246
msgid "Multiple owners"
msgstr "Multiple owners"
@@ -61,134 +61,115 @@ msgstr "Successfully sent notifications for %(count)s recording(s)."
msgid "Skipped %(count)s expired recording(s)."
msgstr "Skipped %(count)s expired recording(s)."
#: core/admin.py:200
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Mark selected recordings as 'Failed to Stop'"
#: core/admin.py:218
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr "%(count)s recording(s) successfully marked as 'Failed to Stop'."
#: core/admin.py:226
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "Skipped %(count)s expired recording(s)."
#: core/admin.py:342
#: core/admin.py:309
msgid "No scopes"
msgstr "No scopes"
#: core/admin.py:344
#: core/admin.py:311
msgid "Scopes"
msgstr "Scopes"
#: core/api/filters.py:25
#: core/api/filters.py:24
msgid "Creator is me"
msgstr "Creator is me"
#: core/api/serializers.py:88
#: core/api/serializers.py:84
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "You must be administrator or owner of a room to add accesses to it."
#: core/api/serializers.py:516
#: core/api/serializers.py:443
msgid "This file extension is not allowed."
msgstr "This file extension is not allowed."
#: core/api/serializers.py:533
msgid "You have reached the maximum number of files for this type."
msgstr "You have reached the maximum number of files for this type."
#: core/models.py:37
#: core/models.py:35
msgid "Member"
msgstr "Member"
#: core/models.py:38
#: core/models.py:36
msgid "Administrator"
msgstr "Administrator"
#: core/models.py:39
#: core/models.py:37
msgid "Owner"
msgstr "Owner"
#: core/models.py:55
#: core/models.py:53
msgid "Initiated"
msgstr "Initiated"
#: core/models.py:56
#: core/models.py:54
msgid "Active"
msgstr "Active"
#: core/models.py:57
#: core/models.py:55
msgid "Stopped"
msgstr "Stopped"
#: core/models.py:58
#: core/models.py:56
msgid "Saved"
msgstr "Saved"
#: core/models.py:59
#: core/models.py:57
msgid "Aborted"
msgstr "Aborted"
#: core/models.py:60
#: core/models.py:58
msgid "Failed to Start"
msgstr "Failed to Start"
#: core/models.py:61
#: core/models.py:59
msgid "Failed to Stop"
msgstr "Failed to Stop"
#: core/models.py:62
#: core/models.py:60
msgid "Notification succeeded"
msgstr "Notification succeeded"
#: core/models.py:89
#: core/models.py:87
msgid "SCREEN_RECORDING"
msgstr "SCREEN_RECORDING"
#: core/models.py:90
#: core/models.py:88
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:96
#: core/models.py:94
msgid "Public Access"
msgstr "Public Access"
#: core/models.py:97
#: core/models.py:95
msgid "Trusted Access"
msgstr "Trusted Access"
#: core/models.py:98
#: core/models.py:96
msgid "Restricted Access"
msgstr "Restricted Access"
#: core/models.py:110
#: core/models.py:108
msgid "id"
msgstr "id"
#: core/models.py:111
#: core/models.py:109
msgid "primary key for the record as UUID"
msgstr "primary key for the record as UUID"
#: core/models.py:117
#: core/models.py:115
msgid "created on"
msgstr "created on"
#: core/models.py:118
#: core/models.py:116
msgid "date and time at which a record was created"
msgstr "date and time at which a record was created"
#: core/models.py:123
#: core/models.py:121
msgid "updated on"
msgstr "updated on"
#: core/models.py:124
#: core/models.py:122
msgid "date and time at which a record was last updated"
msgstr "date and time at which a record was last updated"
#: core/models.py:144
#: core/models.py:142
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -196,11 +177,11 @@ msgstr ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
#: core/models.py:150
#: core/models.py:148
msgid "sub"
msgstr "sub"
#: core/models.py:152
#: core/models.py:150
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -208,55 +189,55 @@ msgstr ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
#: core/models.py:161
#: core/models.py:159
msgid "identity email address"
msgstr "identity email address"
#: core/models.py:166
#: core/models.py:164
msgid "admin email address"
msgstr "admin email address"
#: core/models.py:168
#: core/models.py:166
msgid "full name"
msgstr "full name"
#: core/models.py:170
#: core/models.py:168
msgid "short name"
msgstr "short name"
#: core/models.py:176
#: core/models.py:174
msgid "language"
msgstr "language"
#: core/models.py:177
#: core/models.py:175
msgid "The language in which the user wants to see the interface."
msgstr "The language in which the user wants to see the interface."
#: core/models.py:183
#: core/models.py:181
msgid "The timezone in which the user wants to see times."
msgstr "The timezone in which the user wants to see times."
#: core/models.py:186
#: core/models.py:184
msgid "device"
msgstr "device"
#: core/models.py:188
#: core/models.py:186
msgid "Whether the user is a device or a real user."
msgstr "Whether the user is a device or a real user."
#: core/models.py:191
#: core/models.py:189
msgid "staff status"
msgstr "staff status"
#: core/models.py:193
#: core/models.py:191
msgid "Whether the user can log into this admin site."
msgstr "Whether the user can log into this admin site."
#: core/models.py:196
#: core/models.py:194
msgid "active"
msgstr "active"
#: core/models.py:199
#: core/models.py:197
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -264,63 +245,63 @@ msgstr ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
#: core/models.py:212
#: core/models.py:210
msgid "user"
msgstr "user"
#: core/models.py:213
#: core/models.py:211
msgid "users"
msgstr "users"
#: core/models.py:272
#: core/models.py:270
msgid "Resource"
msgstr "Resource"
#: core/models.py:273
#: core/models.py:271
msgid "Resources"
msgstr "Resources"
#: core/models.py:331
#: core/models.py:329
msgid "Resource access"
msgstr "Resource access"
#: core/models.py:332
#: core/models.py:330
msgid "Resource accesses"
msgstr "Resource accesses"
#: core/models.py:338
#: core/models.py:336
msgid "Resource access with this User and Resource already exists."
msgstr "Resource access with this User and Resource already exists."
#: core/models.py:394
#: core/models.py:392
msgid "Visio room configuration"
msgstr "Visio room configuration"
#: core/models.py:395
#: core/models.py:393
msgid "Values for Visio parameters to configure the room."
msgstr "Values for Visio parameters to configure the room."
#: core/models.py:402
#: core/models.py:400
msgid "Room PIN code"
msgstr "Room PIN code"
#: core/models.py:403
#: core/models.py:401
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr "Unique n-digit code that identifies this room in telephony mode."
#: core/models.py:409 core/models.py:563
#: core/models.py:407 core/models.py:561
msgid "Room"
msgstr "Room"
#: core/models.py:410
#: core/models.py:408
msgid "Rooms"
msgstr "Rooms"
#: core/models.py:574
#: core/models.py:572
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:576
#: core/models.py:574
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -328,125 +309,125 @@ msgstr ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
#: core/models.py:584
#: core/models.py:582
msgid "Recording mode"
msgstr "Recording mode"
#: core/models.py:585
#: core/models.py:583
msgid "Defines the mode of recording being called."
msgstr "Defines the mode of recording being called."
#: core/models.py:590 core/models.py:591
#: core/models.py:588 core/models.py:589
msgid "Recording options"
msgstr "Recording options"
#: core/models.py:597
#: core/models.py:595
msgid "Recording"
msgstr "Recording"
#: core/models.py:598
#: core/models.py:596
msgid "Recordings"
msgstr "Recordings"
#: core/models.py:706
#: core/models.py:704
msgid "Recording/user relation"
msgstr "Recording/user relation"
#: core/models.py:707
#: core/models.py:705
msgid "Recording/user relations"
msgstr "Recording/user relations"
#: core/models.py:713
#: core/models.py:711
msgid "This user is already in this recording."
msgstr "This user is already in this recording."
#: core/models.py:719
#: core/models.py:717
msgid "This team is already in this recording."
msgstr "This team is already in this recording."
#: core/models.py:725
#: core/models.py:723
msgid "Either user or team must be set, not both."
msgstr "Either user or team must be set, not both."
#: core/models.py:742
#: core/models.py:740
#, fuzzy
#| msgid "created on"
msgid "Create rooms"
msgstr "Create rooms"
#: core/models.py:743
#: core/models.py:741
msgid "List rooms"
msgstr "List rooms"
#: core/models.py:744
#: core/models.py:742
msgid "Retrieve room details"
msgstr "Retrieve room details"
#: core/models.py:745
#: core/models.py:743
#, fuzzy
#| msgid "updated on"
msgid "Update rooms"
msgstr "Update rooms"
#: core/models.py:746
#: core/models.py:744
msgid "Delete rooms"
msgstr "Delete rooms"
#: core/models.py:759
#: core/models.py:757
msgid "Application name"
msgstr "Application name"
#: core/models.py:760
#: core/models.py:758
msgid "Descriptive name for this application."
msgstr "Descriptive name for this application."
#: core/models.py:770
#: core/models.py:768
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Hashed on Save. Copy it now if this is a new secret."
#: core/models.py:781
#: core/models.py:779
msgid "Application"
msgstr "Application"
#: core/models.py:782
#: core/models.py:780
msgid "Applications"
msgstr "Applications"
#: core/models.py:805
#: core/models.py:803
msgid "Enter a valid domain"
msgstr "Enter a valid domain"
#: core/models.py:808
#: core/models.py:806
msgid "Domain"
msgstr "Domain"
#: core/models.py:809
#: core/models.py:807
msgid "Email domain this application can act on behalf of."
msgstr "Email domain this application can act on behalf of."
#: core/models.py:821
#: core/models.py:819
msgid "Application domain"
msgstr "Application domain"
#: core/models.py:822
#: core/models.py:820
msgid "Application domains"
msgstr "Application domains"
#: core/models.py:840
#: core/models.py:838
#, fuzzy
#| msgid "Recording"
msgid "Pending"
msgstr "Pending"
#: core/models.py:848
#: core/models.py:846
msgid "Ready"
msgstr "Ready"
#: core/models.py:854
#: core/models.py:852
msgid "Background image"
msgstr "Background image"
#: core/models.py:866
#: core/models.py:864
msgid "title"
msgstr "title"
@@ -462,13 +443,13 @@ msgstr "File"
msgid "Files"
msgstr "Files"
#: core/models.py:1000
#: core/models.py:970
#, fuzzy
#| msgid "This user is already in this recording."
msgid "This file is already hard deleted."
msgstr "This file is already hard deleted."
#: core/models.py:1010
#: core/models.py:980
msgid "To hard delete a file, it must first be soft deleted."
msgstr "To hard delete a file, it must first be soft deleted."
@@ -600,18 +581,18 @@ msgstr ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
#: meet/settings.py:223
#: meet/settings.py:224
msgid "English"
msgstr "English"
#: meet/settings.py:224
#: meet/settings.py:225
msgid "French"
msgstr "French"
#: meet/settings.py:225
#: meet/settings.py:226
msgid "Dutch"
msgstr "Dutch"
#: meet/settings.py:226
#: meet/settings.py:227
msgid "German"
msgstr "German"
Binary file not shown.
+98 -121
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
"POT-Creation-Date: 2026-02-26 17:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,11 +29,11 @@ msgstr "Permissions"
msgid "Important dates"
msgstr "Dates importantes"
#: core/admin.py:132 core/admin.py:275
#: core/admin.py:132 core/admin.py:243
msgid "No owner"
msgstr "Pas de propriétaire"
#: core/admin.py:135 core/admin.py:278
#: core/admin.py:135 core/admin.py:246
msgid "Multiple owners"
msgstr "Plusieurs propriétaires"
@@ -61,139 +61,119 @@ msgstr "Notifications envoyées avec succès pour %(count)s enregistrement(s)."
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s enregistrement(s) expiré(s) ignoré(s)."
#: core/admin.py:200
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Marquer les enregistrements sélectionnés comme « Échec darrêt »"
#: core/admin.py:218
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr ""
"%(count)s enregistrement(s) marqué(s) avec succès comme « Échec darrêt »."
#: core/admin.py:226
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "%(count)s enregistrement(s) avec un statut inéligible ignoré(s)."
#: core/admin.py:342
#: core/admin.py:309
msgid "No scopes"
msgstr "Aucun scopes"
#: core/admin.py:344
#: core/admin.py:311
msgid "Scopes"
msgstr "Scopes"
#: core/api/filters.py:25
#: core/api/filters.py:24
msgid "Creator is me"
msgstr "Je suis le créateur"
#: core/api/serializers.py:88
#: core/api/serializers.py:84
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Vous devez être administrateur ou propriétaire d'une salle pour y ajouter "
"des accès."
#: core/api/serializers.py:516
#: core/api/serializers.py:443
msgid "This file extension is not allowed."
msgstr "Cette extension n'est pas autorisée"
#: core/api/serializers.py:533
msgid "You have reached the maximum number of files for this type."
msgstr "Vous avez atteint le nombre maximum de fichiers de ce type"
#: core/models.py:37
#: core/models.py:35
msgid "Member"
msgstr "Membre"
#: core/models.py:38
#: core/models.py:36
msgid "Administrator"
msgstr "Administrateur"
#: core/models.py:39
#: core/models.py:37
msgid "Owner"
msgstr "Propriétaire"
#: core/models.py:55
#: core/models.py:53
msgid "Initiated"
msgstr "Initié"
#: core/models.py:56
#: core/models.py:54
msgid "Active"
msgstr "Actif"
#: core/models.py:57
#: core/models.py:55
msgid "Stopped"
msgstr "Arrêté"
#: core/models.py:58
#: core/models.py:56
msgid "Saved"
msgstr "Enregistré"
#: core/models.py:59
#: core/models.py:57
msgid "Aborted"
msgstr "Abandonné"
#: core/models.py:60
#: core/models.py:58
msgid "Failed to Start"
msgstr "Échec au démarrage"
#: core/models.py:61
#: core/models.py:59
msgid "Failed to Stop"
msgstr "Échec à l'arrêt"
#: core/models.py:62
#: core/models.py:60
msgid "Notification succeeded"
msgstr "Notification réussie"
#: core/models.py:89
#: core/models.py:87
msgid "SCREEN_RECORDING"
msgstr "ENREGISTREMENT_ÉCRAN"
#: core/models.py:90
#: core/models.py:88
msgid "TRANSCRIPT"
msgstr "TRANSCRIPTION"
#: core/models.py:96
#: core/models.py:94
msgid "Public Access"
msgstr "Accès public"
#: core/models.py:97
#: core/models.py:95
msgid "Trusted Access"
msgstr "Accès de confiance"
#: core/models.py:98
#: core/models.py:96
msgid "Restricted Access"
msgstr "Accès restreint"
#: core/models.py:110
#: core/models.py:108
msgid "id"
msgstr "id"
#: core/models.py:111
#: core/models.py:109
msgid "primary key for the record as UUID"
msgstr "clé primaire pour l'enregistrement sous forme d'UUID"
#: core/models.py:117
#: core/models.py:115
msgid "created on"
msgstr "créé le"
#: core/models.py:118
#: core/models.py:116
msgid "date and time at which a record was created"
msgstr "date et heure auxquelles un enregistrement a été créé"
#: core/models.py:123
#: core/models.py:121
msgid "updated on"
msgstr "mis à jour le"
#: core/models.py:124
#: core/models.py:122
msgid "date and time at which a record was last updated"
msgstr ""
"date et heure auxquelles un enregistrement a été mis à jour pour la dernière "
"fois"
#: core/models.py:144
#: core/models.py:142
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -201,11 +181,11 @@ msgstr ""
"Entrez un sub valide. Cette valeur ne peut contenir que des lettres, des "
"chiffres et les caractères @/./+/-/_."
#: core/models.py:150
#: core/models.py:148
msgid "sub"
msgstr "sub"
#: core/models.py:152
#: core/models.py:150
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -213,55 +193,55 @@ msgstr ""
"Optionnel pour les utilisateurs en attente ; requis lors de l'activation du "
"compte. 255 caractères maximum. Lettres, chiffres et @/./+/-/_ uniquement."
#: core/models.py:161
#: core/models.py:159
msgid "identity email address"
msgstr "adresse e-mail d'identité"
#: core/models.py:166
#: core/models.py:164
msgid "admin email address"
msgstr "adresse e-mail d'administrateur"
#: core/models.py:168
#: core/models.py:166
msgid "full name"
msgstr "nom complet"
#: core/models.py:170
#: core/models.py:168
msgid "short name"
msgstr "nom court"
#: core/models.py:176
#: core/models.py:174
msgid "language"
msgstr "langue"
#: core/models.py:177
#: core/models.py:175
msgid "The language in which the user wants to see the interface."
msgstr "La langue dans laquelle l'utilisateur souhaite voir l'interface."
#: core/models.py:183
#: core/models.py:181
msgid "The timezone in which the user wants to see times."
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
#: core/models.py:186
#: core/models.py:184
msgid "device"
msgstr "appareil"
#: core/models.py:188
#: core/models.py:186
msgid "Whether the user is a device or a real user."
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
#: core/models.py:191
#: core/models.py:189
msgid "staff status"
msgstr "statut du personnel"
#: core/models.py:193
#: core/models.py:191
msgid "Whether the user can log into this admin site."
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
#: core/models.py:196
#: core/models.py:194
msgid "active"
msgstr "actif"
#: core/models.py:199
#: core/models.py:197
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -269,65 +249,65 @@ msgstr ""
"Si cet utilisateur doit être traité comme actif. Désélectionnez cette option "
"au lieu de supprimer des comptes."
#: core/models.py:212
#: core/models.py:210
msgid "user"
msgstr "utilisateur"
#: core/models.py:213
#: core/models.py:211
msgid "users"
msgstr "utilisateurs"
#: core/models.py:272
#: core/models.py:270
msgid "Resource"
msgstr "Ressource"
#: core/models.py:273
#: core/models.py:271
msgid "Resources"
msgstr "Ressources"
#: core/models.py:331
#: core/models.py:329
msgid "Resource access"
msgstr "Accès aux ressources"
#: core/models.py:332
#: core/models.py:330
msgid "Resource accesses"
msgstr "Accès aux ressources"
#: core/models.py:338
#: core/models.py:336
msgid "Resource access with this User and Resource already exists."
msgstr ""
"L'accès à la ressource avec cet utilisateur et cette ressource existe déjà."
#: core/models.py:394
#: core/models.py:392
msgid "Visio room configuration"
msgstr "Configuration de la salle de visioconférence"
#: core/models.py:395
#: core/models.py:393
msgid "Values for Visio parameters to configure the room."
msgstr "Valeurs des paramètres de visioconférence pour configurer la salle."
#: core/models.py:402
#: core/models.py:400
msgid "Room PIN code"
msgstr "Code PIN de la salle"
#: core/models.py:403
#: core/models.py:401
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Code unique à n chiffres qui identifie cette salle en mode téléphonique."
#: core/models.py:409 core/models.py:563
#: core/models.py:407 core/models.py:561
msgid "Room"
msgstr "Salle"
#: core/models.py:410
#: core/models.py:408
msgid "Rooms"
msgstr "Salles"
#: core/models.py:574
#: core/models.py:572
msgid "Worker ID"
msgstr "ID du Worker"
#: core/models.py:576
#: core/models.py:574
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -335,128 +315,127 @@ msgstr ""
"Entrez un identifiant pour l'enregistrement du Worker. Cet identifiant est "
"conservé même lorsque le Worker s'arrête, permettant un suivi facile."
#: core/models.py:584
#: core/models.py:582
msgid "Recording mode"
msgstr "Mode d'enregistrement"
#: core/models.py:585
#: core/models.py:583
msgid "Defines the mode of recording being called."
msgstr "Définit le mode d'enregistrement appelé."
#: core/models.py:590 core/models.py:591
#: core/models.py:588 core/models.py:589
msgid "Recording options"
msgstr "Options d'enregistrement"
#: core/models.py:597
#: core/models.py:595
msgid "Recording"
msgstr "Enregistrement"
#: core/models.py:598
#: core/models.py:596
msgid "Recordings"
msgstr "Enregistrements"
#: core/models.py:706
#: core/models.py:704
msgid "Recording/user relation"
msgstr "Relation enregistrement/utilisateur"
#: core/models.py:707
#: core/models.py:705
msgid "Recording/user relations"
msgstr "Relations enregistrement/utilisateur"
#: core/models.py:713
#: core/models.py:711
msgid "This user is already in this recording."
msgstr "Cet utilisateur est déjà dans cet enregistrement."
#: core/models.py:719
#: core/models.py:717
msgid "This team is already in this recording."
msgstr "Cette équipe est déjà dans cet enregistrement."
#: core/models.py:725
#: core/models.py:723
msgid "Either user or team must be set, not both."
msgstr "Soit l'utilisateur, soit l'équipe doit être défini, pas les deux."
#: core/models.py:742
#: core/models.py:740
msgid "Create rooms"
msgstr "Créer des salles"
#: core/models.py:743
#: core/models.py:741
msgid "List rooms"
msgstr "Lister les salles"
#: core/models.py:744
#: core/models.py:742
msgid "Retrieve room details"
msgstr "Afficher les détails dune salle"
#: core/models.py:745
#: core/models.py:743
msgid "Update rooms"
msgstr "Mettre à jour les salles"
#: core/models.py:746
#: core/models.py:744
msgid "Delete rooms"
msgstr "Supprimer les salles"
#: core/models.py:759
#: core/models.py:757
msgid "Application name"
msgstr "Nom de lapplication"
#: core/models.py:760
#: core/models.py:758
msgid "Descriptive name for this application."
msgstr "Nom descriptif de cette application."
#: core/models.py:770
#: core/models.py:768
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Haché lors de lenregistrement. Copiez-le maintenant sil sagit dun "
"nouveau secret."
#: core/models.py:781
#: core/models.py:779
msgid "Application"
msgstr "Application"
#: core/models.py:782
#: core/models.py:780
msgid "Applications"
msgstr "Applications"
#: core/models.py:805
#: core/models.py:803
msgid "Enter a valid domain"
msgstr "Saisissez un domaine valide"
#: core/models.py:808
#: core/models.py:806
msgid "Domain"
msgstr "Domaine"
#: core/models.py:809
#: core/models.py:807
msgid "Email domain this application can act on behalf of."
msgstr "Domaine de messagerie au nom duquel cette application peut agir."
#: core/models.py:821
#: core/models.py:819
msgid "Application domain"
msgstr "Domaine dapplication"
#: core/models.py:822
#: core/models.py:820
msgid "Application domains"
msgstr "Domaines dapplication"
#: core/models.py:840
#: core/models.py:838
msgid "Pending"
msgstr "En attente"
#: core/models.py:848
#: core/models.py:846
msgid "Ready"
msgstr "Prêt"
#: core/models.py:854
#: core/models.py:852
msgid "Background image"
msgstr "Image de fond"
#: core/models.py:866
#: core/models.py:864
msgid "title"
msgstr "Titre"
#: core/models.py:890
msgid "Malware detection info when the analysis status is unsafe."
msgstr ""
"Information concernant la détection de Malware cand le statut n'est pas sain"
msgstr "Information concernant la détection de Malware cand le statut n'est pas sain"
#: core/models.py:895
msgid "File"
@@ -466,17 +445,15 @@ msgstr "Fichier"
msgid "Files"
msgstr "Fichiers"
#: core/models.py:1000
#: core/models.py:970
#, fuzzy
#| msgid "This user is already in this recording."
msgid "This file is already hard deleted."
msgstr "Ce fichier a été supprimé."
#: core/models.py:1010
#: core/models.py:980
msgid "To hard delete a file, it must first be soft deleted."
msgstr ""
"Pour supprimer définitivement un fichier il doit d'abord avoir été marqué "
"comme supprimé (soft delete)"
msgstr "Pour supprimer définitivement un fichier il doit d'abord avoir été marqué comme supprimé (soft delete)"
#: core/recording/event/notification.py:116
msgid "Your recording is ready"
@@ -606,18 +583,18 @@ msgstr ""
" Si vous avez des questions ou besoin d'assistance, veuillez contacter notre "
"équipe d'assistance à %(support_email)s. "
#: meet/settings.py:223
#: meet/settings.py:224
msgid "English"
msgstr "Anglais"
#: meet/settings.py:224
#: meet/settings.py:225
msgid "French"
msgstr "Français"
#: meet/settings.py:225
#: meet/settings.py:226
msgid "Dutch"
msgstr "Néerlandais"
#: meet/settings.py:226
#: meet/settings.py:227
msgid "German"
msgstr "Allemand"
Binary file not shown.
+96 -115
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
"POT-Creation-Date: 2026-02-26 17:34+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,11 +29,11 @@ msgstr "Rechten"
msgid "Important dates"
msgstr "Belangrijke datums"
#: core/admin.py:132 core/admin.py:275
#: core/admin.py:132 core/admin.py:243
msgid "No owner"
msgstr "Geen eigenaar"
#: core/admin.py:135 core/admin.py:278
#: core/admin.py:135 core/admin.py:246
msgid "Multiple owners"
msgstr "Meerdere eigenaren"
@@ -61,135 +61,116 @@ msgstr "Meldingen succesvol verzonden voor %(count)s opname(n)."
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s verlopen opname(n) overgeslagen."
#: core/admin.py:200
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Geselecteerde opnames markeren als 'Mislukt bij stoppen'"
#: core/admin.py:218
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr "%(count)s opname(s) succesvol gemarkeerd als 'Mislukt bij stoppen'."
#: core/admin.py:226
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "%(count)s opname(s) met een niet-toegestane status overgeslagen."
#: core/admin.py:342
#: core/admin.py:309
msgid "No scopes"
msgstr "Geen scopes"
#: core/admin.py:344
#: core/admin.py:311
msgid "Scopes"
msgstr "Scopes"
#: core/api/filters.py:25
#: core/api/filters.py:24
msgid "Creator is me"
msgstr "Maker ben ik"
#: core/api/serializers.py:88
#: core/api/serializers.py:84
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Je moet beheerder of eigenaar van een ruimte zijn om toegang toe te voegen."
#: core/api/serializers.py:516
#: core/api/serializers.py:443
msgid "This file extension is not allowed."
msgstr "Deze bestandsextensie is niet toegestaan."
#: core/api/serializers.py:533
msgid "You have reached the maximum number of files for this type."
msgstr "Het maximale aantal bestanden voor dit type is bereikt."
#: core/models.py:37
#: core/models.py:35
msgid "Member"
msgstr "Lid"
#: core/models.py:38
#: core/models.py:36
msgid "Administrator"
msgstr "Beheerder"
#: core/models.py:39
#: core/models.py:37
msgid "Owner"
msgstr "Eigenaar"
#: core/models.py:55
#: core/models.py:53
msgid "Initiated"
msgstr "Gestart"
#: core/models.py:56
#: core/models.py:54
msgid "Active"
msgstr "Actief"
#: core/models.py:57
#: core/models.py:55
msgid "Stopped"
msgstr "Gestopt"
#: core/models.py:58
#: core/models.py:56
msgid "Saved"
msgstr "Opgeslagen"
#: core/models.py:59
#: core/models.py:57
msgid "Aborted"
msgstr "Afgebroken"
#: core/models.py:60
#: core/models.py:58
msgid "Failed to Start"
msgstr "Starten mislukt"
#: core/models.py:61
#: core/models.py:59
msgid "Failed to Stop"
msgstr "Stoppen mislukt"
#: core/models.py:62
#: core/models.py:60
msgid "Notification succeeded"
msgstr "Notificatie geslaagd"
#: core/models.py:89
#: core/models.py:87
msgid "SCREEN_RECORDING"
msgstr "SCHERM_OPNAME"
#: core/models.py:90
#: core/models.py:88
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:96
#: core/models.py:94
msgid "Public Access"
msgstr "Openbare toegang"
#: core/models.py:97
#: core/models.py:95
msgid "Trusted Access"
msgstr "Vertrouwde toegang"
#: core/models.py:98
#: core/models.py:96
msgid "Restricted Access"
msgstr "Beperkte toegang"
#: core/models.py:110
#: core/models.py:108
msgid "id"
msgstr "id"
#: core/models.py:111
#: core/models.py:109
msgid "primary key for the record as UUID"
msgstr "primaire sleutel voor het record als UUID"
#: core/models.py:117
#: core/models.py:115
msgid "created on"
msgstr "aangemaakt op"
#: core/models.py:118
#: core/models.py:116
msgid "date and time at which a record was created"
msgstr "datum en tijd waarop een record werd aangemaakt"
#: core/models.py:123
#: core/models.py:121
msgid "updated on"
msgstr "bijgewerkt op"
#: core/models.py:124
#: core/models.py:122
msgid "date and time at which a record was last updated"
msgstr "datum en tijd waarop een record voor het laatst werd bijgewerkt"
#: core/models.py:144
#: core/models.py:142
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -197,11 +178,11 @@ msgstr ""
"Voer een geldige sub in. Deze waarde mag alleen letters, cijfers en @/./+/-/"
"_ tekens bevatten."
#: core/models.py:150
#: core/models.py:148
msgid "sub"
msgstr "sub"
#: core/models.py:152
#: core/models.py:150
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -209,55 +190,55 @@ msgstr ""
"Optioneel voor gebruikers in afwachting; vereist bij accountactivering. "
"Maximum 255 tekens. Alleen letters, cijfers en @/./+/-/_ toegestaan."
#: core/models.py:161
#: core/models.py:159
msgid "identity email address"
msgstr "identiteit e-mailadres"
#: core/models.py:166
#: core/models.py:164
msgid "admin email address"
msgstr "beheerder e-mailadres"
#: core/models.py:168
#: core/models.py:166
msgid "full name"
msgstr "volledige naam"
#: core/models.py:170
#: core/models.py:168
msgid "short name"
msgstr "korte naam"
#: core/models.py:176
#: core/models.py:174
msgid "language"
msgstr "taal"
#: core/models.py:177
#: core/models.py:175
msgid "The language in which the user wants to see the interface."
msgstr "De taal waarin de gebruiker de interface wil zien."
#: core/models.py:183
#: core/models.py:181
msgid "The timezone in which the user wants to see times."
msgstr "De tijdzone waarin de gebruiker tijden wil zien."
#: core/models.py:186
#: core/models.py:184
msgid "device"
msgstr "apparaat"
#: core/models.py:188
#: core/models.py:186
msgid "Whether the user is a device or a real user."
msgstr "Of de gebruiker een apparaat is of een echte gebruiker."
#: core/models.py:191
#: core/models.py:189
msgid "staff status"
msgstr "personeelsstatus"
#: core/models.py:193
#: core/models.py:191
msgid "Whether the user can log into this admin site."
msgstr "Of de gebruiker kan inloggen op deze beheersite."
#: core/models.py:196
#: core/models.py:194
msgid "active"
msgstr "actief"
#: core/models.py:199
#: core/models.py:197
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -265,64 +246,64 @@ msgstr ""
"Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in "
"plaats van accounts te verwijderen."
#: core/models.py:212
#: core/models.py:210
msgid "user"
msgstr "gebruiker"
#: core/models.py:213
#: core/models.py:211
msgid "users"
msgstr "gebruikers"
#: core/models.py:272
#: core/models.py:270
msgid "Resource"
msgstr "Bron"
#: core/models.py:273
#: core/models.py:271
msgid "Resources"
msgstr "Bronnen"
#: core/models.py:331
#: core/models.py:329
msgid "Resource access"
msgstr "Brontoegang"
#: core/models.py:332
#: core/models.py:330
msgid "Resource accesses"
msgstr "Brontoegangsrechten"
#: core/models.py:338
#: core/models.py:336
msgid "Resource access with this User and Resource already exists."
msgstr "Brontoegang met deze gebruiker en bron bestaat al."
#: core/models.py:394
#: core/models.py:392
msgid "Visio room configuration"
msgstr "Visio-ruimteconfiguratie"
#: core/models.py:395
#: core/models.py:393
msgid "Values for Visio parameters to configure the room."
msgstr "Waarden voor Visio-parameters om de ruimte te configureren."
#: core/models.py:402
#: core/models.py:400
msgid "Room PIN code"
msgstr "Pincode van de kamer"
#: core/models.py:403
#: core/models.py:401
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Unieke n-cijferige code die deze kamer identificeert in telefonie-modus."
#: core/models.py:409 core/models.py:563
#: core/models.py:407 core/models.py:561
msgid "Room"
msgstr "Ruimte"
#: core/models.py:410
#: core/models.py:408
msgid "Rooms"
msgstr "Ruimtes"
#: core/models.py:574
#: core/models.py:572
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:576
#: core/models.py:574
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -330,120 +311,120 @@ msgstr ""
"Voer een identificatie in voor de worker-opname. Deze ID blijft behouden, "
"zelfs wanneer de worker stopt, waardoor eenvoudige tracking mogelijk is."
#: core/models.py:584
#: core/models.py:582
msgid "Recording mode"
msgstr "Opnamemodus"
#: core/models.py:585
#: core/models.py:583
msgid "Defines the mode of recording being called."
msgstr "Definieert de modus van opname die wordt aangeroepen."
#: core/models.py:590 core/models.py:591
#: core/models.py:588 core/models.py:589
msgid "Recording options"
msgstr "Opnameopties"
#: core/models.py:597
#: core/models.py:595
msgid "Recording"
msgstr "Opname"
#: core/models.py:598
#: core/models.py:596
msgid "Recordings"
msgstr "Opnames"
#: core/models.py:706
#: core/models.py:704
msgid "Recording/user relation"
msgstr "Opname/gebruiker-relatie"
#: core/models.py:707
#: core/models.py:705
msgid "Recording/user relations"
msgstr "Opname/gebruiker-relaties"
#: core/models.py:713
#: core/models.py:711
msgid "This user is already in this recording."
msgstr "Deze gebruiker is al in deze opname."
#: core/models.py:719
#: core/models.py:717
msgid "This team is already in this recording."
msgstr "Dit team is al in deze opname."
#: core/models.py:725
#: core/models.py:723
msgid "Either user or team must be set, not both."
msgstr "Ofwel gebruiker of team moet worden ingesteld, niet beide."
#: core/models.py:742
#: core/models.py:740
msgid "Create rooms"
msgstr "Ruimtes aanmaken"
#: core/models.py:743
#: core/models.py:741
msgid "List rooms"
msgstr "Ruimtes weergeven"
#: core/models.py:744
#: core/models.py:742
msgid "Retrieve room details"
msgstr "Details van een ruimte ophalen"
#: core/models.py:745
#: core/models.py:743
msgid "Update rooms"
msgstr "Ruimtes bijwerken"
#: core/models.py:746
#: core/models.py:744
msgid "Delete rooms"
msgstr "Ruimtes verwijderen"
#: core/models.py:759
#: core/models.py:757
msgid "Application name"
msgstr "Naam van de applicatie"
#: core/models.py:760
#: core/models.py:758
msgid "Descriptive name for this application."
msgstr "Beschrijvende naam voor deze applicatie."
#: core/models.py:770
#: core/models.py:768
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Wordt gehasht bij het opslaan. Kopieer het nu als dit een nieuw geheim is."
#: core/models.py:781
#: core/models.py:779
msgid "Application"
msgstr "Applicatie"
#: core/models.py:782
#: core/models.py:780
msgid "Applications"
msgstr "Applicaties"
#: core/models.py:805
#: core/models.py:803
msgid "Enter a valid domain"
msgstr "Voer een geldig domein in"
#: core/models.py:808
#: core/models.py:806
msgid "Domain"
msgstr "Domein"
#: core/models.py:809
#: core/models.py:807
msgid "Email domain this application can act on behalf of."
msgstr "E-maildomein namens welke deze applicatie kan handelen."
#: core/models.py:821
#: core/models.py:819
msgid "Application domain"
msgstr "Applicatiedomein"
#: core/models.py:822
#: core/models.py:820
msgid "Application domains"
msgstr "Applicatiedomeinen"
#: core/models.py:840
#: core/models.py:838
msgid "Pending"
msgstr "In afwachting"
#: core/models.py:848
#: core/models.py:846
msgid "Ready"
msgstr "Klaar"
#: core/models.py:854
#: core/models.py:852
msgid "Background image"
msgstr "Achtergrondafbeelding"
#: core/models.py:866
#: core/models.py:864
msgid "title"
msgstr "Titel"
@@ -459,11 +440,11 @@ msgstr "Bestand"
msgid "Files"
msgstr "Bestanden"
#: core/models.py:1000
#: core/models.py:970
msgid "This file is already hard deleted."
msgstr "Dit bestand is al definitief verwijderd."
#: core/models.py:1010
#: core/models.py:980
#, fuzzy
#| msgid "To hard delete a file, it must first be soft deleted."
msgid "To hard delete a file, it must first be soft deleted."
@@ -599,18 +580,18 @@ msgstr ""
" Als je vragen hebt of hulp nodig hebt, neem dan contact op met ons support "
"team via %(support_email)s. "
#: meet/settings.py:223
#: meet/settings.py:224
msgid "English"
msgstr "Engels"
#: meet/settings.py:224
#: meet/settings.py:225
msgid "French"
msgstr "Frans"
#: meet/settings.py:225
#: meet/settings.py:226
msgid "Dutch"
msgstr "Nederlands"
#: meet/settings.py:226
#: meet/settings.py:227
msgid "German"
msgstr "Duits"
+4 -15
View File
@@ -176,13 +176,6 @@ class Base(Configuration):
environ_prefix=None,
)
FILE_UPLOAD_ENABLED = values.BooleanValue(
# False to avoid a breaking change for now
default=False,
environ_name="FILE_UPLOAD_ENABLED",
environ_prefix=None,
)
FILE_UPLOAD_PATH = values.Value(
"files", environ_name="FILE_UPLOAD_PATH", environ_prefix=None
)
@@ -195,7 +188,6 @@ class Base(Configuration):
{
"background_image": {
"max_size": 2 * MB,
"max_count_by_user": 10,
"allowed_extensions": [".jpeg", ".jpg", ".png"],
"allowed_mimetypes": ["image/jpeg", "image/png"],
},
@@ -445,11 +437,9 @@ class Base(Configuration):
# Celery
# Defaults to False to avoid breaking change, async task will be run
# synchronously
CELERY_ENABLED = values.BooleanValue(False, environ_prefix=None)
CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(False, environ_prefix=None)
CELERY_TASK_DEFAULT_QUEUE = values.Value("meet-backend", environ_prefix=None)
CELERY_BROKER_URL = values.Value("redis://redis:6379/0", environ_prefix=None)
CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({}, environ_prefix=None)
CELERY_ENABLED = values.BooleanValue(False)
CELERY_BROKER_URL = values.Value("redis://redis:6379/0")
CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({})
# Session
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
@@ -980,8 +970,7 @@ class Test(Base):
APPLICATION_JWT_SECRET_KEY = "devKey" # noqa:S105
APPLICATION_JWT_AUDIENCE = "Test inc."
CELERY_TASK_ALWAYS_EAGER = True
FILE_UPLOAD_ENABLED = True
CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(True)
def __init__(self):
# pylint: disable=invalid-name
+11 -11
View File
@@ -2,8 +2,8 @@
# Meet package
#
[build-system]
requires = ["uv_build>=0.10.9,<0.11.0"]
build-backend = "uv_build"
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "meet"
@@ -21,7 +21,8 @@ classifiers = [
]
description = "A simple video and phone conferencing tool, powered by LiveKit"
keywords = ["Django", "Contacts", "Templates", "RBAC"]
license = "MIT"
license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"boto3==1.42.49",
@@ -69,7 +70,7 @@ dependencies = [
"Homepage" = "https://github.com/suitenumerique/meet"
"Repository" = "https://github.com/suitenumerique/meet"
[dependency-groups]
[project.optional-dependencies]
dev = [
"django-extensions==4.1",
"drf-spectacular-sidecar==2026.1.1",
@@ -89,13 +90,12 @@ dev = [
"types-requests==2.32.4.20260107",
]
[tool.uv.build-backend]
module-root = ""
source-exclude = [
"**/tests/**",
"**/test_*.py",
"**/tests.py",
]
[tool.setuptools]
packages = { find = { where = ["."], exclude = ["tests"] } }
zip-safe = true
[tool.distutils.bdist_wheel]
universal = true
[tool.ruff]
exclude = [
-2365
View File
File diff suppressed because it is too large Load Diff
+85 -160
View File
@@ -3959,9 +3959,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.2.tgz",
"integrity": "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==",
"cpu": [
"arm"
],
@@ -3973,9 +3973,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.2.tgz",
"integrity": "sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==",
"cpu": [
"arm64"
],
@@ -3987,9 +3987,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.2.tgz",
"integrity": "sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==",
"cpu": [
"arm64"
],
@@ -4001,9 +4001,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.2.tgz",
"integrity": "sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==",
"cpu": [
"x64"
],
@@ -4015,9 +4015,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.2.tgz",
"integrity": "sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==",
"cpu": [
"arm64"
],
@@ -4029,9 +4029,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.2.tgz",
"integrity": "sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==",
"cpu": [
"x64"
],
@@ -4043,9 +4043,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.2.tgz",
"integrity": "sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==",
"cpu": [
"arm"
],
@@ -4057,9 +4057,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.2.tgz",
"integrity": "sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==",
"cpu": [
"arm"
],
@@ -4071,9 +4071,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.2.tgz",
"integrity": "sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==",
"cpu": [
"arm64"
],
@@ -4085,9 +4085,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.2.tgz",
"integrity": "sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==",
"cpu": [
"arm64"
],
@@ -4098,10 +4098,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.2.tgz",
"integrity": "sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==",
"cpu": [
"loong64"
],
@@ -4112,38 +4112,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.2.tgz",
"integrity": "sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==",
"cpu": [
"ppc64"
],
@@ -4155,9 +4127,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.2.tgz",
"integrity": "sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==",
"cpu": [
"riscv64"
],
@@ -4169,9 +4141,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.2.tgz",
"integrity": "sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==",
"cpu": [
"riscv64"
],
@@ -4183,9 +4155,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.2.tgz",
"integrity": "sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==",
"cpu": [
"s390x"
],
@@ -4197,9 +4169,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.2.tgz",
"integrity": "sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==",
"cpu": [
"x64"
],
@@ -4211,9 +4183,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.2.tgz",
"integrity": "sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==",
"cpu": [
"x64"
],
@@ -4224,38 +4196,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.2.tgz",
"integrity": "sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==",
"cpu": [
"arm64"
],
@@ -4267,9 +4211,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.2.tgz",
"integrity": "sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==",
"cpu": [
"ia32"
],
@@ -4280,24 +4224,10 @@
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.2.tgz",
"integrity": "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==",
"cpu": [
"x64"
],
@@ -10131,9 +10061,9 @@
}
},
"node_modules/rollup": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"version": "4.44.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.2.tgz",
"integrity": "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10147,31 +10077,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.59.0",
"@rollup/rollup-android-arm64": "4.59.0",
"@rollup/rollup-darwin-arm64": "4.59.0",
"@rollup/rollup-darwin-x64": "4.59.0",
"@rollup/rollup-freebsd-arm64": "4.59.0",
"@rollup/rollup-freebsd-x64": "4.59.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
"@rollup/rollup-linux-arm64-musl": "4.59.0",
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
"@rollup/rollup-linux-loong64-musl": "4.59.0",
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
"@rollup/rollup-linux-x64-gnu": "4.59.0",
"@rollup/rollup-linux-x64-musl": "4.59.0",
"@rollup/rollup-openbsd-x64": "4.59.0",
"@rollup/rollup-openharmony-arm64": "4.59.0",
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
"@rollup/rollup-win32-x64-gnu": "4.59.0",
"@rollup/rollup-win32-x64-msvc": "4.59.0",
"@rollup/rollup-android-arm-eabi": "4.44.2",
"@rollup/rollup-android-arm64": "4.44.2",
"@rollup/rollup-darwin-arm64": "4.44.2",
"@rollup/rollup-darwin-x64": "4.44.2",
"@rollup/rollup-freebsd-arm64": "4.44.2",
"@rollup/rollup-freebsd-x64": "4.44.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.44.2",
"@rollup/rollup-linux-arm-musleabihf": "4.44.2",
"@rollup/rollup-linux-arm64-gnu": "4.44.2",
"@rollup/rollup-linux-arm64-musl": "4.44.2",
"@rollup/rollup-linux-loongarch64-gnu": "4.44.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.44.2",
"@rollup/rollup-linux-riscv64-gnu": "4.44.2",
"@rollup/rollup-linux-riscv64-musl": "4.44.2",
"@rollup/rollup-linux-s390x-gnu": "4.44.2",
"@rollup/rollup-linux-x64-gnu": "4.44.2",
"@rollup/rollup-linux-x64-musl": "4.44.2",
"@rollup/rollup-win32-arm64-msvc": "4.44.2",
"@rollup/rollup-win32-ia32-msvc": "4.44.2",
"@rollup/rollup-win32-x64-msvc": "4.44.2",
"fsevents": "~2.3.2"
}
},
+2 -2
View File
@@ -4,7 +4,7 @@ import { Suspense } from 'react'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { QueryClientProvider } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { useTitle } from 'hoofd'
import { useLang } from 'hoofd'
import { Switch, Route } from 'wouter'
import { I18nProvider } from 'react-aria-components'
import { Layout } from './layout/Layout'
@@ -17,7 +17,7 @@ import { useIsSdkContext } from '@/features/sdk/hooks/useIsSdkContext'
function App() {
const { i18n } = useTranslation()
useTitle(import.meta.env.VITE_APP_TITLE ?? '')
useLang(i18n.language)
const isSDKContext = useIsSdkContext()
@@ -2,25 +2,23 @@ import { A, Text } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { useConfig } from '@/api/useConfig'
const appTitle = import.meta.env.VITE_APP_TITLE ?? 'LaSuite Meet'
export const MoreLink = () => {
const { t } = useTranslation('home')
const { data } = useConfig()
if (!data?.manifest_link) return null
if (!data?.manifest_link) return
return (
<Text as="p" variant="sm" style={{ padding: '1rem 0' }}>
<Text as={'p'} variant={'sm'} style={{ padding: '1rem 0' }}>
<A
href={data?.manifest_link}
target="_blank"
rel="noopener noreferrer"
externalIcon
aria-label={t('moreLinkLabel', { appTitle })}
aria-label={t('moreLinkLabel')}
>
{t('moreLink')} {t('moreAbout', { appTitle })}
</A>
{t('moreLink')}
</A>{' '}
{t('moreAbout', { appTitle: `${import.meta.env.VITE_APP_TITLE}` })}
</Text>
)
}
+14 -1
View File
@@ -258,10 +258,23 @@ export const Home = () => {
</DialogTrigger>
</div>
<Separator />
<MoreLink />
<div
className={css({
display: { base: 'none', lg: 'inline' },
})}
>
<MoreLink />
</div>
</LeftColumn>
<RightColumn>
<IntroSlider />
<div
className={css({
display: { base: 'inline', lg: 'none' },
})}
>
<MoreLink />
</div>
</RightColumn>
</Columns>
<LaterMeetingDialog
@@ -18,15 +18,10 @@ import {
ANIMATION_DURATION,
ReactionPortals,
} from '@/features/rooms/livekit/components/ReactionPortal'
import { layoutStore } from '@/stores/layout'
import { PanelId } from '@/features/rooms/livekit/hooks/useSidePanel'
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
export const MainNotificationToast = () => {
const room = useRoomContext()
const { triggerNotificationSound } = useNotificationSound()
const { t } = useTranslation('notifications')
const announce = useScreenReaderAnnounce()
const [reactions, setReactions] = useState<Reaction[]>([])
const instanceIdRef = useRef(0)
@@ -46,21 +41,12 @@ export const MainNotificationToast = () => {
},
{ timeout: NotificationDuration.MESSAGE }
)
if (layoutStore.activePanelId !== PanelId.CHAT) {
announce(
t('chatMessageReceived', {
name: participant.name || t('defaultName'),
message: chatMessage.message,
}),
'polite'
)
}
}
room.on(RoomEvent.ChatMessage, handleChatMessage)
return () => {
room.off(RoomEvent.ChatMessage, handleChatMessage)
}
}, [room, triggerNotificationSound, announce, t])
}, [room, triggerNotificationSound])
const handleEmoji = (emoji: string, participant: Participant) => {
if (!emoji || !Object.values(Emoji).includes(emoji as Emoji)) return
@@ -79,20 +79,11 @@ export const NoAccessView = ({
})}
>
{t(`${i18nKey}.body`)}
<br />
{helpArticle && (
<>
{' '}
<A
href={helpArticle}
target="_blank"
rel="noopener noreferrer"
externalIcon
color="note"
aria-label={t(`${i18nKey}.linkAriaLabel`)}
>
{t(`${i18nKey}.linkMore`)}
</A>
</>
<A href={helpArticle} target="_blank">
{t(`${i18nKey}.linkMore`)}
</A>
)}
</Text>
</VStack>
@@ -151,16 +151,12 @@ export const ScreenRecordingSidePanel = () => {
</H>
<Text variant="body" fullWidth>
{recordingMaxDuration
? t('body', { max_duration: recordingMaxDuration })
? t('body', {
max_duration: recordingMaxDuration,
})
: t('bodyWithoutMaxDuration')}{' '}
{data?.support?.help_article_recording && (
<A
href={data.support.help_article_recording}
target="_blank"
rel="noopener noreferrer"
externalIcon
aria-label={t('linkAriaLabel')}
>
<A href={data.support.help_article_recording} target="_blank">
{t('linkMore')}
</A>
)}
@@ -187,16 +187,12 @@ export const TranscriptSidePanel = () => {
</H>
<Text variant="body" fullWidth>
{recordingMaxDuration
? t('body', { max_duration: recordingMaxDuration })
? t('body', {
max_duration: recordingMaxDuration,
})
: t('bodyWithoutMaxDuration')}{' '}
{data?.support?.help_article_transcript && (
<A
href={data.support.help_article_transcript}
target="_blank"
rel="noopener noreferrer"
externalIcon
aria-label={t('linkAriaLabel')}
>
<A href={data.support.help_article_transcript} target="_blank">
{t('linkMore')}
</A>
)}
@@ -212,7 +208,6 @@ export const TranscriptSidePanel = () => {
href={data.transcription_destination}
target="_blank"
rel="noopener noreferrer"
externalIcon
>
{data.transcription_destination.replace('https://', '')}
</A>
@@ -72,8 +72,7 @@ export const RecordingDownload = () => {
if (
data.status !== RecordingStatus.Saved &&
data.status !== RecordingStatus.NotificationSucceed &&
data.status !== RecordingStatus.FailedToStop
data.status !== RecordingStatus.NotificationSucceed
) {
return <ErrorScreen title={t('unsaved.title')} body={t('unsaved.body')} />
}
@@ -183,13 +183,7 @@ export const ParticipantTile: (
}}
>
{isHandRaised && !isScreenShare && (
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.1rem',
}}
>
<span>
<span>{positionInQueue}</span>
<RiHand
color="black"
@@ -148,22 +148,17 @@ export const Tools = () => {
className={css({
textStyle: 'sm',
paddingX: '0.75rem',
paddingTop: '0.25rem',
marginBottom: '1rem',
})}
>
{t('body')}{' '}
{data?.support?.help_article_more_tools && (
<A
href={data.support.help_article_more_tools}
target="_blank"
rel="noopener noreferrer"
externalIcon
color="note"
aria-label={t('linkAriaLabel')}
>
{t('moreLink')}
</A>
<>
<A href={data?.support?.help_article_more_tools} target="_blank">
{t('moreLink')}
</A>
.
</>
)}
</Text>
{isTranscriptEnabled && (
@@ -22,7 +22,7 @@ import { useSize } from '@/features/rooms/livekit/hooks/useResizeObserver'
export function useGridLayout(
/** HTML element that contains the grid. */
gridElement: React.RefObject<HTMLDivElement>,
/** Count of tracks that should get laid out */
/** Count of tracks that should get layed out */
trackCount: number,
options: {
gridLayouts?: GridLayoutDefinition[]
@@ -1,28 +0,0 @@
import { useTitle } from 'hoofd'
import { useRoomData } from './useRoomData'
import { useMemo } from 'react'
const APP_TITLE = import.meta.env.VITE_APP_TITLE ?? ''
/**
* Updates the browser tab title with the room name to help users easily find
* the meeting tab among many open tabs. Works on both the join screen and
* once connected.
*/
export const useRoomPageTitle = () => {
const roomData = useRoomData()
const pageTitle = useMemo(() => {
if (!roomData) {
return APP_TITLE
}
const roomLabel = roomData.name || roomData.slug || ''
if (!roomLabel) return APP_TITLE
return `${APP_TITLE} - ${roomLabel} `
}, [roomData])
useTitle(pageTitle)
}
@@ -151,12 +151,7 @@ export function Chat({ ...props }: ChatProps) {
minHeight={0}
overflowY="scroll"
>
<ul
className="lk-list lk-chat-messages"
ref={ulRef}
role="log"
aria-relevant="additions"
>
<ul className="lk-list lk-chat-messages" ref={ulRef}>
{renderedMessages}
</ul>
</Div>
@@ -30,7 +30,6 @@ import { useSidePanel } from '../hooks/useSidePanel'
import { RecordingProvider } from '@/features/recording'
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
import { useConnectionObserver } from '../hooks/useConnectionObserver'
import { useRoomPageTitle } from '../hooks/useRoomPageTitle'
import { useNoiseReduction } from '../hooks/useNoiseReduction'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import { useSettingsDialog } from '@/features/settings'
@@ -114,7 +113,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
)
useConnectionObserver()
useRoomPageTitle()
useVideoResolutionSubscription()
useRegisterKeyboardShortcut({
@@ -4,7 +4,6 @@ import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import { accessibilityStore } from '@/stores/accessibility'
import { CaptionsSettings } from '@/features/subtitle/component/CaptionsSettings'
export type AccessibilityTabProps = Pick<TabPanelProps, 'id'>
@@ -33,7 +32,6 @@ export const AccessibilityTab = ({ id }: AccessibilityTabProps) => {
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
</li>
<CaptionsSettings />
</ul>
</TabPanel>
)
@@ -1,49 +0,0 @@
import { Field, H } from '@/primitives'
import { css } from '@/styled-system/css'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import {
accessibilityStore,
type CaptionTextSize,
CAPTION_TEXT_SIZE_OPTIONS,
} from '@/stores/accessibility'
export const CaptionsSettings = () => {
const { t } = useTranslation('settings', {
keyPrefix: 'accessibility.captions',
})
const snap = useSnapshot(accessibilityStore)
const captionTextSizeItems = useMemo(
() =>
CAPTION_TEXT_SIZE_OPTIONS.map((size) => ({
value: size,
label: t(`textSize.options.${size}`),
})),
[t]
)
return (
<li>
<H
lvl={3}
className={css({
marginBottom: '0.5rem',
})}
>
{t('heading')}
</H>
<Field
type="select"
label={t('textSize.label')}
items={captionTextSizeItems}
selectedKey={snap.captionTextSize}
onSelectionChange={(key) => {
accessibilityStore.captionTextSize = key as CaptionTextSize
}}
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
</li>
)
}
@@ -8,25 +8,6 @@ import { useRoomContext } from '@livekit/components-react'
import { getParticipantColor } from '@/features/rooms/utils/getParticipantColor'
import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
import { Participant, RoomEvent } from 'livekit-client'
import { useSnapshot } from 'valtio'
import {
accessibilityStore,
CAPTION_TEXT_SIZE_OPTIONS,
type CaptionTextSize,
} from '@/stores/accessibility'
const FONT_SIZE_CONFIG: Record<
CaptionTextSize,
{ fontSize: string; lineHeight: string }
> = {
small: { fontSize: '0.875rem', lineHeight: '1.2rem' },
medium: { fontSize: '1.5rem', lineHeight: '1.7rem' },
large: { fontSize: '2.25rem', lineHeight: '2.5rem' },
}
const CAPTION_FONT_SIZES = Object.fromEntries(
CAPTION_TEXT_SIZE_OPTIONS.map((size) => [size, FONT_SIZE_CONFIG[size]])
) as Record<CaptionTextSize, { fontSize: string; lineHeight: string }>
export interface TranscriptionSegment {
id: string
@@ -92,10 +73,8 @@ const useTranscriptionState = () => {
}
const Transcription = ({ row }: { row: TranscriptionRow }) => {
const { captionTextSize } = useSnapshot(accessibilityStore)
const participantColor = getParticipantColor(row.participant)
const participantName = getParticipantName(row.participant)
const { fontSize, lineHeight } = CAPTION_FONT_SIZES[captionTextSize]
const getDisplayText = (row: TranscriptionRow): string => {
return row.segments
@@ -137,9 +116,10 @@ const Transcription = ({ row }: { row: TranscriptionRow }) => {
</Text>
<p
className={css({
fontSize: '1.5rem',
lineHeight: '1.7rem',
fontWeight: '400',
})}
style={{ fontSize, lineHeight }}
>
{displayText}
</p>
@@ -218,6 +198,7 @@ export const Subtitles = () => {
)
}
}
return rows
}, [transcriptionSegments])
+1 -9
View File
@@ -3,7 +3,6 @@ import resourcesToBackend from 'i18next-resources-to-backend'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
const i18nDefaultNamespace = 'global'
const fallbackLng = 'fr'
i18n.setDefaultNamespace(i18nDefaultNamespace)
i18n
@@ -16,7 +15,7 @@ i18n
.use(LanguageDetector)
.init({
supportedLngs: ['en', 'fr', 'nl', 'de'],
fallbackLng,
fallbackLng: 'fr',
ns: i18nDefaultNamespace,
detection: {
order: ['localStorage', 'navigator'],
@@ -25,10 +24,3 @@ i18n
escapeValue: false,
},
})
.then(() => {
document.documentElement.setAttribute('lang', i18n.language || fallbackLng)
})
i18n.on('languageChanged', (lang) => {
document.documentElement.setAttribute('lang', lang)
})
@@ -8,12 +8,12 @@
},
"scope": "Diese Erklärung zur Barrierefreiheit gilt für die Website visio.numerique.gouv.fr",
"complianceStatus": {
"title": "Stand der Gesetzeskonformität",
"title": "Stand der Vereinbarkeit",
"body": "visio.numerique.gouv.fr ist nicht konform mit RGAA 4.1. Die Website wurde noch nicht geprüft. Das Team bemüht sich jedoch, eine für alle zugängliche Seite gemäß den Empfehlungen des RGAA zu erstellen."
},
"improvement": {
"title": "Verbesserung und Kontakt",
"body": "Wenn du auf Inhalte oder Dienste nicht zugreifen können, kannst du die verantwortliche Person von lasuite.numerique.gouv.fr kontaktieren, um eine barrierefreie Alternative zu erhalten oder die Inhalte in einer anderen Form zu bekommen.",
"body": "Wenn Sie auf Inhalte oder Dienste nicht zugreifen können, können Sie die verantwortliche Person von lasuite.numerique.gouv.fr kontaktieren, um eine barrierefreie Alternative zu erhalten oder die Inhalte in einer anderen Form zu bekommen.",
"contact": {
"email": "E-Mail: visio@numerique.gouv.fr",
"address": "Adresse: DINUM, 20 avenue de Ségur, 75007 Paris"
@@ -22,11 +22,11 @@
},
"recourse": {
"title": "Rechtsbehelfe",
"introduction": "Dieses Verfahren ist in folgendem Fall zu verwenden: Du hast der für die Website zuständigen Stelle ein Problem mit der Barrierefreiheit gemeldet, das dich daran hindert, auf Inhalte oder Dienste des Portals zuzugreifen, und keine zufriedenstellende Antwort erhalten.",
"introduction": "Dieses Verfahren ist in folgendem Fall zu verwenden: Sie haben der für die Website zuständigen Stelle ein Problem mit der Barrierefreiheit gemeldet, das Sie daran hindert, auf Inhalte oder Dienste des Portals zuzugreifen, und keine zufriedenstellende Antwort erhalten.",
"options": {
"intro": "Du kannst:",
"option1": "Dem*der Rechtsbeauftragten eine Nachricht schreiben",
"option2": "Den*die Vertreter*in des*der Rechtsbeauftragten in Ihrer Region kontaktieren",
"intro": "Sie können:",
"option1": "Dem*der Beauftragten für Rechte eine Nachricht schreiben",
"option2": "Den*die Vertreter*in des*der Beauftragten für Rechte in Ihrer Region kontaktieren",
"option3": "Einen Brief per Post senden (kostenfrei, ohne Briefmarke): </br> Défenseur des droits, Libre réponse 71120, 75342 Paris CEDEX 07"
}
}
+11 -11
View File
@@ -6,8 +6,8 @@
"heading": "Beim Laden der Seite ist ein Fehler aufgetreten"
},
"feedback": {
"context": "Dein Feedback gestaltet das Produkt!",
"cta": "Teile uns deine Meinung mit neuer Tab"
"context": "Ihr Feedback prägt das Produkt!",
"cta": "Teilen Sie uns Ihre Meinung mit - neues Fenster"
},
"forbidden": {
"heading": "Zugriff verweigert"
@@ -17,13 +17,13 @@
"login": {
"buttonLabel": "Anmelden",
"proconnectButtonLabel": "Mit ProConnect anmelden",
"proconnectLinkLabel": "Was ist ProConnect? neuer Tab",
"proconnectLinkLabel": "Was ist ProConnect? neues Fenster",
"proconnectLink": "Was ist ProConnect?"
},
"logout": "Abmelden",
"notFound": {
"heading": "Überprüfe deinen Meeting-Code",
"body": "Stelle sicher, dass du den richtigen Meeting-Code in der URL eingegeben haben. Beispiel:"
"heading": "Überprüfen Sie Ihren Meeting-Code",
"body": "Stellen Sie sicher, dass Sie den richtigen Meeting-Code in der URL eingegeben haben. Beispiel:"
},
"selected": "ausgewählt",
"submit": "OK",
@@ -38,16 +38,16 @@
"accessibility": "Barrierefreiheit: nicht konform",
"ariaLabel": "neues Fenster",
"codeAnnotation": "Unser Code ist offen und verfügbar in diesem",
"code": "Open-Source-Repository",
"technicalDetails": "Technische Angaben",
"code": "Open-Source-Code-Repository",
"technicalDetails": "Technisches Datenblatt",
"termsOfService": "Nutzungsbedingungen"
},
"mentions": "Sofern nicht anders angegeben, sind die Inhalte dieser Website verfügbar unter der",
"license": "Etalab 2.0 Lizenz"
},
"loginHint": {
"title": "Melde dich mit deinem Konto an",
"body": "Melde dich mit deinem Konto an, statt zu warten.",
"title": "Melden Sie sich mit Ihrem Konto an",
"body": "Statt zu warten, melden Sie sich mit Ihrem ProConnect-Konto an.",
"button": {
"ariaLabel": "Hinweis schließen",
"label": "OK"
@@ -55,7 +55,7 @@
},
"skipLink": "Zum Hauptinhalt springen",
"clipboardContent": {
"url": "Um an der Videokonferenz teilzunehmen, folge diesem Link: {{roomUrl}}",
"numberAndPin": "Um per Telefon teilzunehmen, wähle {{phoneNumber}} und gib diesen Code ein: {{pinCode}}"
"url": "Um an der Videokonferenz teilzunehmen, klicken Sie auf diesen Link: {{roomUrl}}",
"numberAndPin": "Um telefonisch teilzunehmen, wählen Sie {{phoneNumber}} und geben Sie diesen Code ein: {{pinCode}}"
}
}
+16 -16
View File
@@ -1,31 +1,31 @@
{
"createMeeting": "Meeting erstellen",
"heading": "Einfache und sichere Videokonferenzen",
"intro": "Kommuniziere und arbeite mühelos, ohne deine digitale Souveränität zu gefährden",
"joinInputError": "Verwende einen Meeting-Link oder -Code. Beispiele:",
"intro": "Kommunizieren und arbeiten Sie mühelos, ohne Ihre digitale Souveränität zu gefährden",
"joinInputError": "Verwenden Sie einen Meeting-Link oder -Code. Beispiele:",
"joinInputExample": "URL oder 10-stelliger Code",
"joinInputLabel": "Meeting-Link",
"joinInputSubmit": "Meeting beitreten",
"joinMeeting": "Meeting beitreten",
"joinMeetingTipContent": "Du kannst einem Meeting beitreten, indem du den vollständigen Link in die Adressleiste deines Browsers einfügst.",
"joinMeetingTipHeading": "Wusstest du schon?",
"loginToCreateMeeting": "Melde dich an, um ein Meeting zu erstellen",
"moreLinkLabel": "Mehr über {{appTitle}} erfahren neuer Tab",
"joinMeetingTipContent": "Sie können einem Meeting beitreten, indem Sie den vollständigen Link in die Adressleiste Ihres Browsers einfügen.",
"joinMeetingTipHeading": "Wussten Sie schon?",
"loginToCreateMeeting": "Melden Sie sich an, um ein Meeting zu erstellen",
"moreLinkLabel": "Mehr erfahren neues Tab",
"moreLink": "Mehr erfahren",
"moreAbout": "über {{appTitle}}",
"createMenu": {
"laterOption": "Meeting für später planen",
"instantOption": "Meeting sofort starten"
"instantOption": "Sofort-Meeting starten"
},
"laterMeetingDialog": {
"heading": "Zugangsdaten",
"description": "Teile diese Informationen mit den Gästen. Sie können dem Meeting beitreten, ohne sich anmelden zu müssen. Dieses Meeting ist dauerhaft und kann wiederverwendet werden.",
"heading": "Ihre Zugangsdaten",
"description": "Teilen Sie diese Informationen mit den Gästen. Sie können dem Meeting beitreten, ohne sich anmelden zu müssen. Dieses Meeting ist dauerhaft und kann wiederverwendet werden.",
"permissions": "Personen mit diesem Link benötigen keine Genehmigung, um diesem Meeting beizutreten.",
"copy": "Informationen kopieren",
"copied": "Informationen kopiert",
"copyUrl": "Meeting-Link kopieren",
"phone": {
"call": "Ruf an:",
"call": "Rufen Sie an:",
"pinCode": "Code:"
}
},
@@ -45,16 +45,16 @@
"tooltip": "Formular ausfüllen"
},
"slide1": {
"title": "Mach Schluss mit kompliziert probier uns jetzt aus!",
"body": "Entdecke eine intuitive und leicht zugängliche Lösung, entwickelt für alle Mitarbeitenden im öffentlichen Dienst, ihre Partner und viele weitere."
"title": "Wechseln Sie zur Einfachheit. Testen Sie uns jetzt!",
"body": "Entdecken Sie eine intuitive und zugängliche Lösung, entwickelt für alle Mitarbeitenden im öffentlichen Dienst, ihre Partner und viele weitere."
},
"slide2": {
"title": "Gruppenmeetings ohne Einschränkungen",
"body": "Unbegrenzte Meeting-Dauer mit flüssiger Kommunikation in hoher Qualität unabhängig von der Gruppengröße."
"title": "Gruppenanrufe ohne Einschränkungen",
"body": "Unbegrenzte Meeting-Dauer mit flüssiger, hochwertiger Kommunikation unabhängig von der Gruppengröße."
},
"slide3": {
"title": "Verwandele deine Meetings mit KI",
"body": "Erhalte präzise und verwertbare Transkripte zur Steigerung deiner Produktivität. Beta-Funktion jetzt testen!"
"title": "Verwandeln Sie Ihre Meetings mit KI",
"body": "Erhalten Sie präzise und verwertbare Transkripte zur Steigerung Ihrer Produktivität. Funktion in der Beta jetzt testen!"
},
"carouselLabel": "Einführungs-Diashow",
"slidePosition": "Folie {{current}} von {{total}}"
+4 -4
View File
@@ -1,8 +1,8 @@
{
"title": "Rechtliche Hinweise",
"creator": {
"title": "Herausgebende",
"body": "Der Visio-Dienst wird herausgegeben von der Interministeriellen Digitaldirektion Frankreichs (DINUM) mit Sitz:",
"title": "Herausgeber",
"body": "Der Visio-Dienst wird herausgegeben von der Interministeriellen Digitaldirektion des Staates (DINUM) mit Sitz:",
"contact": {
"title": "Kontaktdaten",
"address": "20 avenue de Ségur",
@@ -31,7 +31,7 @@
"title": "Wiederverwendung von Inhalten und Links",
"body1": "Sofern nicht ausdrücklich anders angegeben und geistige Eigentumsrechte Dritter betroffen sind, werden die Inhalte dieser Website unter der ",
"license": "offenen Lizenz Etalab 2.0",
"body2": "bereitgestellt. Insbesondere dürfen diese Inhalte frei vervielfältigt, kopiert, geändert, extrahiert, umgewandelt, verbreitet, weitergeleitet, veröffentlicht, übertragen und genutzt werden unter der Bedingung, dass die Quelle und das Datum der letzten Aktualisierung genannt werden und Dritte nicht durch die Informationen in die Irre geführt werden.",
"body3": "Jede öffentliche oder private Website ist berechtigt, ohne vorherige Genehmigung, Links (einschließlich Deep Links) zu den auf dieser Website veröffentlichten Informationen zu setzen."
"body2": "bereitgestellt. Insbesondere dürfen Sie diese Inhalte frei vervielfältigen, kopieren, ändern, extrahieren, umwandeln, verbreiten, weiterleiten, veröffentlichen, übertragen und nutzen unter der Bedingung, dass die Quelle und das Datum der letzten Aktualisierung genannt werden und Dritte nicht durch die Informationen in die Irre geführt werden.",
"body3": "Jede öffentliche oder private Website ist berechtigt, ohne vorherige Genehmigung, einen Link (einschließlich eines Deep Links) zu den auf dieser Website veröffentlichten Informationen zu setzen."
}
}
+19 -20
View File
@@ -1,5 +1,5 @@
{
"defaultName": "Eine Person",
"defaultName": "Ein Mitwirkender",
"joined": {
"description": "{{name}} ist dem Raum beigetreten"
},
@@ -7,47 +7,46 @@
"description": "{{name}} hat die Hand gehoben.",
"cta": "Warteliste öffnen"
},
"muted": "{{name}} hat dein Mikrofon stummgeschaltet.",
"muted": "{{name}} hat dein Mikrofon stummgeschaltet. Kein Teilnehmer kann dich hören.",
"openChat": "Chat öffnen",
"chatMessageReceived": "{{name}} sagt: {{message}}",
"lowerHand": {
"auto": "Du hast begonnen zu sprechen deine Hand wird daher automatisch gesenkt.",
"auto": "Es scheint, dass du angefangen hast zu sprechen, daher wird deine Hand gesenkt.",
"dismiss": "Hand oben lassen"
},
"reaction": {
"description": "{{name}} hat mit {{emoji}} reagiert"
},
"permissionsRemoved": {
"camera": "Die Moderation hat das Video für alle Teilnehmenden deaktiviert.",
"microphone": "Die Moderation hat das Mikrofon für alle Teilnehmenden deaktiviert.",
"screen_share": "Die Moderation hat die Bildschirmfreigabe für alle Teilnehmenden deaktiviert."
"camera": "Der Organisator hat das Video für alle Teilnehmer deaktiviert.",
"microphone": "Der Organisator hat das Mikrofon für alle Teilnehmer deaktiviert.",
"screen_share": "Der Organisator hat die Bildschirmfreigabe für alle Teilnehmer deaktiviert."
},
"waitingParticipants": {
"one": "Eine Person möchte diesem Meeting beitreten.",
"several": "Mehrere Personen möchten diesem Meeting beitreten.",
"one": "Eine Person möchte diesem Anruf beitreten.",
"several": "Mehrere Personen möchten diesem Anruf beitreten.",
"open": "Öffnen",
"accept": "Hereinlassen"
"accept": "Annehmen"
},
"transcript": {
"started": "{{name}} hat die Meeting-Transkription gestartet.",
"stopped": "{{name}} hat die Meeting-Transkription gestoppt.",
"started": "{{name}} hat die Transkription des Treffens gestartet.",
"stopped": "{{name}} hat die Transkription des Treffens gestoppt.",
"limitReached": "Die Transkription hat die maximal zulässige Dauer überschritten und wird automatisch gespeichert.",
"requested": "{{name}} möchte die Meeting-Transkription starten."
"requested": "{{name}} will die Transkription starten."
},
"screenRecording": {
"started": "{{name}} hat die Meeting-Aufzeichnung gestartet.",
"stopped": "{{name}} hat die Meeting-Aufzeichnung gestoppt.",
"started": "{{name}} hat die Aufzeichnung des Treffens gestartet.",
"stopped": "{{name}} hat die Aufzeichnung des Treffens gestoppt.",
"limitReached": "Die Aufzeichnung hat die maximal zulässige Dauer überschritten und wird automatisch gespeichert.",
"requested": "{{name}} möchte die Meeting-Aufzeichnung starten."
"requested": "{{name}} will die Aufnahme starten."
},
"recordingSave": {
"transcript": {
"message": "Deine Transkription wird gespeichert! Du erhältst eine E-Mail auf <strong>{{email}}</strong>, sobald die Transkription bereit ist.",
"default": "Deine Transkription wird gespeichert! Du erhältst das Transkript per E-Mail, sobald es bereit ist."
"message": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail an <strong>{{email}}</strong>, sobald die Transkription fertig ist.",
"default": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail, sobald die Transkription fertig ist."
},
"screenRecording": {
"message": "Deine Aufzeichnung wird gespeichert! Du erhältst eine E-Mail auf <strong>{{email}}</strong>, sobald sie bereit ist.",
"default": "Deine Aufzeichnung wird gespeichert! Du erhältst eine E-Mail, sobald sie bereit ist."
"message": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail an <strong>{{email}}</strong>, sobald sie fertig ist.",
"default": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail, sobald sie fertig ist."
}
},
"openMenu": "Menü öffnen"
+6 -6
View File
@@ -1,7 +1,7 @@
{
"error": {
"title": "Aufzeichnung nicht verfügbar",
"body": "Die Aufzeichnung ist nicht verfügbar oder wurde möglicherweise gelöscht. Nur die*der Organisator:in der Besprechung hat Zugriff darauf. Wende dich bei Bedarf gerne an die Person."
"body": "Die Aufzeichnung ist nicht verfügbar oder wurde möglicherweise gelöscht. Nur der Organisator der Besprechung hat Zugriff darauf. Wenden Sie sich bei Bedarf gerne an ihn."
},
"expired": {
"title": "Aufzeichnung abgelaufen",
@@ -9,20 +9,20 @@
},
"authentication": {
"title": "Authentifizierung erforderlich",
"body": "Bitte melde dich sich an, um auf diese Aufzeichnung zuzugreifen."
"body": "Bitte melden Sie sich an, um auf diese Aufzeichnung zuzugreifen."
},
"unsaved": {
"title": "Download nicht verfügbar",
"body": "Diese Aufzeichnung ist noch nicht zum Herunterladen bereit. Bitte versuche es später erneut."
"body": "Diese Aufzeichnung ist noch nicht zum Herunterladen bereit. Bitte versuchen Sie es später erneut."
},
"success": {
"title": "Deine Aufzeichnung ist bereit!",
"body": "Aufzeichnung des Meetings {{room}} vom {{created_at}}.",
"title": "Ihre Aufzeichnung ist bereit!",
"body": "Aufzeichnung des Treffens {{room}} vom {{created_at}}.",
"expiration": "Achtung, diese Aufzeichnung wird nach {{expiration_days}} Tag(en) gelöscht.",
"button": "Herunterladen",
"warning": {
"title": "Linkfreigabe deaktiviert",
"body": "Die Freigabe der Aufzeichnung per Link ist noch nicht verfügbar. Nur Organisator:innen können sie herunterladen."
"body": "Die Freigabe der Aufzeichnung per Link ist noch nicht verfügbar. Nur Organisatoren können sie herunterladen."
}
}
}
+117 -124
View File
@@ -1,17 +1,17 @@
{
"feedback": {
"heading": {
"normal": "Du hast das Meeting verlassen",
"duplicateIdentity": "Du bist dem Meeting von einem anderen Gerät aus beigetreten",
"participantRemoved": "Du wurdest von der*dem Organisator:in aus dem Meeting entfernt"
"normal": "Sie haben das Meeting verlassen",
"duplicateIdentity": "Sie haben dem Meeting von einem anderen Gerät aus beigetreten",
"participantRemoved": "Sie wurden von einem Administrator aus dem Anruf entfernt"
},
"home": "Zur Startseite zurückkehren",
"back": "Dem Meeting erneut beitreten"
},
"selectDevice": {
"loading": "Laden…",
"select": "Wähle einen Wert",
"permissionsNeeded": "Berechtigung erforderlich",
"select": "Wählen Sie einen Wert",
"permissionsNeeded": "Genehmigung erforderlich",
"settings": {
"audio": "Audioeinstellungen",
"video": "Videoeinstellungen"
@@ -19,7 +19,7 @@
"effects": "Effekte anwenden",
"videoinput": {
"choose": "Kamera auswählen",
"permissionsNeeded": "Kamera auswählen Berechtigung erforderlich",
"permissionsNeeded": "Kamera auswählen - genehmigung erforderlich",
"disable": "Kamera deaktivieren",
"enable": "Kamera aktivieren",
"turnedOff": "Kamera deaktiviert",
@@ -29,7 +29,7 @@
},
"audioinput": {
"choose": "Mikrofon auswählen",
"permissionsNeeded": "Mikrofon auswählen Berechtigung erforderlich",
"permissionsNeeded": "Mikrofon auswählen - genehmigung erforderlich",
"disable": "Mikrofon deaktivieren",
"enable": "Mikrofon aktivieren",
"turnedOff": "Mikrofon deaktiviert",
@@ -37,30 +37,30 @@
"label": "Mikrofon"
},
"audiooutput": {
"choose": "Audioausgabe auswählen",
"permissionsNeeded": "Audioausgabe auswählen Berechtigung erforderlich"
"choose": "Lautsprecher auswählen",
"permissionsNeeded": "Lautsprecher auswählen - genehmigung erforderlich"
}
},
"join": {
"effects": {
"description": "Effekte anwenden",
"title": "Effekte",
"subTitle": "Konfiguriere die Effekte deiner Kamera."
"subTitle": "Konfigurieren Sie die Effekte Ihrer Kamera."
},
"heading": "Dem Meeting beitreten?",
"joinLabel": "Beitreten",
"joinMeeting": "Meeting beitreten",
"toggleOff": "Klicken, um auszuschalten",
"toggleOn": "Klicken, um einzuschalten",
"usernameHint": "Wird anderen Teilnehmenden angezeigt",
"usernameLabel": "Dein Name",
"usernameHint": "Wird anderen Teilnehmern angezeigt",
"usernameLabel": "Ihr Name",
"errors": {
"usernameEmpty": "Dein Name darf nicht leer sein"
"usernameEmpty": "Ihr Name darf nicht leer sein"
},
"cameraDisabled": "Kamera ist deaktiviert.",
"cameraStarting": "Kamera wird gestartet…",
"cameraNotGranted": "Möchtest du, dass andere dich während des Meetings sehen können?",
"cameraAndMicNotGranted": "Möchtest du, dass andere dich während des Meetings sehen und hören können?",
"cameraNotGranted": "Möchten Sie, dass andere Sie während der Besprechung sehen können?",
"cameraAndMicNotGranted": "Möchten Sie, dass andere Sie während der Besprechung sehen und hören können?",
"permissionsButton": {
"cameraAndMicNotGranted": "Zugriff auf Kamera und Mikrofon erlauben",
"cameraNotGranted": "Zugriff auf Kamera erlauben"
@@ -71,28 +71,28 @@
},
"waiting": {
"title": "Beitrittsanfrage wird gesendet...",
"body": "Du kannst diesem Meeting beitreten, sobald deine Anfrage angenommen wird"
"body": "Sie können diesem Anruf beitreten, sobald jemand Sie autorisiert"
},
"denied": {
"title": "Du kannst diesem Meeting nicht beitreten",
"body": "Deine Beitrittsanfrage wurde abgelehnt."
"title": "Sie können diesem Anruf nicht beitreten",
"body": "Ihre Beitrittsanfrage wurde abgelehnt."
},
"timeoutInvite": {
"title": "Du kannst diesem Meeting nicht beitreten",
"body": "Niemand hat auf deine Anfrage reagiert"
"title": "Sie können diesem Anruf nicht beitreten",
"body": "Niemand hat auf Ihre Anfrage reagiert"
}
},
"leaveRoomPrompt": "Hiermit verlässt du das Meeting.",
"leaveRoomPrompt": "Dadurch verlassen Sie das Meeting.",
"shareDialog": {
"copy": "Infos kopieren",
"copy": "Informationen kopieren",
"copyUrl": "Link kopieren",
"copied": "Infos kopiert",
"heading": "Dein Meeting ist bereit",
"description": "Teile diesen Link mit Personen, die du zum Meeting einladen möchten.",
"copied": "Informationen kopiert",
"heading": "Ihr Meeting ist bereit",
"description": "Teilen Sie diesen Link mit Personen, die Sie zum Meeting einladen möchten.",
"permissions": "Personen mit diesem Link benötigen keine Erlaubnis, um diesem Meeting beizutreten.",
"closeDialog": "Dialogfenster schließen",
"phone": {
"call": "Ruf an:",
"call": "Rufen Sie an:",
"pinCode": "Code:"
}
},
@@ -108,62 +108,62 @@
"videoinput": "Kamera konnte nicht aktiviert werden"
},
"body": {
"audioinput": "Dein Mikrofon wird bereits in einem anderen Tab oder von einer anderen App verwendet. Dies verhindert die Aktivierung in diesem Meeting. Wenn du dein Mikrofon hier verwenden möchtest, schließe den anderen Tab oder die App.",
"videoinput": "Deine Kamera wird bereits in einem anderen Tab oder von einer anderen App verwendet. Dies verhindert die Aktivierung in diesem Meeting. Wenn du deine Kamera hier verwenden möchtest, schließe den anderen Tab oder die App."
"audioinput": "Ihr Mikrofon wird bereits in einem anderen Tab oder von einer anderen Anwendung verwendet, was die Aktivierung in diesem Videoanruf verhindert. Wenn Sie möchten, schließen Sie den anderen Tab oder die Anwendung, um es hier zu verwenden.",
"videoinput": "Ihre Kamera wird bereits in einem anderen Tab oder von einer anderen Anwendung verwendet, was die Aktivierung in diesem Videoanruf verhindert. Wenn Sie möchten, schließen Sie den anderen Tab oder die Anwendung, um sie hier zu verwenden."
}
},
"close": "OK"
},
"permissionErrorDialog": {
"heading": {
"camera": "{{appTitle}} hat keine Berechtigung, um deine Kamera zu verwenden",
"microphone": "{{appTitle}} hat keine Berechtigung, um dein Mikrofon zu verwenden",
"cameraAndMicrophone": "{{appTitle}} hat keine Berechtigung, um dein Mikrofon und deine Kamera zu verwenden",
"camera": "{{appTitle}} darf Ihre Kamera nicht verwenden",
"microphone": "{{appTitle}} darf Ihr Mikrofon nicht verwenden",
"cameraAndMicrophone": "{{appTitle}} darf weder Ihr Mikrofon noch Ihre Kamera verwenden",
"default": "{{appTitle}} hat keine Berechtigung für bestimmte Zugriffe"
},
"body": {
"openMenu": {
"others": "Klicke das Einstellungs-Symbol ICON_PLACEHOLDER in der Adressleiste deines Browsers",
"safari": "Klicke auf das Menü 'Safari' und öffne 'Einstellungen für {{appDomain}}'."
"others": "Klicken Sie auf das Einstellungen-Symbol ICON_PLACEHOLDER in der Adressleiste Ihres Browsers",
"safari": "Klicken Sie auf das Menü 'Safari' und öffnen Sie 'Einstellungen für {{appDomain}}'."
},
"details": {
"camera": "Zugriff auf die Kamera erlauben",
"microphone": "Zugriff auf das Mikrofon erlauben",
"cameraAndMicrophone": "Zugriff auf Kamera und Mikrofon erlauben",
"default": "Erlaube die erforderlichen Berechtigungen."
"default": "Aktivieren Sie die erforderlichen Berechtigungen."
}
}
},
"permissionsButton": {
"tooltip": "Mehr Infos",
"ariaLabel": "Problem mit Berechtigungen. Mehr Infos anzeigen"
"ariaLabel": "Berechtigungsproblem. Mehr Infos anzeigen"
},
"error": {
"createRoom": {
"heading": "Authentifizierung erforderlich",
"body": "Dieser Raum wurde noch nicht erstellt. Bitte authentifiziere dich, um ihn zu erstellen, oder warte, bis eine authentifizierte Person dies tut."
"body": "Dieser Raum wurde noch nicht erstellt. Bitte authentifizieren Sie sich, um ihn zu erstellen, oder warten Sie, bis ein authentifizierter Benutzer dies tut."
},
"screenShare": {
"title": "Bildschirmfreigabe nicht möglich",
"ariaLabel": "Bildschirmfreigabe nicht möglich",
"message": "Deinem Browser fehlt möglicherweise die Berechtigung, den Bildschirm deines Geräts abzugreifen.",
"macInstructions": "Gehe zu den",
"message": "Ihr Browser darf möglicherweise den Bildschirm Ihres Computers nicht aufzeichnen.",
"macInstructions": "Gehen Sie zu Ihren",
"macSystemPreferences": "Systemeinstellungen",
"helpLinkText": "Weitere Informationen findest du unter",
"helpLinkLabel": "Bildschirmfreigabeproblem",
"helpLinkText": "Weitere Informationen finden Sie unter",
"helpLinkLabel": "Präsentationsproblem",
"closeButton": "Schließen",
"newTab": "Neuer Tab"
"newTab": "Neues Fenster"
}
},
"isIdleDisconnectModal": {
"title": "Bist du noch da?",
"body": "Du bist die*der einzige Teilnehmende. Diese Meeting endet in {{duration}}. Möchtest du das Meeting fortsetzen?",
"body": "Du bist der einzige Teilnehmer. Dieses Gespräch endet in {{duration}}. Möchtest du das Gespräch fortsetzen?",
"settingsPrefix": "Um diese Nachricht nicht mehr zu sehen, gehe zu",
"settingsLink": "Einstellungen > Allgemein",
"settingsSuffix": ".",
"stayButton": "Meeting fortsetzen",
"stayButton": "Gespräch fortsetzen",
"leaveButton": "Jetzt verlassen",
"countdownAnnouncement": "Das Meeting endet in {{duration}}."
"countdownAnnouncement": "Das Gespräch endet in {{duration}}."
},
"controls": {
"microphone": "Mikrofon",
@@ -201,8 +201,8 @@
"participants": {
"open": "Alle ausblenden",
"closed": "Alle anzeigen",
"count_one": "{{count}} Teilnehmende",
"count_other": "{{count}} Teilnehmende"
"count_one": "{{count}} Teilnehmer",
"count_other": "{{count}} Teilnehmer"
},
"tools": {
"open": "Weitere Tools ausblenden",
@@ -217,16 +217,16 @@
"button": "Reaktion senden",
"send": "Reaktion {{emoji}} senden",
"announce": "{{name}} : {{emoji}}",
"you": "du",
"you": "Sie",
"emojis": {
"thumbs-up": "Daumen hoch",
"thumbs-down": "Daumen runter",
"clapping-hands": "Klatschende Hände",
"red-heart": "Rotes Herz",
"red-heart": "rotes Herz",
"face-with-tears-of-joy": "Gesicht mit Freudentränen",
"face-with-open-mouth": "Überraschter Gesichtsausdruck",
"party-popper": "Konfettikanone",
"folded-hands": "Gefaltete Hände"
"face-with-open-mouth": "überraschter Gesichtsausdruck",
"party-popper": "Party-Popper",
"folded-hands": "gefaltete Hände"
}
}
},
@@ -237,7 +237,7 @@
"settings": "Einstellungen",
"screenRecording": "Aufzeichnung",
"transcript": "Transkription",
"username": "Deinen Namen aktualisieren",
"username": "Ihren Namen aktualisieren",
"effects": "Effekte anwenden",
"switchCamera": "Kamera wechseln",
"fullscreen": {
@@ -248,9 +248,9 @@
}
},
"effects": {
"activateCamera": "Deine Kamera ist deaktiviert. Wähle eine Option, um sie zu aktivieren.",
"cameraDisabled": "Deine Kamera ist deaktiviert.",
"notAvailable": "Videoeffekte werden bald in deinem Browser verfügbar sein. Wir arbeiten daran! In der Zwischenzeit kannst du Google Chrome für die beste Leistung oder Firefox verwenden :(",
"activateCamera": "Ihre Kamera ist deaktiviert. Wählen Sie eine Option, um sie zu aktivieren.",
"cameraDisabled": "Ihre Kamera ist deaktiviert.",
"notAvailable": "Videoeffekte werden bald in Ihrem Browser verfügbar sein. Wir arbeiten daran! In der Zwischenzeit können Sie Google Chrome für die beste Leistung oder Firefox verwenden :(",
"heading": "Unschärfe",
"clear": "Effekt deaktivieren",
"blur": {
@@ -300,8 +300,8 @@
"ariaLabel": "Seitenleiste",
"backToTools": "Zurück zu den Meeting-Tools",
"heading": {
"participants": "Teilnehmende",
"effects": "Hintergrund & Effekte",
"participants": "Teilnehmer",
"effects": "Effekte",
"chat": "Nachrichten im Chat",
"transcript": "Transkribieren",
"screenRecording": "Aufzeichnen",
@@ -310,8 +310,8 @@
"info": "Meeting-Informationen"
},
"content": {
"participants": "Teilnehmende",
"effects": "Hintergrund & Effekte",
"participants": "Teilnehmer",
"effects": "Effekte",
"chat": "Nachrichten",
"transcript": "transkribieren",
"screenRecording": "aufzeichnen",
@@ -322,13 +322,11 @@
"closeButton": "{{content}} ausblenden"
},
"chat": {
"disclaimer": "Die Nachrichten sind nur für Teilnehmende zum Zeitpunkt des Sendens sichtbar. Alle Nachrichten werden am Ende des Meetings gelöscht.",
"messagesLog": "Chat-Nachrichten"
"disclaimer": "Die Nachrichten sind nur für Teilnehmer zum Zeitpunkt des Sendens sichtbar. Alle Nachrichten werden am Ende des Anrufs gelöscht."
},
"moreTools": {
"body": "Nutze weitere Tools, um deine Meetings zu verbessern.",
"linkAriaLabel": "Dokumentation zu den Tools öffnen öffnet in neuem Tab",
"moreLink": "Dokumentation öffnen",
"body": "Greifen Sie auf weitere Tools zu, um Ihre Meetings zu verbessern.",
"moreLink": "Mehr erfahren",
"tools": {
"transcript": {
"title": "Transkribieren",
@@ -344,77 +342,73 @@
"roomInformation": {
"title": "Verbindungsinformationen",
"button": {
"ariaLabel": "Kopiere die Informationen aus dem Meeting",
"ariaLabel": "Kopieren Sie die Informationen aus Ihrer Besprechung",
"copy": "Informationen kopieren",
"copied": "Informationen kopiert"
},
"phone": {
"call": "Ruf an:",
"call": "Rufen Sie an:",
"pinCode": "Code:"
}
}
},
"transcript": {
"heading": "Transkribiere dein Meeting mithilfe von KI",
"body": "Transkribiere bis zu {{max_duration}} Meetingdauer.",
"bodyWithoutMaxDuration": "Transkribieren dein Meeting ohne Zeitbegrenzung.",
"heading": "Transkribieren Sie Ihr Meeting mit dem KI-Assistenten",
"body": "Transkribieren Sie bis zu {{max_duration}} Meetingdauer.",
"bodyWithoutMaxDuration": "Transkribieren Sie Ihr Meeting ohne Begrenzung.",
"details": {
"receiver": "Das Transkript wird an die*den Organisator:in und die Mitorganisierenden gesendet.",
"receiver": "Das Transkript wird an den Organisator und die Mitorganisatoren gesendet.",
"destination": "Ein neues Dokument wird erstellt auf",
"destinationUnknown": "Ein neues Dokument wird erstellt",
"language": "Meeting-Sprache:",
"recording": "Auch eine Aufzeichnung starten"
},
"button": {
"start": "Meeting-Transkription starten",
"stop": "Meeting-Transkription beenden",
"start": "Meeting transkribieren starten",
"stop": "Meeting transkribieren beenden",
"saving": "Speichern…",
"starting": "Wird gestartet…",
"anotherModeStarted": "Eine Aufnahme läuft. <br/> Wechsele das Menü und stoppe sie."
"anotherModeStarted": "Eine Aufnahme läuft. <br/> Wechseln Sie das Menü und stoppen Sie sie."
},
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zur Transkription öffnen öffnet in neuem Tab",
"linkMore": "Mehr erfahren",
"notAdminOrOwner": {
"heading": "Zugriff eingeschränkt",
"body": "Aus Sicherheitsgründen kann nur die*der Ersteller:in bzw. Admin des Meetings eine Transkription (Beta) starten.",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zum Transkriptionszugang öffnen öffnet in neuem Tab",
"body": "Aus Sicherheitsgründen kann nur der Ersteller oder ein Administrator des Meetings eine Transkription (Beta) starten.",
"linkMore": "Mehr erfahren",
"dividerLabel": "ODER",
"login": {
"heading": "Anmeldung erforderlich",
"body": "Nur die*der Ersteller:in bzw. Admin kann die Transkription starten. Melde dich an, um deine Berechtigungen zu überprüfen."
"body": "Nur der Ersteller des Meetings oder ein Administrator kann die Transkription starten. Melden Sie sich an, um Ihre Berechtigungen zu überprüfen."
},
"request": {
"heading": "Transkription anfragen",
"body": "Die Moderation wird benachrichtigt und kann die Transkription starten.",
"buttonLabel": "Anfragen"
"heading": "Anfrage an Moderator",
"body": "Der Moderator wird benachrichtigt und kann die Transkription starten.",
"buttonLabel": "Anfordern"
}
},
"premium": {
"heading": "Premium-Funktion",
"body": "Diese Funktion ist öffentlichen Bediensteten vorbehalten. Wenn deine E-Mail-Adresse nicht autorisiert ist, kontaktiere bitte den Support, um Zugriff zu erhalten.",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zum Premium-Zugang öffnen öffnet in neuem Tab",
"body": "Diese Funktion ist öffentlichen Bediensteten vorbehalten. Wenn Ihre E-Mail-Adresse nicht autorisiert ist, kontaktieren Sie bitte den Support, um Zugriff zu erhalten.",
"linkMore": "Mehr erfahren",
"dividerLabel": "ODER",
"login": {
"heading": "Anmeldung erforderlich",
"body": "Nur die*der Ersteller:in bzw. Admin kann die Transkription starten. Melde dich an, um deine Berechtigungen zu überprüfen."
"body": "Nur der Ersteller des Meetings oder ein Administrator kann die Transkription starten. Melden Sie sich an, um Ihre Berechtigungen zu überprüfen."
},
"request": {
"heading": "Transkription anfragen",
"body": "Die Moderation wird benachrichtigt und kann die Transkription starten.",
"heading": "Anfrage an Moderator",
"body": "Der Moderator wird benachrichtigt und kann die Transkription starten.",
"buttonLabel": "Anfordern"
}
}
},
"screenRecording": {
"heading": "Dieses Meeting für später aufzeichnen",
"body": "Zeichne bis zu {{max_duration}} des Meetings auf.",
"bodyWithoutMaxDuration": "Zeichne dein Meeting ohne Zeitbegrenzung auf.",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zur Aufzeichnung öffnen öffnet in neuem Tab",
"heading": "Diesen Anruf für später aufzeichnen",
"body": "Zeichnen Sie bis zu {{max_duration}} des Meetings auf.",
"bodyWithoutMaxDuration": "Zeichnen Sie Ihr Meeting unbegrenzt auf.",
"linkMore": "Mehr erfahren",
"details": {
"receiver": "Die Aufzeichnung wird an die*den Organisator:in und die Mitorganisierenden gesendet.",
"receiver": "Die Aufzeichnung wird an den Organisator und die Mitorganisatoren gesendet.",
"destination": "Diese Aufzeichnung wird vorübergehend auf unseren Servern gespeichert",
"loading": "Aufzeichnung wird gestartet",
"linkMore": "Mehr erfahren",
@@ -425,21 +419,20 @@
"stop": "Meeting-Aufzeichnung beenden",
"saving": "Wird gespeichert…",
"starting": "Wird gestartet…",
"anotherModeStarted": "Eine Transkription läuft. <br/> Wechsele das Menü und stoppen sie."
"anotherModeStarted": "Eine Transkription läuft. <br/> Wechseln Sie das Menü und stoppen Sie sie."
},
"notAdminOrOwner": {
"heading": "Zugriff eingeschränkt",
"body": "Aus Sicherheitsgründen kann nur die*der Ersteller:in bzw. Admin des Meetings eine Videoaufnahme (Beta) starten.",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zum Aufzeichnungszugang öffnen öffnet in neuem Tab",
"body": "Aus Sicherheitsgründen kann nur der Ersteller oder ein Administrator des Meetings eine Videoaufnahme (Beta) starten.",
"linkMore": "Mehr erfahren",
"dividerLabel": "ODER",
"login": {
"heading": "Anmeldung erforderlich",
"body": "Nur die*der Ersteller:in bzw. Admin kann die Aufzeichnung starten. Melde dich an, um deine Berechtigungen zu überprüfen."
"body": "Nur der Ersteller der Besprechung oder ein Administrator kann die Aufzeichnung starten. Melden Sie sich an, um Ihre Berechtigungen zu überprüfen."
},
"request": {
"heading": "Aufnahme anfragen",
"body": "Die Moderation wird benachrichtigt und kann die Aufnahme starten.",
"heading": "Aufnahme anfordern",
"body": "Der Moderator wird benachrichtigt und kann die Aufnahme für Sie starten.",
"buttonLabel": "Anfrage senden"
}
},
@@ -454,10 +447,10 @@
"button": "OK"
},
"admin": {
"description": "Diese Einstellungen für Organisierende ermöglichen dir die Kontrolle über dein Meeting. Nur Organisierende haben Zugriff auf diese Optionen.",
"description": "Diese Einstellungen für Organisatoren ermöglichen dir die Kontrolle über dein Meeting. Nur Organisatoren haben Zugriff auf diese Optionen.",
"access": {
"title": "Raumzugang",
"description": "Diese Einstellungen gelten auch für zukünftige Meetings in diesem Raum.",
"description": "Diese Einstellungen gelten auch für zukünftige Ausgaben dieses Meetings.",
"type": "Zugangsarten für Meetings",
"levels": {
"public": {
@@ -475,28 +468,28 @@
}
},
"moderation": {
"title": "Meeting-Moderation",
"description": "Diese Einstellungen beschränken die Aktionen, die Teilnehmende während des Meetings durchführen können.",
"title": "Moderation des Meetings",
"description": "Diese Einstellungen beschränken die Aktionen, die Mitwirkende während des Meetings durchführen können.",
"microphone": {
"label": "Mikrofon einschalten",
"description": ""
},
"camera": {
"label": "Video einschalten",
"label": "Video aktivieren",
"description": ""
},
"screenshare": {
"label": "Bildschirm teilen",
"description": "Wenn du diese Option deaktivierst, können Teilnehmende ihren Bildschirm nicht mehr teilen. Laufende Bildschirmfreigaben werden sofort beendet."
"label": "Ihren Bildschirm teilen",
"description": "Wenn Sie diese Option deaktivieren, können Teilnehmer ihren Bildschirm nicht mehr teilen, und laufende Bildschirmfreigaben werden sofort beendet."
}
}
},
"rating": {
"submit": "Absenden",
"question": "Wie bewertest du die Qualität dieses Meetings?",
"question": "Wie bewertest du die Qualität deines Gesprächs?",
"levels": {
"min": "sehr schlecht",
"max": "hervorragend"
"max": "ausgezeichnet"
}
},
"openFeedback": {
@@ -506,7 +499,7 @@
"skip": "Überspringen"
},
"confirmationMessage": {
"heading": "Vielen Dank für dein Feedback",
"heading": "Vielen Dank für deine Rückmeldung",
"body": "Unser Produktteam nimmt sich Zeit, dein Feedback sorgfältig zu prüfen. Wir melden uns so bald wie möglich bei dir."
},
"authenticationMessage": {
@@ -518,9 +511,9 @@
"participants": {
"subheading": "Im Raum",
"you": "Du",
"unknown": "Unbekannte Person",
"unknown": "Unbekannter Teilnehmer",
"host": "Host",
"contributors": "Teilnehmende",
"contributors": "Mitwirkende",
"collapsable": {
"open": "{{name}}-Liste öffnen",
"close": "{{name}}-Liste schließen"
@@ -529,7 +522,7 @@
"muteParticipant": "{{name}}s Mikrofon ausschalten",
"muteParticipantAlert": {
"heading": "{{name}} stummschalten",
"description": "{{name}} für alle Teilnehmenden stummschalten? {{name}} kann sich nur selbst wieder einschalten.",
"description": "{{name}} für alle Teilnehmer stummschalten? {{name}} kann sich nur selbst wieder einschalten.",
"confirm": "Stummschalten",
"cancel": "Abbrechen"
},
@@ -554,8 +547,8 @@
},
"participantMenu": {
"remove": {
"label": "Vom Meeting ausschließen",
"ariaLabel": "{{name}} vom Meeting ausschließen"
"label": "Vom Anruf ausschließen",
"ariaLabel": "{{name}} vom Anruf ausschließen"
},
"unpin": {
"label": "Lösen",
@@ -589,8 +582,8 @@
"started": "Aufzeichnung läuft"
},
"limitReachedAlert": {
"title": "Aufzeichnungslimit überschritten",
"description": "Die Aufzeichnung hat die zulässige Höchstdauer{{duration_message}} überschritten. Sie wird jetzt automatisch gespeichert.",
"title": "Aufnahmelimit überschritten",
"description": "Die Aufnahme hat die zulässige Höchstdauer{{duration_message}} überschritten. Sie wird jetzt automatisch gespeichert.",
"durationMessage": " von {{duration}}",
"button": "OK"
}
@@ -602,12 +595,12 @@
"enable": "Anheften",
"disable": "Lösen"
},
"effects": "Hintergründe und Effekte anwenden",
"effects": "Visuelle Effekte anwenden",
"muteParticipant": "{{name}} stummschalten",
"fullScreen": "Vollbild"
},
"shortcutsPanel": {
"title": "Tastenkürzel",
"title": "Tastenkombinationen",
"categories": {
"navigation": "Navigation",
"media": "Medien",
@@ -618,13 +611,13 @@
"focus-toolbar": "Fokus auf die untere Symbolleiste",
"toggle-microphone": "Mikrofon umschalten",
"toggle-camera": "Kamera umschalten",
"push-to-talk": "Push-to-talk",
"push-to-talk": "Push-to-talk (gedrückt halten zum Einschalten)",
"reaction": "Reaktionspanel",
"fullscreen": "Vollbild umschalten",
"recording": "Aufnahmepanel umschalten",
"raise-hand": "Hand heben oder senken",
"toggle-chat": "Chat anzeigen/ausblenden",
"toggle-participants": "Teilnehmende anzeigen/ausblenden",
"toggle-participants": "Teilnehmer anzeigen/ausblenden",
"open-shortcuts-settings": "Tastenkürzel-Einstellungen öffnen"
},
"sr": {
@@ -642,8 +635,8 @@
}
},
"fullScreenWarning": {
"message": "Damit dein Bildschirm nicht unendlich gespiegelt wird, teile nicht deinen gesamten Bildschirm. Teile stattdessen einen Tab oder ein anderes Fenster.",
"stop": "Teilen beenden",
"message": "Um eine Endlosschleife zu vermeiden, teile nicht deinen gesamten Bildschirm. Teile stattdessen einen Tab oder ein anderes Fenster.",
"stop": "Präsentation beenden",
"ignore": "Ignorieren"
},
"participantTile": {
+2 -2
View File
@@ -4,7 +4,7 @@
"joinButton": "Mit Visio beitreten",
"copyLinkTooltip": "Link kopieren",
"resetLabel": "Zurücksetzen",
"participantLimit": "Bis zu 150 Teilnehmende.",
"popupBlocked": "Popup wurde blockiert. Bitte erlaube Popups für diese Website."
"participantLimit": "Bis zu 150 Teilnehmer.",
"popupBlocked": "Popup wurde blockiert. Bitte erlauben Sie Popups für diese Website."
}
}
+21 -32
View File
@@ -1,46 +1,46 @@
{
"account": {
"currentlyLoggedAs": "Du bist derzeit angemeldet als <0>{{user}}</0>",
"currentlyLoggedAs": "Sie sind derzeit angemeldet als <0>{{user}}</0>",
"heading": "Konto",
"youAreNotLoggedIn": "Du bist nicht angemeldet.",
"nameLabel": "Dein Name",
"youAreNotLoggedIn": "Sie sind nicht angemeldet.",
"nameLabel": "Ihr Name",
"authentication": "Authentifizierung",
"nameError": "Dein Name darf nicht leer sein"
"nameError": "Ihr Name darf nicht leer sein"
},
"preferences": {
"title": "Einstellungen",
"idleDisconnectModal": {
"label": "Meetings ohne Teilnehmende verlassen",
"description": "Meetings nach einigen Minuten automatisch verlassen, wenn keine andere Person beitritt"
"label": "Anrufe ohne Teilnehmer verlassen",
"description": "Verlässt automatisch einen Anruf nach einigen Minuten, wenn kein anderer Teilnehmer beitritt"
}
},
"audio": {
"microphone": {
"heading": "Mikrofon",
"label": "Audioeingabe wählen",
"label": "Wählen Sie Ihre Audioeingabe",
"disabled": "Mikrofon deaktiviert"
},
"noiseReduction": {
"label": "Geräuschunterdrückung",
"heading": "Geräuschunterdrückung",
"label": "Rauschunterdrückung",
"heading": "Rauschunterdrückung",
"ariaLabel": {
"enable": "Geräuschunterdrückung aktivieren",
"disable": "Geräuschunterdrückung deaktivieren"
"enable": "Rauschunterdrückung aktivieren",
"disable": "Rauschunterdrückung deaktivieren"
}
},
"speakers": {
"heading": "Lautsprecher",
"label": "Audioausgabe wählen",
"label": "Wählen Sie Ihre Audioausgabe",
"test": "Testen",
"ongoingTest": "Soundtest läuft…",
"safariWarning": "Die Audioausgabeauswahl ist auf Safari aufgrund von Browser-Einschränkungen noch nicht verfügbar."
"safariWarning": "Die Lautsprecherauswahl ist auf Safari aufgrund von Browser-Einschränkungen noch nicht verfügbar."
},
"permissionsRequired": "Berechtigungen erforderlich"
},
"video": {
"camera": {
"heading": "Kamera",
"label": "Videoeingabe wählen",
"label": "Wählen Sie Ihre Videoeingabe aus",
"disabled": "Kamera deaktiviert",
"previewAriaLabel": {
"enabled": "Videovorschau aktiviert",
@@ -50,18 +50,18 @@
"resolution": {
"heading": "Auflösung",
"publish": {
"label": "Wähle die maximale Auflösung beim Senden",
"label": "Wählen Sie Ihre maximale Sendeauflösung (max.)",
"items": {
"high": "Hohe Auflösung",
"medium": "Mittlere Auflösung",
"medium": "Standardauflösung",
"low": "Niedrige Auflösung"
}
},
"subscribe": {
"label": "Wähle die maximale Auflösung beim Empfangen",
"label": "Wählen Sie Ihre Empfangsauflösung (max.)",
"items": {
"high": "Hohe Auflösung (automatisch)",
"medium": "Mittlere Auflösung",
"medium": "Standardauflösung",
"low": "Niedrige Auflösung"
}
}
@@ -82,15 +82,15 @@
}
},
"notifications": {
"heading": "Benachrichtigungen mit Ton",
"label": "Benachrichtigungen mit Ton für",
"heading": "Tonbenachrichtigungen",
"label": "Tonbenachrichtigungen für",
"actions": {
"disable": "Deaktivieren",
"enable": "Aktivieren"
},
"items": {
"participantJoined": {
"label": "Person beigetreten"
"label": "Teilnehmer beigetreten"
},
"handRaised": {
"label": "Hand gehoben"
@@ -116,17 +116,6 @@
"accessibility": {
"announceReactions": {
"label": "Reaktionen vorlesen"
},
"captions": {
"heading": "Untertitel",
"textSize": {
"label": "Untertitel-Schriftgröße",
"options": {
"small": "Klein",
"medium": "Mittel",
"large": "Groß"
}
}
}
},
"tabs": {
+1 -1
View File
@@ -47,7 +47,7 @@
},
"loginHint": {
"title": "Log in with your account",
"body": "Instead of waiting, log in with your account.",
"body": "Instead of waiting, log in with your ProConnect account.",
"button": {
"ariaLabel": "Close the suggestion",
"label": "OK"
+1 -1
View File
@@ -10,7 +10,7 @@
"joinMeetingTipContent": "You can join a meeting by pasting its full link in the browser's address bar.",
"joinMeetingTipHeading": "Did you know?",
"loginToCreateMeeting": "Login to create a meeting",
"moreLinkLabel": "Learn more about {{appTitle}} - new tab",
"moreLinkLabel": "Learn more - new tab",
"moreLink": "Learn more",
"moreAbout": "about {{appTitle}}",
"createMenu": {
@@ -9,7 +9,6 @@
},
"muted": "{{name}} has muted your microphone. No participant can hear you.",
"openChat": "Open chat",
"chatMessageReceived": "{{name}} says: {{message}}",
"lowerHand": {
"auto": "It seems you have started speaking, so your hand will be lowered.",
"dismiss": "Keep hand raised"
+7 -14
View File
@@ -322,13 +322,11 @@
"closeButton": "Hide {{content}}"
},
"chat": {
"disclaimer": "The messages are visible to participants only at the time they are sent. All messages are deleted at the end of the call.",
"messagesLog": "Chat messages"
"disclaimer": "The messages are visible to participants only at the time they are sent. All messages are deleted at the end of the call."
},
"moreTools": {
"body": "Access more tools to enhance your meetings.",
"linkAriaLabel": "Open documentation about tools - opens in new window",
"moreLink": "Open documentation",
"moreLink": "Learn more",
"tools": {
"transcript": {
"title": "Transcribe",
@@ -372,13 +370,11 @@
"starting": "Starting…",
"anotherModeStarted": "Screen recording is running. Switch panels and stop it."
},
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about transcription - opens in new window",
"linkMore": "Learn more",
"notAdminOrOwner": {
"heading": "Restricted Access",
"body": "For security reasons, only the meeting creator or an admin can start a transcription (beta).",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about transcription access - opens in new window",
"linkMore": "Learn more",
"dividerLabel": "OR",
"login": {
"heading": "Login Required",
@@ -393,8 +389,7 @@
"premium": {
"heading": "Premium feature",
"body": "This feature is reserved for public agents. If your email address is not authorized, please contact support to get access.",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about premium access - opens in new window",
"linkMore": "Learn more",
"dividerLabel": "OR",
"login": {
"heading": "You are not logged in!",
@@ -411,8 +406,7 @@
"heading": "Record this call for later",
"body": "Record up to {{max_duration}} of meeting.",
"bodyWithoutMaxDuration": "Record your meeting without limit.",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about recording - opens in new window",
"linkMore": "Learn more",
"details": {
"receiver": "The recording will be sent to the host and co-hosts.",
"destination": "This recording will be temporarily stored on our servers",
@@ -430,8 +424,7 @@
"notAdminOrOwner": {
"heading": "Restricted Access",
"body": "For security reasons, only the meeting creator or an admin can start a recording (beta).",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about recording access - opens in new window",
"linkMore": "Learn more",
"dividerLabel": "OR",
"login": {
"heading": "Login Required",
-11
View File
@@ -116,17 +116,6 @@
"accessibility": {
"announceReactions": {
"label": "Announce reactions aloud"
},
"captions": {
"heading": "Captions",
"textSize": {
"label": "Caption text size",
"options": {
"small": "Small",
"medium": "Medium",
"large": "Large"
}
}
}
},
"tabs": {
+1 -1
View File
@@ -47,7 +47,7 @@
},
"loginHint": {
"title": "Connectez-vous avec votre compte",
"body": "Au lieu de patienter, connectez-vous avec votre compte.",
"body": "Au lieu de patienter, connectez-vous avec votre compte ProConnect.",
"button": {
"ariaLabel": "Fermer la suggestion",
"label": "OK"
+1 -1
View File
@@ -10,7 +10,7 @@
"joinMeetingTipContent": "Vous pouvez rejoindre une réunion en copiant directement son lien complet dans la barre d'adresse du navigateur.",
"joinMeetingTipHeading": "Astuce",
"loginToCreateMeeting": "Connectez-vous pour créer une réunion",
"moreLinkLabel": "En savoir plus sur {{appTitle}} - nouvelle fenêtre",
"moreLinkLabel": "En savoir plus - nouvelle fenêtre",
"moreLink": "En savoir plus",
"moreAbout": "sur {{appTitle}}",
"createMenu": {
@@ -9,7 +9,6 @@
},
"muted": "{{name}} a coupé votre micro. Aucun participant ne peut l'entendre.",
"openChat": "Ouvrir le chat",
"chatMessageReceived": "{{name}} dit : {{message}}",
"lowerHand": {
"auto": "Il semblerait que vous ayez pris la parole, donc la main va être baissée.",
"dismiss": "Laisser la main levée"
+9 -16
View File
@@ -322,13 +322,11 @@
"closeButton": "Masquer {{content}}"
},
"chat": {
"disclaimer": "Les messages sont visibles par les participants uniquement au moment de\nleur envoi. Tous les messages sont supprimés à la fin de l'appel.",
"messagesLog": "Messages du chat"
"disclaimer": "Les messages sont visibles par les participants uniquement au moment de\nleur envoi. Tous les messages sont supprimés à la fin de l'appel."
},
"moreTools": {
"body": "Accéder à davantage d'outils pour améliorer vos réunions.",
"linkAriaLabel": "Ouvrir la documentation sur les outils - ouvre dans une nouvelle fenêtre",
"moreLink": "Ouvrir la documentation",
"body": "Accèder à d'avantage d'outils pour améliorer vos réunions.",
"moreLink": "En savoir plus",
"tools": {
"transcript": {
"title": "Transcrire",
@@ -372,13 +370,11 @@
"starting": "Démarrage…",
"anotherModeStarted": "Un enregistrement est en cours. <br/> Changez de menu et arrêtez le."
},
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur la transcription - ouvre dans une nouvelle fenêtre",
"linkMore": "En savoir plus",
"notAdminOrOwner": {
"heading": "Accès restreint",
"body": "Pour des raisons de sécurité, seul le créateur ou un administrateur de la réunion peut lancer une transcription (beta).",
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur l'accès à la transcription - ouvre dans une nouvelle fenêtre",
"linkMore": "En savoir plus",
"dividerLabel": "OU",
"login": {
"heading": "Connexion requise",
@@ -393,7 +389,7 @@
"premium": {
"heading": "Fonctionnalité premium",
"body": "Cette fonctionnalité est réservée aux agents publics. Si votre adresse email nest pas autorisée, contactez le support pour obtenir l'accès.",
"linkMore": "Ouvrir la documentation",
"linkMore": "En savoir plus",
"dividerLabel": "OU",
"login": {
"heading": "Vous n'êtes pas connecté !",
@@ -403,16 +399,14 @@
"heading": "Demander à l'organisateur",
"body": "L'hôte recevra une notification et pourra démarrer la transcription pour vous.",
"buttonLabel": "Demander"
},
"linkAriaLabel": "Ouvrir la documentation sur l'accès premium - ouvre dans une nouvelle fenêtre"
}
}
},
"screenRecording": {
"heading": "Enregistrez cet appel pour plus tard",
"body": "Enregistrez jusqu'à {{max_duration}} de réunion.",
"bodyWithoutMaxDuration": "Enregistrez votre réunion sans limite.",
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur l'enregistrement - ouvre dans une nouvelle fenêtre",
"linkMore": "En savoir plus",
"details": {
"receiver": "L'enregistrement sera envoyé à l'organisateur et aux coorganisateurs.",
"destination": "Cet enregistrement sera conservé temporairement sur nos serveurs",
@@ -430,8 +424,7 @@
"notAdminOrOwner": {
"heading": "Accès restreint",
"body": "Pour des raisons de sécurité, seul le créateur ou un administrateur de la réunion peut lancer un enregistrement (beta).",
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur l'accès à l'enregistrement - ouvre dans une nouvelle fenêtre",
"linkMore": "En savoir plus",
"dividerLabel": "OU",
"login": {
"heading": "Connexion requise",
-11
View File
@@ -116,17 +116,6 @@
"accessibility": {
"announceReactions": {
"label": "Vocaliser les réactions"
},
"captions": {
"heading": "Sous-titres",
"textSize": {
"label": "Taille du texte des sous-titres",
"options": {
"small": "Petit",
"medium": "Moyen",
"large": "Grand"
}
}
}
},
"tabs": {
+1 -1
View File
@@ -46,7 +46,7 @@
},
"loginHint": {
"title": "Log in met je account",
"body": "In plaats van te wachten, log in met je account.",
"body": "In plaats van te wachten, log in met je ProConnect-account.",
"button": {
"ariaLabel": "Sluit de suggestie",
"label": "OK"
+1 -1
View File
@@ -10,7 +10,7 @@
"joinMeetingTipContent": "U kunt deelnemen aan een vergadering door de volledige link in de adresbalk van de browser te plakken.",
"joinMeetingTipHeading": "Wist u dat?",
"loginToCreateMeeting": "Log in om een vergadering te maken",
"moreLinkLabel": "Meer informatie over {{appTitle}} - nieuw tabblad",
"moreLinkLabel": "Meer informatie - nieuw tabblad",
"moreLink": "Meer informatie",
"moreAbout": "over {{appTitle}}",
"createMenu": {
@@ -9,7 +9,6 @@
},
"muted": "{{name}} heeft uw microfoon gedempt. Deelnemers kunnen u niet horen.",
"openChat": "Open chat",
"chatMessageReceived": "{{name}} zegt: {{message}}",
"lowerHand": {
"auto": "Het lijkt erop dat u bent begonnen te spreken, dus we laten uw hand zakken.",
"dismiss": "Houdt uw hand opgestoken"
+7 -14
View File
@@ -322,13 +322,11 @@
"closeButton": "Verberg {{content}}"
},
"chat": {
"disclaimer": "De berichten zijn alleen voor de deelnemers zichtbaar op het moment dat ze worden verzonden. Alle berichten worden verwijderd aan het einde van het gesprek.",
"messagesLog": "Chatberichten"
"disclaimer": "De berichten zijn alleen voor de deelnemers zichtbaar op het moment dat ze worden verzonden. Alle berichten worden verwijderd aan het einde van het gesprek."
},
"moreTools": {
"body": "Je krijgt toegang tot meer tools om je vergaderingen te verbeteren.",
"linkAriaLabel": "Documentatie over tools openen - opent in nieuw venster",
"moreLink": "Documentatie openen",
"moreLink": "Lees meer",
"tools": {
"transcript": {
"title": "Transcriberen",
@@ -372,13 +370,11 @@
"starting": "Wordt gestart…",
"anotherModeStarted": "Er loopt een opname. <br/> Ga naar het menu en stop deze."
},
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over transcriptie openen - opent in nieuw venster",
"linkMore": "Meer informatie",
"notAdminOrOwner": {
"heading": "Toegang beperkt",
"body": "Om veiligheidsredenen kan alleen de maker of een beheerder van de vergadering een transcriptie starten (beta).",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over transcriptietoegang openen - opent in nieuw venster",
"linkMore": "Meer informatie",
"dividerLabel": "OF",
"login": {
"heading": "Inloggen vereist",
@@ -393,8 +389,7 @@
"premium": {
"heading": "Premiumfunctie",
"body": "Deze functie is voorbehouden aan openbare medewerkers. Als uw e-mailadres niet is toegestaan, neem dan contact op met de support om toegang te krijgen.",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over premiumtoegang openen - opent in nieuw venster",
"linkMore": "Meer informatie",
"dividerLabel": "OF",
"login": {
"heading": "Inloggen vereist",
@@ -411,8 +406,7 @@
"heading": "Neem dit gesprek op voor later",
"body": "Neem tot {{max_duration}} van de vergadering op.",
"bodyWithoutMaxDuration": "Neem je vergadering onbeperkt op.",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over opname openen - opent in nieuw venster",
"linkMore": "Meer informatie",
"details": {
"receiver": "De opname wordt verzonden naar de organisator en co-organisatoren.",
"destination": "Deze opname wordt tijdelijk op onze servers bewaard",
@@ -430,8 +424,7 @@
"notAdminOrOwner": {
"heading": "Toegang beperkt",
"body": "Om veiligheidsredenen kan alleen de maker of een beheerder van de vergadering een opname starten (beta).",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over opnametoegang openen - opent in nieuw venster",
"linkMore": "Meer informatie",
"dividerLabel": "OF",
"login": {
"heading": "Inloggen vereist",
-17
View File
@@ -113,29 +113,12 @@
"label": "Taal"
},
"settingsButtonLabel": "Instellingen",
"accessibility": {
"announceReactions": {
"label": "Reacties hardop aankondigen"
},
"captions": {
"heading": "Ondertitels",
"textSize": {
"label": "Ondertiteltekstgrootte",
"options": {
"small": "Klein",
"medium": "Gemiddeld",
"large": "Groot"
}
}
}
},
"tabs": {
"account": "Profiel",
"audio": "Audio",
"video": "Video",
"general": "Algemeen",
"notifications": "Meldingen",
"accessibility": "Toegankelijkheid",
"transcription": "Transcriptie",
"shortcuts": "Sneltoetsen"
}
-15
View File
@@ -2,22 +2,12 @@ import { proxy, subscribe } from 'valtio'
import { STORAGE_KEYS } from '@/utils/storageKeys'
import { deserializeToProxyMap } from '@/utils/valtio'
export type CaptionTextSize = 'small' | 'medium' | 'large'
export const CAPTION_TEXT_SIZE_OPTIONS: CaptionTextSize[] = [
'small',
'medium',
'large',
]
type AccessibilityState = {
announceReactions: boolean
captionTextSize: CaptionTextSize
}
const DEFAULT_STATE: AccessibilityState = {
announceReactions: false,
captionTextSize: 'medium',
}
function getAccessibilityState(): AccessibilityState {
@@ -25,10 +15,6 @@ function getAccessibilityState(): AccessibilityState {
const stored = localStorage.getItem(STORAGE_KEYS.ACCESSIBILITY)
if (stored) {
const parsed = JSON.parse(stored)
const validCaptionSizes = CAPTION_TEXT_SIZE_OPTIONS
const captionTextSize = validCaptionSizes.includes(parsed.captionTextSize)
? parsed.captionTextSize
: DEFAULT_STATE.captionTextSize
return {
...DEFAULT_STATE,
...parsed,
@@ -36,7 +22,6 @@ function getAccessibilityState(): AccessibilityState {
typeof parsed.announceReactions === 'boolean'
? parsed.announceReactions
: DEFAULT_STATE.announceReactions,
captionTextSize,
}
}
@@ -71,7 +71,7 @@ backend:
SUMMARY_SERVICE_API_TOKEN: password
RECORDING_DOWNLOAD_BASE_URL: https://meet.127.0.0.1.nip.io/recording
ROOM_TELEPHONY_ENABLED: True
SSL_CERT_FILE: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
SSL_CERT_FILE: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
migrate:
@@ -100,7 +100,7 @@ backend:
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
extraVolumeMounts:
- name: certs
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
subPath: cacert.pem
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
@@ -64,7 +64,6 @@ backend:
AWS_S3_SECRET_ACCESS_KEY: password
AWS_STORAGE_BUCKET_NAME: meet-media-storage
AWS_S3_REGION_NAME: local
AWS_S3_SIGNATURE_VERSION: s3v4
MEDIA_BASE_URL: https://meet.127.0.0.1.nip.io
RECORDING_ENABLE: True
RECORDING_STORAGE_EVENT_ENABLE: True
@@ -75,16 +74,12 @@ backend:
ROOM_TELEPHONY_ENABLED: True
ROOM_TELEPHONY_DEFAULT_COUNTRY: 'FR'
ROOM_TELEPHONY_PHONE_NUMBER: '+33901020304'
SSL_CERT_FILE: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
ROOM_SUBTITLE_ENABLED: True
EXTERNAL_API_ENABLED: True
APPLICATION_JWT_AUDIENCE: https://meet.127.0.0.1.nip.io/external-api/v1.0/
APPLICATION_JWT_SECRET_KEY: devKeyApplication
APPLICATION_BASE_URL: https://meet.127.0.0.1.nip.io
FILE_UPLOAD_ENABLED: True
AWS_S3_DOMAIN_REPLACE: https://minio.127.0.0.1.nip.io
CELERY_ENABLED: True
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
migrate:
@@ -113,7 +108,7 @@ backend:
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
extraVolumeMounts:
- name: certs
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
subPath: cacert.pem
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
@@ -316,18 +311,3 @@ ingressMedia:
serviceMedia:
host: minio.meet.svc.cluster.local
port: 9000
ingressMediaFiles:
enabled: true
host: meet.127.0.0.1.nip.io
annotations:
nginx.ingress.kubernetes.io/auth-url: https://meet.127.0.0.1.nip.io/api/v1.0/files/media-auth/
nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256"
nginx.ingress.kubernetes.io/upstream-vhost: minio.meet.svc.cluster.local:9000
nginx.ingress.kubernetes.io/rewrite-target: /meet-media-storage/files/$1
serviceMediaFiles:
host: minio.meet.svc.cluster.local
port: 9000
+2 -2
View File
@@ -94,7 +94,7 @@ backend:
key: BREVO_API_KEY
BREVO_API_CONTACT_LIST_IDS: 8
ROOM_TELEPHONY_ENABLED: True
SSL_CERT_FILE: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
migrate:
@@ -123,7 +123,7 @@ backend:
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
extraVolumeMounts:
- name: certs
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
subPath: cacert.pem
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
+4 -6
View File
@@ -3,8 +3,6 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 10m
spec:
rules:
- host: "minio.127.0.0.1.nip.io"
@@ -16,7 +14,7 @@ spec:
service:
name: minio
port:
number: 9000
number: 9001
tls:
- hosts:
- minio.127.0.0.1.nip.io
@@ -112,7 +110,7 @@ spec:
/usr/bin/mc mb meet/meet-media-storage && \
exit 0
restartPolicy: Never
backoffLimit: 3
backoffLimit: 1
---
apiVersion: batch/v1
kind: Job
@@ -132,7 +130,7 @@ spec:
/usr/bin/mc admin config set meet notify_webhook:meet-webhook endpoint="https://meet.127.0.0.1.nip.io/api/v1.0/recordings/storage-hook/" auth_token="Bearer password" && \
/usr/bin/mc admin service restart meet --wait --json && \
sleep 15 && \
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put --prefix "recordings" && \
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put && \
exit 0
restartPolicy: Never
backoffLimit: 3
backoffLimit: 1
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.17
version: 0.0.16
-9
View File
@@ -175,15 +175,6 @@ Requires top level scope
{{ include "meet.fullname" . }}-summary
{{- end }}
{{/*
Full name for the Celery Backend
Requires top level scope
*/}}
{{- define "meet.celeryBackend.fullname" -}}
{{ include "meet.fullname" . }}-celery-backend
{{- end }}
{{/*
Full name for the Celery Transcribe
@@ -1,153 +0,0 @@
{{- $envVars := include "meet.env.transformDict" (mergeOverwrite (default dict .Values.backend.envVars) (default dict .Values.celeryBackend.envVars)) -}}
{{- $fullName := include "meet.celeryBackend.fullname" . -}}
{{- $component := "celery-backend" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $fullName }}
annotations:
{{- with .Values.celeryBackend.dpAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
spec:
replicas: {{ .Values.celeryBackend.replicas }}
selector:
matchLabels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.celeryBackend.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 8 }}
spec:
{{- if $.Values.image.credentials }}
imagePullSecrets:
- name: {{ include "meet.secret.dockerconfigjson.name" (dict "fullname" (include "meet.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end }}
shareProcessNamespace: {{ .Values.celeryBackend.shareProcessNamespace }}
containers:
{{- with .Values.celeryBackend.sidecars }}
{{- toYaml . | nindent 8 }}
{{- end }}
- name: {{ .Chart.Name }}
image: "{{ (.Values.backend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.backend.image | default dict).tag | default .Values.image.tag }}"
imagePullPolicy: {{ (.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
{{- with .Values.celeryBackend.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.celeryBackend.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
env:
{{- if $envVars }}
{{- $envVars | indent 12 }}
{{- end }}
{{- with .Values.celeryBackend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.celeryBackend.service.targetPort }}
protocol: TCP
{{- if .Values.celeryBackend.probes.liveness }}
livenessProbe:
{{- include "meet.probes.abstract" (merge .Values.celeryBackend.probes.liveness (dict "targetPort" .Values.celeryBackend.service.targetPort )) | nindent 12 }}
{{- end }}
{{- if .Values.celeryBackend.probes.readiness }}
readinessProbe:
{{- include "meet.probes.abstract" (merge .Values.celeryBackend.probes.readiness (dict "targetPort" .Values.celeryBackend.service.targetPort )) | nindent 12 }}
{{- end }}
{{- if .Values.celeryBackend.probes.startup }}
startupProbe:
{{- include "meet.probes.abstract" (merge .Values.celeryBackend.probes.startup (dict "targetPort" .Values.celeryBackend.service.targetPort )) | nindent 12 }}
{{- end }}
{{- with .Values.celeryBackend.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
subPath: content
{{- end }}
{{- range $name, $volume := .Values.celeryBackend.persistence }}
- name: "{{ $name }}"
mountPath: "{{ $volume.mountPath }}"
{{- end }}
{{- range .Values.celeryBackend.extraVolumeMounts }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
subPath: {{ .subPath | default "" }}
readOnly: {{ .readOnly }}
{{- end }}
{{- with .Values.celeryBackend.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.celeryBackend.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.celeryBackend.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
name: "{{ include "meet.fullname" $ }}-files-{{ $index }}"
{{- end }}
{{- range $name, $volume := .Values.celeryBackend.persistence }}
- name: "{{ $name }}"
{{- if eq $volume.type "emptyDir" }}
emptyDir: {}
{{- else }}
persistentVolumeClaim:
claimName: "{{ $fullName }}-{{ $name }}"
{{- end }}
{{- end }}
{{- range .Values.celeryBackend.extraVolumes }}
- name: {{ .name }}
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
{{- else if .csi }}
csi:
{{- toYaml .csi | nindent 12 }}
{{- else if .configMap }}
configMap:
{{- toYaml .configMap | nindent 12 }}
{{- else if .emptyDir }}
emptyDir:
{{- toYaml .emptyDir | nindent 12 }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.celeryBackend.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
@@ -1,83 +0,0 @@
{{- if .Values.ingressMediaFiles.enabled -}}
{{- $fullName := include "meet.fullname" . -}}
{{- if and .Values.ingressMediaFiles.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingressMediaFiles.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingressMediaFiles.annotations "kubernetes.io/ingress.class" .Values.ingressMediaFiles.className }}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}-media-files
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "meet.labels" . | nindent 4 }}
{{- with .Values.ingressMediaFiles.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingressMediaFiles.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingressMediaFiles.className }}
{{- end }}
{{- if .Values.ingressMediaFiles.tls.enabled }}
tls:
{{- if .Values.ingressMediaFiles.host }}
- secretName: {{ .Values.ingressMediaFiles.tls.secretName | default (printf "%s-tls" $fullName) | quote }}
hosts:
- {{ .Values.ingressMediaFiles.host | quote }}
{{- end }}
{{- range .Values.ingressMediaFiles.tls.additional }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- if .Values.ingressMediaFiles.host }}
- host: {{ .Values.ingressMediaFiles.host | quote }}
http:
paths:
- path: {{ .Values.ingressMediaFiles.path | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: ImplementationSpecific
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}-media-files
port:
number: {{ .Values.serviceMediaFiles.port }}
{{- else }}
serviceName: {{ $fullName }}-media-files
servicePort: {{ .Values.serviceMediaFiles.port }}
{{- end }}
{{- end }}
{{- range .Values.ingressMediaFiles.hosts }}
- host: {{ . | quote }}
http:
paths:
- path: {{ $.Values.ingressMediaFiles.path | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: ImplementationSpecific
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}-media-files
port:
number: {{ .Values.serviceMediaFiles.port }}
{{- else }}
serviceName: {{ $fullName }}-media-files
servicePort: {{ .Values.serviceMediaFiles.port }}
{{- end }}
{{- end }}
{{- end }}

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