Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine 68ce56c1c8 🔒️(frontend) prevent disconnected users from accessing recording tools
Block recording and transcript features when user isn't connected to prevent
database state corruption. Users were previously able to trigger these
actions despite being disconnected.
2025-05-03 19:15:14 +02:00
72 changed files with 3517 additions and 4058 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ clean_old_images('localhost:5001/meet-summary')
# This is necessary because we need to inject the certificate into our LiveKit container
local_resource(
'copy-root-ca',
cmd='cp -f "$(mkcert -CAROOT)/rootCA.pem" ../docker/livekit/rootCA.pem',
cmd='cp "$(mkcert -CAROOT)/rootCA.pem" ../docker/livekit/rootCA.pem',
deps=[], # No dependencies needed
)
# Build a custom LiveKit Docker image that includes our root CA certificate
+6 -17
View File
@@ -2,7 +2,7 @@
This document is a step-by-step guide that describes how to install Visio on a k8s cluster without AI features.
## Prerequisites for a kubernetes setup
## Prerequisites
- k8s cluster with an nginx-ingress controller
- an OIDC provider (if you don't have one, we will provide an example)
@@ -12,25 +12,14 @@ This document is a step-by-step guide that describes how to install Visio on a k
### Test cluster
If you do not have a kubernetes test cluster, you can install everything on a local kind cluster. In this case, the simplest way is to use our script located in this repo under **bin/start-kind.sh**.
If you do not have a test cluster, you can install everything on a local kind cluster. In this case, the simplest way is to use our script **bin/start-kind.sh**.
IMPORTANT: The kind method will only deploy meet as a local instance(127.0.0.1) that can only be accessed from the device where it has been deployed.
To be able to use the script, you will need to install the following components:
To be able to use the script, you will need to install:
- Docker (https://docs.docker.com/desktop/)
- Kind (https://kind.sigs.k8s.io/docs/user/quick-start/#installation)
- Mkcert (https://github.com/FiloSottile/mkcert#installation)
- Helm (https://helm.sh/docs/intro/quickstart/#install-helm)
- kubectl (https://kubernetes.io/docs/tasks/tools/)
In order to initiate the local kind installation via **start-kind.sh** do the following:
1) Make sure administrator/root user context is able to execute mkcert, docker, kind etc. commands or the script might fail
2) Download the script to the device where the above components are installed
3) Make the script executable
4) Run the script with proper permissions (administrator/sudo etc.)
The output of the script will resemble the below example:
```
$ ./bin/start-kind.sh
@@ -110,7 +99,7 @@ When your k8s cluster is ready, you can start the deployment. This cluster is sp
Please remember that \*.127.0.0.1.nip.io will always resolve to 127.0.0.1, except in the k8s cluster where we configure CoreDNS to answer with the ingress-nginx service IP.
## Preparation of components
## Preparation
### What will you use to authenticate your users ?
@@ -122,7 +111,7 @@ 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/ 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.
If you have already run the script, you can skip this step and proceed to the next instruction.
```
$ kubectl config set-context --current --namespace=meet
@@ -243,7 +232,7 @@ meet <none> meet.127.0.0.1.nip.io localhost 80, 44
meet-admin <none> meet.127.0.0.1.nip.io localhost 80, 443 52m
```
You can use Visio on https://meet.127.0.0.1.nip.io from the local device. The provisioning user in keycloak is meet/meet.
You can use Visio on https://meet.127.0.0.1.nip.io. The provisioning user in keycloak is meet/meet.
## All options
-3
View File
@@ -54,6 +54,3 @@ ALLOW_UNREGISTERED_ROOMS=False
# Recording
SCREEN_RECORDING_BASE_URL=http://localhost:3000/recordings
# Telephony
ROOM_TELEPHONY_ENABLED=True
+1 -1
View File
@@ -93,6 +93,6 @@ class OIDCAuthenticationBackend(LaSuiteOIDCAuthenticationBackend):
pass
except User.MultipleObjectsReturned as e:
raise SuspiciousOperation(
"Multiple user accounts share a common email."
_("Multiple user accounts share a common email.")
) from e
return None
@@ -1,45 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-13 08:22
import secrets
from django.db import migrations, models
def generate_pin_for_rooms(apps, schema_editor):
"""Generate unique 10-digit PIN codes for existing rooms.
The PIN code is required for SIP telephony features.
"""
Room = apps.get_model('core', 'Room')
rooms_without_pin_code = Room.objects.filter(pin_code__isnull=True)
existing_pins = set(Room.objects.values_list('pin_code', flat=True))
length = 10
def generate_pin_code():
while True:
pin_code = str(secrets.randbelow(10 ** length)).zfill(length)
if not pin_code in existing_pins:
return pin_code
for room in rooms_without_pin_code:
room.pin_code = generate_pin_code()
room.save()
class Migration(migrations.Migration):
dependencies = [
('core', '0013_alter_user_language'),
]
operations = [
migrations.AddField(
model_name='room',
name='pin_code',
field=models.CharField(blank=True, help_text='Unique n-digit code that identifies this room in telephony mode.', max_length=None, null=True, unique=True, verbose_name='Room PIN code'),
),
migrations.RunPython(
generate_pin_for_rooms,
reverse_code=migrations.RunPython.noop
),
]
+2 -44
View File
@@ -2,7 +2,6 @@
Declare and configure the models for the Meet core application
"""
import secrets
import uuid
from datetime import datetime, timedelta
from logging import getLogger
@@ -15,6 +14,7 @@ from django.core import mail, validators
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import models
from django.utils import timezone
from django.utils.functional import lazy
from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _
@@ -164,7 +164,7 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
)
language = models.CharField(
max_length=10,
choices=settings.LANGUAGES,
choices=lazy(lambda: settings.LANGUAGES, tuple)(),
default=settings.LANGUAGE_CODE,
verbose_name=_("language"),
help_text=_("The language in which the user wants to see the interface."),
@@ -383,14 +383,6 @@ class Room(Resource):
verbose_name=_("Visio room configuration"),
help_text=_("Values for Visio parameters to configure the room."),
)
pin_code = models.CharField(
max_length=None,
unique=True,
blank=True,
null=True,
verbose_name=_("Room PIN code"),
help_text=_("Unique n-digit code that identifies this room in telephony mode."),
)
class Meta:
db_table = "meet_room"
@@ -401,14 +393,6 @@ class Room(Resource):
def __str__(self):
return capfirst(self.name)
def save(self, *args, **kwargs):
"""Generate a unique n-digit pin code for new rooms."""
if settings.ROOM_TELEPHONY_ENABLED and not self.pk and not self.pin_code:
self.pin_code = self.generate_unique_pin_code(
length=settings.ROOM_TELEPHONY_PIN_LENGTH
)
super().save(*args, **kwargs)
def clean_fields(self, exclude=None):
"""
Automatically generate the slug from the name and make sure it does not look like a UUID.
@@ -423,7 +407,6 @@ class Room(Resource):
pass
else:
raise ValidationError({"name": f'Room name "{self.name:s}" is reserved.'})
super().clean_fields(exclude=exclude)
@property
@@ -431,31 +414,6 @@ class Room(Resource):
"""Check if a room is public"""
return self.access_level == RoomAccessLevel.PUBLIC
@staticmethod
def generate_unique_pin_code(length):
"""Generate a unique n-digit PIN code"""
if length < 4:
raise ValueError(
"PIN code length must be at least 4 digits for minimal security"
)
max_value = 10**length
for _ in range(settings.ROOM_TELEPHONY_PIN_MAX_RETRIES):
pin_code = str(secrets.randbelow(max_value)).zfill(length)
if not Room.objects.filter(pin_code=pin_code).exists():
return pin_code
# Log a warning as a temporary measure until backend observability is implemented.
logger.warning(
"Failed to generate unique PIN code of length %s after %s attempts",
length,
settings.ROOM_TELEPHONY_PIN_MAX_RETRIES,
)
return None
class BaseAccessManager(models.Manager):
"""Base manager for handling resource access control."""
@@ -55,7 +55,7 @@ class StorageEventAuthentication(BaseAuthentication):
if not required_token:
if settings.RECORDING_ENABLE_STORAGE_EVENT_AUTH:
raise AuthenticationFailed(
"Authentication is enabled but token is not configured."
_("Authentication is enabled but token is not configured.")
)
return MachineUser(), None
@@ -67,7 +67,7 @@ class StorageEventAuthentication(BaseAuthentication):
"Authentication failed: Missing Authorization header (ip: %s)",
request.META.get("REMOTE_ADDR"),
)
raise AuthenticationFailed("Authorization header is required")
raise AuthenticationFailed(_("Authorization header is required"))
auth_parts = auth_header.split(" ")
if len(auth_parts) != 2 or auth_parts[0] != self.TOKEN_TYPE:
@@ -75,7 +75,7 @@ class StorageEventAuthentication(BaseAuthentication):
"Authentication failed: Invalid authorization header (ip: %s)",
request.META.get("REMOTE_ADDR"),
)
raise AuthenticationFailed("Invalid authorization header.")
raise AuthenticationFailed(_("Invalid authorization header."))
token = auth_parts[1]
@@ -85,7 +85,7 @@ class StorageEventAuthentication(BaseAuthentication):
"Authentication failed: Invalid token (ip: %s)",
request.META.get("REMOTE_ADDR"),
)
raise AuthenticationFailed("Invalid token")
raise AuthenticationFailed(_("Invalid token"))
return MachineUser(), token
-172
View File
@@ -2,12 +2,6 @@
Unit tests for the Room model
"""
# pylint: disable=W0613
import secrets
from logging import Logger
from unittest import mock
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ValidationError
@@ -181,169 +175,3 @@ def test_models_rooms_is_public_property():
# Test non-public room
private_room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
assert private_room.is_public is False
@mock.patch.object(Room, "generate_unique_pin_code")
def test_telephony_disabled_skips_pin_generation(
mock_generate_unique_pin_code, settings
):
"""Telephony disabled should not generate pin codes."""
settings.ROOM_TELEPHONY_ENABLED = False
room = RoomFactory()
mock_generate_unique_pin_code.assert_not_called()
assert room.pin_code is None
def test_default_and_custom_pin_length(settings):
"""Pin codes should be created with correct configured length."""
settings.ROOM_TELEPHONY_ENABLED = True
room = RoomFactory()
# Assert default value is 10 for collision reasons
assert len(room.pin_code) == 10
settings.ROOM_TELEPHONY_PIN_LENGTH = 5
room = RoomFactory()
# Assert custom size
assert len(room.pin_code) == 5
def test_room_updates_preserve_pin_code(settings):
"""Room updates should preserve existing pin code."""
settings.ROOM_TELEPHONY_ENABLED = True
room = RoomFactory()
# Store the original pin code to compare after updates
previous_pin_code = room.pin_code
# If this method is called, it would indicate the pin is being regenerated unnecessarily
with mock.patch.object(
Room, "generate_unique_pin_code"
) as mock_generate_unique_pin_code:
# Explicitly call save to persist the changes to the room
room.slug = "aaa-aaaa-aaa"
room.save()
assert room.pin_code == previous_pin_code
mock_generate_unique_pin_code.assert_not_called()
@pytest.mark.parametrize("is_telephony_enabled", [True, False])
def test_manual_pin_code_updates(is_telephony_enabled, settings):
"""Manual pin code changes should persist regardless of telephony setting."""
settings.ROOM_TELEPHONY_ENABLED = is_telephony_enabled
settings.ROOM_TELEPHONY_PIN_LENGTH = 5
room = RoomFactory()
assert room.pin_code != "12345"
room.pin_code = "12345"
room.save()
assert room.pin_code == "12345"
# No data validation when manual updates are made
room.pin_code = "123"
room.save()
assert room.pin_code == "123"
def test_pin_code_uniqueness(settings):
"""Duplicate pin codes should raise validation error."""
settings.ROOM_TELEPHONY_ENABLED = True
settings.ROOM_TELEPHONY_PIN_LENGTH = 5
RoomFactory(pin_code="12345")
with pytest.raises(ValidationError) as excinfo:
RoomFactory(pin_code="12345")
assert "Room with this Room PIN code already exists." in str(excinfo.value)
@pytest.mark.parametrize("invalid_length", [0, 1, 2, 3])
def test_pin_code_minimum_length(invalid_length, settings):
"""Pin codes should enforce minimum length for security."""
settings.ROOM_TELEPHONY_ENABLED = True
settings.ROOM_TELEPHONY_PIN_LENGTH = 4
# Assert no exception is raised with a valid length
RoomFactory()
settings.ROOM_TELEPHONY_PIN_LENGTH = invalid_length
with pytest.raises(ValueError) as excinfo:
RoomFactory()
assert "PIN code length must be at least 4 digits for minimal security" in str(
excinfo.value
)
@mock.patch.object(Logger, "warning")
@mock.patch.object(secrets, "randbelow", return_value=12345)
def test_pin_generation_max_retries(mock_randbelow, mock_logger, settings):
"""Pin generation should give up after max retries."""
settings.ROOM_TELEPHONY_ENABLED = True
settings.ROOM_TELEPHONY_PIN_LENGTH = 5
RoomFactory(pin_code="12345")
# Assert default max retries is low, 3
room1 = RoomFactory()
assert mock_randbelow.call_count == 3
assert room1.pin_code is None
mock_logger.assert_called_once_with(
"Failed to generate unique PIN code of length %s after %s attempts", 5, 3
)
mock_logger.reset_mock()
mock_randbelow.reset_mock()
settings.ROOM_TELEPHONY_PIN_MAX_RETRIES = 5
room2 = RoomFactory()
assert mock_randbelow.call_count == 5
assert room2.pin_code is None
mock_logger.assert_called_once_with(
"Failed to generate unique PIN code of length %s after %s attempts", 5, 5
)
@mock.patch.object(secrets, "randbelow", return_value=12345)
def test_pin_code_zero_padding(mock_randbelow, settings):
"""Pin codes should be zero-padded to meet required length."""
settings.ROOM_TELEPHONY_ENABLED = True
settings.ROOM_TELEPHONY_PIN_LENGTH = 10
room = RoomFactory()
assert room.pin_code == "0000012345"
@mock.patch.object(secrets, "randbelow", return_value=12345)
def test_pin_generation_upper_bound(mock_randbelow, settings):
"""Random number generator should use correct upper bound based on pin length."""
settings.ROOM_TELEPHONY_ENABLED = False
room = RoomFactory()
room.generate_unique_pin_code(length=5)
# Assert called with the right exclusive upper bound, 10^5
mock_randbelow.assert_called_with(100000)
Binary file not shown.
@@ -1,438 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-16 17:04+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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: core/admin.py:26
msgid "Personal info"
msgstr "Persönliche Informationen"
#: core/admin.py:39
msgid "Permissions"
msgstr "Berechtigungen"
#: core/admin.py:51
msgid "Important dates"
msgstr "Wichtige Daten"
#: core/api/serializers.py:63
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/models.py:31
msgid "Member"
msgstr "Mitglied"
#: core/models.py:32
msgid "Administrator"
msgstr "Administrator"
#: core/models.py:33
msgid "Owner"
msgstr "Eigentümer"
#: core/models.py:49
msgid "Initiated"
msgstr "Gestartet"
#: core/models.py:50
msgid "Active"
msgstr "Aktiv"
#: core/models.py:51
msgid "Stopped"
msgstr "Beendet"
#: core/models.py:52
msgid "Saved"
msgstr "Gespeichert"
#: core/models.py:53
msgid "Aborted"
msgstr "Abgebrochen"
#: core/models.py:54
msgid "Failed to Start"
msgstr "Start fehlgeschlagen"
#: core/models.py:55
msgid "Failed to Stop"
msgstr "Stopp fehlgeschlagen"
#: core/models.py:56
msgid "Notification succeeded"
msgstr "Benachrichtigung erfolgreich"
#: core/models.py:83
msgid "SCREEN_RECORDING"
msgstr "BILDSCHIRMAUFZEICHNUNG"
#: core/models.py:84
msgid "TRANSCRIPT"
msgstr "TRANSKRIPT"
#: core/models.py:90
msgid "Public Access"
msgstr "Öffentlicher Zugriff"
#: core/models.py:91
msgid "Trusted Access"
msgstr "Vertrauenswürdiger Zugriff"
#: core/models.py:92
msgid "Restricted Access"
msgstr "Eingeschränkter Zugriff"
#: core/models.py:104
msgid "id"
msgstr "ID"
#: core/models.py:105
msgid "primary key for the record as UUID"
msgstr "Primärschlüssel des Eintrags als UUID"
#: core/models.py:111
msgid "created on"
msgstr "erstellt am"
#: core/models.py:112
msgid "date and time at which a record was created"
msgstr "Datum und Uhrzeit der Erstellung eines Eintrags"
#: core/models.py:117
msgid "updated on"
msgstr "aktualisiert am"
#: core/models.py:118
msgid "date and time at which a record was last updated"
msgstr "Datum und Uhrzeit der letzten Aktualisierung eines Eintrags"
#: core/models.py:138
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
msgstr ""
"Geben Sie einen gültigen Sub ein. Dieser Wert darf nur Buchstaben, Zahlen "
"und die Zeichen @/./+/-/_ enthalten."
#: core/models.py:144
msgid "sub"
msgstr "Sub"
#: core/models.py:146
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
msgstr ""
"Erforderlich. Maximal 255 Zeichen. Nur Buchstaben, Zahlen und @/./+/-/_ sind "
"erlaubt."
#: core/models.py:154
msgid "identity email address"
msgstr "Identitäts-E-Mail-Adresse"
#: core/models.py:159
msgid "admin email address"
msgstr "Administrator-E-Mail-Adresse"
#: core/models.py:161
msgid "full name"
msgstr "Vollständiger Name"
#: core/models.py:163
msgid "short name"
msgstr "Kurzname"
#: core/models.py:169
msgid "language"
msgstr "Sprache"
#: core/models.py:170
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:176
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:179
msgid "device"
msgstr "Gerät"
#: core/models.py:181
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:184
msgid "staff status"
msgstr "Mitarbeiterstatus"
#: core/models.py:186
msgid "Whether the user can log into this admin site."
msgstr "Ob der Benutzer sich bei dieser Admin-Seite anmelden kann."
#: core/models.py:189
msgid "active"
msgstr "aktiv"
#: core/models.py:192
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
msgstr ""
"Ob dieser Benutzer als aktiv behandelt werden soll. Deaktivieren Sie dies "
"anstelle des Löschens des Kontos."
#: core/models.py:205
msgid "user"
msgstr "Benutzer"
#: core/models.py:206
msgid "users"
msgstr "Benutzer"
#: core/models.py:265
msgid "Resource"
msgstr "Ressource"
#: core/models.py:266
msgid "Resources"
msgstr "Ressourcen"
#: core/models.py:320
msgid "Resource access"
msgstr "Ressourcenzugriff"
#: core/models.py:321
msgid "Resource accesses"
msgstr "Ressourcenzugriffe"
#: core/models.py:327
msgid "Resource access with this User and Resource already exists."
msgstr ""
"Ein Ressourcenzugriff mit diesem Benutzer und dieser Ressource existiert "
"bereits."
#: core/models.py:383
msgid "Visio room configuration"
msgstr "Visio-Raumkonfiguration"
#: core/models.py:384
msgid "Values for Visio parameters to configure the room."
msgstr "Werte für Visio-Parameter zur Konfiguration des Raums."
#: core/models.py:391
msgid "Room PIN code"
msgstr "PIN-Code für den Raum"
#: core/models.py:392
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:398 core/models.py:552
msgid "Room"
msgstr "Raum"
#: core/models.py:399
msgid "Rooms"
msgstr "Räume"
#: core/models.py:563
msgid "Worker ID"
msgstr "Worker-ID"
#: core/models.py:565
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
msgstr ""
"Geben Sie eine ID für die Aufzeichnung des Workers ein. Diese ID bleibt "
"erhalten, auch wenn der Worker stoppt, was ein einfaches Nachverfolgen "
"ermöglicht."
#: core/models.py:573
msgid "Recording mode"
msgstr "Aufzeichnungsmodus"
#: core/models.py:574
msgid "Defines the mode of recording being called."
msgstr "Definiert den aufgerufenen Aufzeichnungsmodus."
#: core/models.py:580
msgid "Recording"
msgstr "Aufzeichnung"
#: core/models.py:581
msgid "Recordings"
msgstr "Aufzeichnungen"
#: core/models.py:689
msgid "Recording/user relation"
msgstr "Beziehung Aufzeichnung/Benutzer"
#: core/models.py:690
msgid "Recording/user relations"
msgstr "Beziehungen Aufzeichnung/Benutzer"
#: core/models.py:696
msgid "This user is already in this recording."
msgstr "Dieser Benutzer ist bereits Teil dieser Aufzeichnung."
#: core/models.py:702
msgid "This team is already in this recording."
msgstr "Dieses Team ist bereits Teil dieser Aufzeichnung."
#: core/models.py:708
msgid "Either user or team must be set, not both."
msgstr "Entweder Benutzer oder Team muss festgelegt werden, nicht beides."
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Ihre Aufzeichnung ist bereit"
#: core/services/invitation.py:44
#, python-brace-format
msgid "Video call in progress: {sender.email} is waiting for you to connect"
msgstr "Videoanruf läuft: {sender.email} wartet auf Ihre Teilnahme"
#: core/templates/mail/html/invitation.html:159
#: core/templates/mail/html/screen_recording.html:159
#: core/templates/mail/text/invitation.txt:3
#: core/templates/mail/text/screen_recording.txt:3
msgid "Logo email"
msgstr "Logo-E-Mail"
#: core/templates/mail/html/invitation.html:189
#: core/templates/mail/text/invitation.txt:5
msgid "invites you to join an ongoing video call"
msgstr "lädt Sie zu einem laufenden Videoanruf ein"
#: core/templates/mail/html/invitation.html:200
#: core/templates/mail/text/invitation.txt:7
msgid "JOIN THE CALL"
msgstr "AM ANRUF TEILNEHMEN"
#: core/templates/mail/html/invitation.html:227
#: core/templates/mail/text/invitation.txt:13
msgid ""
"If you can't click the button, copy and paste the URL into your browser to "
"join the call."
msgstr ""
"Wenn Sie den Button nicht anklicken können, kopieren Sie die URL und fügen "
"Sie sie in Ihren Browser ein, um am Anruf teilzunehmen."
#: core/templates/mail/html/invitation.html:235
#: core/templates/mail/text/invitation.txt:15
msgid "Tips for a better experience:"
msgstr "Tipps für ein besseres Erlebnis:"
#: core/templates/mail/html/invitation.html:237
#: core/templates/mail/text/invitation.txt:17
msgid "Use Chrome or Firefox for better call quality"
msgstr "Verwenden Sie Chrome oder Firefox für eine bessere Anrufqualität"
#: core/templates/mail/html/invitation.html:238
#: core/templates/mail/text/invitation.txt:18
msgid "Test your microphone and camera before joining"
msgstr "Testen Sie Ihr Mikrofon und Ihre Kamera vor dem Beitritt"
#: core/templates/mail/html/invitation.html:239
#: core/templates/mail/text/invitation.txt:19
msgid "Make sure you have a stable internet connection"
msgstr "Stellen Sie sicher, dass Sie eine stabile Internetverbindung haben"
#: core/templates/mail/html/invitation.html:248
#: core/templates/mail/html/screen_recording.html:240
#: core/templates/mail/text/invitation.txt:21
#: core/templates/mail/text/screen_recording.txt:22
#, python-format
msgid " Thank you for using %(brandname)s. "
msgstr " Vielen Dank für die Nutzung von %(brandname)s. "
#: core/templates/mail/html/screen_recording.html:188
#: core/templates/mail/text/screen_recording.txt:6
msgid "Your recording is ready!"
msgstr "Ihre Aufzeichnung ist fertig!"
#: core/templates/mail/html/screen_recording.html:195
#: core/templates/mail/text/screen_recording.txt:8
#, python-format
msgid ""
" Your recording of \"%(room_name)s\" on %(recording_date)s at "
"%(recording_time)s is now ready to download. "
msgstr ""
" Ihre Aufzeichnung von \"%(room_name)s\" am %(recording_date)s um "
"%(recording_time)s steht nun zum Herunterladen bereit. "
#: core/templates/mail/html/screen_recording.html:195
#: core/templates/mail/text/screen_recording.txt:8
#, python-format
msgid " The recording will expire in %(days)s days. "
msgstr " Die Aufzeichnung wird in %(days)s Tagen ablaufen. "
#: core/templates/mail/html/screen_recording.html:201
#: core/templates/mail/text/screen_recording.txt:10
msgid "To keep this recording permanently:"
msgstr "So speichern Sie diese Aufzeichnung dauerhaft:"
#: core/templates/mail/html/screen_recording.html:203
#: core/templates/mail/text/screen_recording.txt:12
msgid "Click the \"Open\" button below "
msgstr "Klicken Sie auf den Button „Öffnen“ unten "
#: core/templates/mail/html/screen_recording.html:204
#: core/templates/mail/text/screen_recording.txt:13
msgid "Use the \"Download\" button in the interface "
msgstr "Verwenden Sie den Button „Herunterladen“ in der Oberfläche "
#: core/templates/mail/html/screen_recording.html:205
#: core/templates/mail/text/screen_recording.txt:14
msgid "Save the file to your preferred location"
msgstr "Speichern Sie die Datei an einem gewünschten Ort"
#: core/templates/mail/html/screen_recording.html:216
#: core/templates/mail/text/screen_recording.txt:16
msgid "Open"
msgstr "Öffnen"
#: core/templates/mail/html/screen_recording.html:225
#: core/templates/mail/text/screen_recording.txt:18
#, python-format
msgid ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
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:162
msgid "English"
msgstr "Englisch"
#: meet/settings.py:163
msgid "French"
msgstr "Französisch"
#: meet/settings.py:164
msgid "Dutch"
msgstr "Niederländisch"
#: meet/settings.py:165
msgid "German"
msgstr "Deutsch"
Binary file not shown.
+89 -73
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-16 17:04+0000\n"
"POT-Creation-Date: 2025-04-23 14:04+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"
@@ -33,95 +33,107 @@ msgstr "Important dates"
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/models.py:31
#: core/authentication/backends.py:77
msgid "User info contained no recognizable user identification"
msgstr "User info contained no recognizable user identification"
#: core/authentication/backends.py:102
msgid "User account is disabled"
msgstr "User account is disabled"
#: core/authentication/backends.py:142
msgid "Multiple user accounts share a common email."
msgstr "Multiple user accounts share a common email."
#: core/models.py:30
msgid "Member"
msgstr "Member"
#: core/models.py:32
#: core/models.py:31
msgid "Administrator"
msgstr "Administrator"
#: core/models.py:33
#: core/models.py:32
msgid "Owner"
msgstr "Owner"
#: core/models.py:49
#: core/models.py:48
msgid "Initiated"
msgstr "Initiated"
#: core/models.py:50
#: core/models.py:49
msgid "Active"
msgstr "Active"
#: core/models.py:51
#: core/models.py:50
msgid "Stopped"
msgstr "Stopped"
#: core/models.py:52
#: core/models.py:51
msgid "Saved"
msgstr "Saved"
#: core/models.py:53
#: core/models.py:52
msgid "Aborted"
msgstr "Aborted"
#: core/models.py:54
#: core/models.py:53
msgid "Failed to Start"
msgstr "Failed to Start"
#: core/models.py:55
#: core/models.py:54
msgid "Failed to Stop"
msgstr "Failed to Stop"
#: core/models.py:56
#: core/models.py:55
msgid "Notification succeeded"
msgstr "Notification succeeded"
#: core/models.py:83
#: core/models.py:82
msgid "SCREEN_RECORDING"
msgstr "SCREEN_RECORDING"
#: core/models.py:84
#: core/models.py:83
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:90
#: core/models.py:89
msgid "Public Access"
msgstr "Public Access"
#: core/models.py:91
#: core/models.py:90
msgid "Trusted Access"
msgstr "Trusted Access"
#: core/models.py:92
#: core/models.py:91
msgid "Restricted Access"
msgstr "Restricted Access"
#: core/models.py:104
#: core/models.py:103
msgid "id"
msgstr "id"
#: core/models.py:105
#: core/models.py:104
msgid "primary key for the record as UUID"
msgstr "primary key for the record as UUID"
#: core/models.py:111
#: core/models.py:110
msgid "created on"
msgstr "created on"
#: core/models.py:112
#: core/models.py:111
msgid "date and time at which a record was created"
msgstr "date and time at which a record was created"
#: core/models.py:117
#: core/models.py:116
msgid "updated on"
msgstr "updated on"
#: core/models.py:118
#: core/models.py:117
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:138
#: core/models.py:137
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -129,11 +141,11 @@ msgstr ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
#: core/models.py:144
#: core/models.py:143
msgid "sub"
msgstr "sub"
#: core/models.py:146
#: core/models.py:145
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
@@ -141,55 +153,55 @@ msgstr ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
#: core/models.py:154
#: core/models.py:153
msgid "identity email address"
msgstr "identity email address"
#: core/models.py:159
#: core/models.py:158
msgid "admin email address"
msgstr "admin email address"
#: core/models.py:161
#: core/models.py:160
msgid "full name"
msgstr "full name"
#: core/models.py:163
#: core/models.py:162
msgid "short name"
msgstr "short name"
#: core/models.py:169
#: core/models.py:168
msgid "language"
msgstr "language"
#: core/models.py:170
#: core/models.py:169
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:176
#: core/models.py:175
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:179
#: core/models.py:178
msgid "device"
msgstr "device"
#: core/models.py:181
#: core/models.py:180
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:184
#: core/models.py:183
msgid "staff status"
msgstr "staff status"
#: core/models.py:186
#: core/models.py:185
msgid "Whether the user can log into this admin site."
msgstr "Whether the user can log into this admin site."
#: core/models.py:189
#: core/models.py:188
msgid "active"
msgstr "active"
#: core/models.py:192
#: core/models.py:191
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -197,63 +209,55 @@ msgstr ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
#: core/models.py:205
#: core/models.py:204
msgid "user"
msgstr "user"
#: core/models.py:206
#: core/models.py:205
msgid "users"
msgstr "users"
#: core/models.py:265
#: core/models.py:264
msgid "Resource"
msgstr "Resource"
#: core/models.py:266
#: core/models.py:265
msgid "Resources"
msgstr "Resources"
#: core/models.py:320
#: core/models.py:319
msgid "Resource access"
msgstr "Resource access"
#: core/models.py:321
#: core/models.py:320
msgid "Resource accesses"
msgstr "Resource accesses"
#: core/models.py:327
#: core/models.py:326
msgid "Resource access with this User and Resource already exists."
msgstr "Resource access with this User and Resource already exists."
#: core/models.py:383
#: core/models.py:382
msgid "Visio room configuration"
msgstr "Visio room configuration"
#: core/models.py:384
#: core/models.py:383
msgid "Values for Visio parameters to configure the room."
msgstr "Values for Visio parameters to configure the room."
#: core/models.py:391
msgid "Room PIN code"
msgstr "Room PIN code"
#: core/models.py:392
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:398 core/models.py:552
#: core/models.py:389 core/models.py:509
msgid "Room"
msgstr "Room"
#: core/models.py:399
#: core/models.py:390
msgid "Rooms"
msgstr "Rooms"
#: core/models.py:563
#: core/models.py:520
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:565
#: core/models.py:522
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -261,42 +265,58 @@ msgstr ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
#: core/models.py:573
#: core/models.py:530
msgid "Recording mode"
msgstr "Recording mode"
#: core/models.py:574
#: core/models.py:531
msgid "Defines the mode of recording being called."
msgstr "Defines the mode of recording being called."
#: core/models.py:580
#: core/models.py:537
msgid "Recording"
msgstr "Recording"
#: core/models.py:581
#: core/models.py:538
msgid "Recordings"
msgstr "Recordings"
#: core/models.py:689
#: core/models.py:646
msgid "Recording/user relation"
msgstr "Recording/user relation"
#: core/models.py:690
#: core/models.py:647
msgid "Recording/user relations"
msgstr "Recording/user relations"
#: core/models.py:696
#: core/models.py:653
msgid "This user is already in this recording."
msgstr "This user is already in this recording."
#: core/models.py:702
#: core/models.py:659
msgid "This team is already in this recording."
msgstr "This team is already in this recording."
#: core/models.py:708
#: core/models.py:665
msgid "Either user or team must be set, not both."
msgstr "Either user or team must be set, not both."
#: core/recording/event/authentication.py:58
msgid "Authentication is enabled but token is not configured."
msgstr "Authentication is enabled but token is not configured."
#: core/recording/event/authentication.py:70
msgid "Authorization header is required"
msgstr "Authorization header is required"
#: core/recording/event/authentication.py:78
msgid "Invalid authorization header."
msgstr "Invalid authorization header."
#: core/recording/event/authentication.py:88
msgid "Invalid token"
msgstr "Invalid token"
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Your recording is ready"
@@ -427,7 +447,3 @@ msgstr "French"
#: meet/settings.py:164
msgid "Dutch"
msgstr "Dutch"
#: meet/settings.py:165
msgid "German"
msgstr "German"
Binary file not shown.
+95 -77
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-16 17:04+0000\n"
"POT-Creation-Date: 2025-04-23 14:04+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"
@@ -35,109 +35,123 @@ msgstr ""
"Vous devez être administrateur ou propriétaire d'une salle pour y ajouter "
"des accès."
#: core/models.py:31
#: core/authentication/backends.py:77
msgid "User info contained no recognizable user identification"
msgstr ""
"Les informations utilisateur ne contiennent aucune identification "
"utilisateur reconnaissable"
#: core/authentication/backends.py:102
msgid "User account is disabled"
msgstr "Le compte utilisateur est désactivé"
#: core/authentication/backends.py:142
msgid "Multiple user accounts share a common email."
msgstr "Plusieurs comptes utilisateur partagent une adresse e-mail commune."
#: core/models.py:30
msgid "Member"
msgstr "Membre"
#: core/models.py:32
#: core/models.py:31
msgid "Administrator"
msgstr "Administrateur"
#: core/models.py:33
#: core/models.py:32
msgid "Owner"
msgstr "Propriétaire"
#: core/models.py:49
#: core/models.py:48
msgid "Initiated"
msgstr "Initié"
#: core/models.py:50
#: core/models.py:49
msgid "Active"
msgstr "Actif"
#: core/models.py:51
#: core/models.py:50
msgid "Stopped"
msgstr "Arrêté"
#: core/models.py:52
#: core/models.py:51
msgid "Saved"
msgstr "Enregistré"
#: core/models.py:53
#: core/models.py:52
msgid "Aborted"
msgstr "Abandonné"
#: core/models.py:54
#: core/models.py:53
msgid "Failed to Start"
msgstr "Échec au démarrage"
#: core/models.py:55
#: core/models.py:54
msgid "Failed to Stop"
msgstr "Échec à l'arrêt"
#: core/models.py:56
#: core/models.py:55
msgid "Notification succeeded"
msgstr "Notification réussie"
#: core/models.py:83
#: core/models.py:82
msgid "SCREEN_RECORDING"
msgstr "ENREGISTREMENT_ÉCRAN"
#: core/models.py:84
#: core/models.py:83
msgid "TRANSCRIPT"
msgstr "TRANSCRIPTION"
#: core/models.py:90
#: core/models.py:89
msgid "Public Access"
msgstr "Accès public"
#: core/models.py:91
#: core/models.py:90
msgid "Trusted Access"
msgstr "Accès de confiance"
#: core/models.py:92
#: core/models.py:91
msgid "Restricted Access"
msgstr "Accès restreint"
#: core/models.py:104
#: core/models.py:103
msgid "id"
msgstr "id"
#: core/models.py:105
#: core/models.py:104
msgid "primary key for the record as UUID"
msgstr "clé primaire pour l'enregistrement sous forme d'UUID"
#: core/models.py:111
#: core/models.py:110
msgid "created on"
msgstr "créé le"
#: core/models.py:112
#: core/models.py:111
msgid "date and time at which a record was created"
msgstr "date et heure auxquelles un enregistrement a été créé"
#: core/models.py:117
#: core/models.py:116
msgid "updated on"
msgstr "mis à jour le"
#: core/models.py:118
#: core/models.py:117
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:138
#: core/models.py:137
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
msgstr ""
"Entrez un sub valide. Cette valeur ne peut contenir que des lettres, "
"Entrez un identifiant valide. Cette valeur ne peut contenir que des lettres, "
"des chiffres et les caractères @/./+/-/_."
#: core/models.py:144
#: core/models.py:143
msgid "sub"
msgstr "sub"
msgstr "identifiant"
#: core/models.py:146
#: core/models.py:145
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
@@ -145,55 +159,55 @@ msgstr ""
"Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./"
"+/-/_ uniquement."
#: core/models.py:154
#: core/models.py:153
msgid "identity email address"
msgstr "adresse e-mail d'identité"
#: core/models.py:159
#: core/models.py:158
msgid "admin email address"
msgstr "adresse e-mail d'administrateur"
#: core/models.py:161
#: core/models.py:160
msgid "full name"
msgstr "nom complet"
#: core/models.py:163
#: core/models.py:162
msgid "short name"
msgstr "nom court"
#: core/models.py:169
#: core/models.py:168
msgid "language"
msgstr "langue"
#: core/models.py:170
#: core/models.py:169
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:176
#: core/models.py:175
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:179
#: core/models.py:178
msgid "device"
msgstr "appareil"
#: core/models.py:181
#: core/models.py:180
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:184
#: core/models.py:183
msgid "staff status"
msgstr "statut du personnel"
#: core/models.py:186
#: core/models.py:185
msgid "Whether the user can log into this admin site."
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
#: core/models.py:189
#: core/models.py:188
msgid "active"
msgstr "actif"
#: core/models.py:192
#: core/models.py:191
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -201,108 +215,116 @@ msgstr ""
"Si cet utilisateur doit être traité comme actif. Désélectionnez cette option "
"au lieu de supprimer des comptes."
#: core/models.py:205
#: core/models.py:204
msgid "user"
msgstr "utilisateur"
#: core/models.py:206
#: core/models.py:205
msgid "users"
msgstr "utilisateurs"
#: core/models.py:265
#: core/models.py:264
msgid "Resource"
msgstr "Ressource"
#: core/models.py:266
#: core/models.py:265
msgid "Resources"
msgstr "Ressources"
#: core/models.py:320
#: core/models.py:319
msgid "Resource access"
msgstr "Accès aux ressources"
#: core/models.py:321
#: core/models.py:320
msgid "Resource accesses"
msgstr "Accès aux ressources"
#: core/models.py:327
#: core/models.py:326
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:383
#: core/models.py:382
msgid "Visio room configuration"
msgstr "Configuration de la salle de visioconférence"
#: core/models.py:384
#: core/models.py:383
msgid "Values for Visio parameters to configure the room."
msgstr "Valeurs des paramètres de visioconférence pour configurer la salle."
#: core/models.py:391
msgid "Room PIN code"
msgstr "Code PIN de la salle"
#: core/models.py:392
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:398 core/models.py:552
#: core/models.py:389 core/models.py:509
msgid "Room"
msgstr "Salle"
#: core/models.py:399
#: core/models.py:390
msgid "Rooms"
msgstr "Salles"
#: core/models.py:563
#: core/models.py:520
msgid "Worker ID"
msgstr "ID du Worker"
#: core/models.py:565
#: core/models.py:522
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
msgstr ""
"Entrez un identifiant pour l'enregistrement du Worker. Cet identifiant "
"est conservé même lorsque le Worker s'arrête, permettant un suivi "
"Entrez un identifiant pour l'enregistrement du travailleur. Cet identifiant "
"est conservé même lorsque le travailleur s'arrête, permettant un suivi "
"facile."
#: core/models.py:573
#: core/models.py:530
msgid "Recording mode"
msgstr "Mode d'enregistrement"
#: core/models.py:574
#: core/models.py:531
msgid "Defines the mode of recording being called."
msgstr "Définit le mode d'enregistrement appelé."
#: core/models.py:580
#: core/models.py:537
msgid "Recording"
msgstr "Enregistrement"
#: core/models.py:581
#: core/models.py:538
msgid "Recordings"
msgstr "Enregistrements"
#: core/models.py:689
#: core/models.py:646
msgid "Recording/user relation"
msgstr "Relation enregistrement/utilisateur"
#: core/models.py:690
#: core/models.py:647
msgid "Recording/user relations"
msgstr "Relations enregistrement/utilisateur"
#: core/models.py:696
#: core/models.py:653
msgid "This user is already in this recording."
msgstr "Cet utilisateur est déjà dans cet enregistrement."
#: core/models.py:702
#: core/models.py:659
msgid "This team is already in this recording."
msgstr "Cette équipe est déjà dans cet enregistrement."
#: core/models.py:708
#: core/models.py:665
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/recording/event/authentication.py:58
msgid "Authentication is enabled but token is not configured."
msgstr "L'authentification est activée mais le jeton n'est pas configuré."
#: core/recording/event/authentication.py:70
msgid "Authorization header is required"
msgstr "L'en-tête d'autorisation est requis"
#: core/recording/event/authentication.py:78
msgid "Invalid authorization header."
msgstr "En-tête d'autorisation invalide."
#: core/recording/event/authentication.py:88
msgid "Invalid token"
msgstr "Jeton invalide"
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Votre enregistrement est prêt"
@@ -433,7 +455,3 @@ msgstr "Français"
#: meet/settings.py:164
msgid "Dutch"
msgstr "Néerlandais"
#: meet/settings.py:165
msgid "German"
msgstr "Allemand"
Binary file not shown.
+89 -73
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-16 17:04+0000\n"
"POT-Creation-Date: 2025-04-23 14:04+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"
@@ -34,95 +34,107 @@ 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/models.py:31
#: core/authentication/backends.py:77
msgid "User info contained no recognizable user identification"
msgstr "Gebruikersinformatie bevatte geen herkenbare gebruikersidentificatie"
#: core/authentication/backends.py:102
msgid "User account is disabled"
msgstr "Gebruikersaccount is uitgeschakeld"
#: core/authentication/backends.py:142
msgid "Multiple user accounts share a common email."
msgstr "Meerdere gebruikersaccounts delen een gemeenschappelijk e-mailadres."
#: core/models.py:30
msgid "Member"
msgstr "Lid"
#: core/models.py:32
#: core/models.py:31
msgid "Administrator"
msgstr "Beheerder"
#: core/models.py:33
#: core/models.py:32
msgid "Owner"
msgstr "Eigenaar"
#: core/models.py:49
#: core/models.py:48
msgid "Initiated"
msgstr "Gestart"
#: core/models.py:50
#: core/models.py:49
msgid "Active"
msgstr "Actief"
#: core/models.py:51
#: core/models.py:50
msgid "Stopped"
msgstr "Gestopt"
#: core/models.py:52
#: core/models.py:51
msgid "Saved"
msgstr "Opgeslagen"
#: core/models.py:53
#: core/models.py:52
msgid "Aborted"
msgstr "Afgebroken"
#: core/models.py:54
#: core/models.py:53
msgid "Failed to Start"
msgstr "Starten mislukt"
#: core/models.py:55
#: core/models.py:54
msgid "Failed to Stop"
msgstr "Stoppen mislukt"
#: core/models.py:56
#: core/models.py:55
msgid "Notification succeeded"
msgstr "Notificatie geslaagd"
#: core/models.py:83
#: core/models.py:82
msgid "SCREEN_RECORDING"
msgstr "SCHERM_OPNAME"
#: core/models.py:84
#: core/models.py:83
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:90
#: core/models.py:89
msgid "Public Access"
msgstr "Openbare toegang"
#: core/models.py:91
#: core/models.py:90
msgid "Trusted Access"
msgstr "Vertrouwde toegang"
#: core/models.py:92
#: core/models.py:91
msgid "Restricted Access"
msgstr "Beperkte toegang"
#: core/models.py:104
#: core/models.py:103
msgid "id"
msgstr "id"
#: core/models.py:105
#: core/models.py:104
msgid "primary key for the record as UUID"
msgstr "primaire sleutel voor het record als UUID"
#: core/models.py:111
#: core/models.py:110
msgid "created on"
msgstr "aangemaakt op"
#: core/models.py:112
#: core/models.py:111
msgid "date and time at which a record was created"
msgstr "datum en tijd waarop een record werd aangemaakt"
#: core/models.py:117
#: core/models.py:116
msgid "updated on"
msgstr "bijgewerkt op"
#: core/models.py:118
#: core/models.py:117
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:138
#: core/models.py:137
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -130,66 +142,66 @@ msgstr ""
"Voer een geldige sub in. Deze waarde mag alleen letters, cijfers en @/./+/-/"
"_ tekens bevatten."
#: core/models.py:144
#: core/models.py:143
msgid "sub"
msgstr "sub"
#: core/models.py:146
#: core/models.py:145
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
msgstr ""
"Vereist. 255 tekens of minder. Alleen letters, cijfers en @/./+/-/_ tekens."
#: core/models.py:154
#: core/models.py:153
msgid "identity email address"
msgstr "identiteit e-mailadres"
#: core/models.py:159
#: core/models.py:158
msgid "admin email address"
msgstr "beheerder e-mailadres"
#: core/models.py:161
#: core/models.py:160
msgid "full name"
msgstr "volledige naam"
#: core/models.py:163
#: core/models.py:162
msgid "short name"
msgstr "korte naam"
#: core/models.py:169
#: core/models.py:168
msgid "language"
msgstr "taal"
#: core/models.py:170
#: core/models.py:169
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:176
#: core/models.py:175
msgid "The timezone in which the user wants to see times."
msgstr "De tijdzone waarin de gebruiker tijden wil zien."
#: core/models.py:179
#: core/models.py:178
msgid "device"
msgstr "apparaat"
#: core/models.py:181
#: core/models.py:180
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:184
#: core/models.py:183
msgid "staff status"
msgstr "personeelsstatus"
#: core/models.py:186
#: core/models.py:185
msgid "Whether the user can log into this admin site."
msgstr "Of de gebruiker kan inloggen op deze beheersite."
#: core/models.py:189
#: core/models.py:188
msgid "active"
msgstr "actief"
#: core/models.py:192
#: core/models.py:191
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -197,63 +209,55 @@ msgstr ""
"Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in "
"plaats van accounts te verwijderen."
#: core/models.py:205
#: core/models.py:204
msgid "user"
msgstr "gebruiker"
#: core/models.py:206
#: core/models.py:205
msgid "users"
msgstr "gebruikers"
#: core/models.py:265
#: core/models.py:264
msgid "Resource"
msgstr "Bron"
#: core/models.py:266
#: core/models.py:265
msgid "Resources"
msgstr "Bronnen"
#: core/models.py:320
#: core/models.py:319
msgid "Resource access"
msgstr "Brontoegang"
#: core/models.py:321
#: core/models.py:320
msgid "Resource accesses"
msgstr "Brontoegangsrechten"
#: core/models.py:327
#: core/models.py:326
msgid "Resource access with this User and Resource already exists."
msgstr "Brontoegang met deze gebruiker en bron bestaat al."
#: core/models.py:383
#: core/models.py:382
msgid "Visio room configuration"
msgstr "Visio-ruimteconfiguratie"
#: core/models.py:384
#: core/models.py:383
msgid "Values for Visio parameters to configure the room."
msgstr "Waarden voor Visio-parameters om de ruimte te configureren."
#: core/models.py:391
msgid "Room PIN code"
msgstr "Pincode van de kamer"
#: core/models.py:392
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:398 core/models.py:552
#: core/models.py:389 core/models.py:509
msgid "Room"
msgstr "Ruimte"
#: core/models.py:399
#: core/models.py:390
msgid "Rooms"
msgstr "Ruimtes"
#: core/models.py:563
#: core/models.py:520
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:565
#: core/models.py:522
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -261,42 +265,58 @@ 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:573
#: core/models.py:530
msgid "Recording mode"
msgstr "Opnamemodus"
#: core/models.py:574
#: core/models.py:531
msgid "Defines the mode of recording being called."
msgstr "Definieert de modus van opname die wordt aangeroepen."
#: core/models.py:580
#: core/models.py:537
msgid "Recording"
msgstr "Opname"
#: core/models.py:581
#: core/models.py:538
msgid "Recordings"
msgstr "Opnames"
#: core/models.py:689
#: core/models.py:646
msgid "Recording/user relation"
msgstr "Opname/gebruiker-relatie"
#: core/models.py:690
#: core/models.py:647
msgid "Recording/user relations"
msgstr "Opname/gebruiker-relaties"
#: core/models.py:696
#: core/models.py:653
msgid "This user is already in this recording."
msgstr "Deze gebruiker is al in deze opname."
#: core/models.py:702
#: core/models.py:659
msgid "This team is already in this recording."
msgstr "Dit team is al in deze opname."
#: core/models.py:708
#: core/models.py:665
msgid "Either user or team must be set, not both."
msgstr "Ofwel gebruiker of team moet worden ingesteld, niet beide."
#: core/recording/event/authentication.py:58
msgid "Authentication is enabled but token is not configured."
msgstr "Authenticatie is ingeschakeld maar token is niet geconfigureerd."
#: core/recording/event/authentication.py:70
msgid "Authorization header is required"
msgstr "Autorisatie-header is vereist"
#: core/recording/event/authentication.py:78
msgid "Invalid authorization header."
msgstr "Ongeldige autorisatie-header."
#: core/recording/event/authentication.py:88
msgid "Invalid token"
msgstr "Ongeldig token"
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Je opname is klaar"
@@ -427,7 +447,3 @@ msgstr "Frans"
#: meet/settings.py:164
msgid "Dutch"
msgstr "Nederlands"
#: meet/settings.py:165
msgid "German"
msgstr "Duits"
-18
View File
@@ -162,7 +162,6 @@ class Base(Configuration):
("en-us", _("English")),
("fr-fr", _("French")),
("nl-nl", _("Dutch")),
("de-de", _("German")),
)
)
@@ -573,23 +572,6 @@ class Base(Configuration):
environ_prefix=None,
)
# SIP Telephony
ROOM_TELEPHONY_ENABLED = values.BooleanValue(
False,
environ_name="ROOM_TELEPHONY_ENABLED",
environ_prefix=None,
)
ROOM_TELEPHONY_PIN_LENGTH = values.PositiveIntegerValue(
10,
environ_name="ROOM_TELEPHONY_PIN_LENGTH",
environ_prefix=None,
)
ROOM_TELEPHONY_PIN_MAX_RETRIES = values.PositiveIntegerValue(
5,
environ_name="ROOM_TELEPHONY_PIN_MAX_RETRIES",
environ_prefix=None,
)
# pylint: disable=invalid-name
@property
def ENVIRONMENT(self):
+2 -2
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "meet"
version = "0.1.22"
version = "0.1.21"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -38,7 +38,7 @@ dependencies = [
"django-redis==5.4.0",
"django-storages[s3]==1.14.5",
"django-timezone-field>=5.1",
"django==5.1.9",
"django==5.1.8",
"djangorestframework==3.15.2",
"drf_spectacular==0.28.0",
"dockerflow==2024.4.2",
+1336 -1374
View File
File diff suppressed because it is too large Load Diff
+25 -25
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "0.1.22",
"version": "0.1.21",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -13,48 +13,48 @@
"check": "prettier --check ./src"
},
"dependencies": {
"@livekit/components-react": "2.9.3",
"@livekit/components-styles": "1.1.5",
"@livekit/track-processors": "0.5.6",
"@pandacss/preset-panda": "0.53.6",
"@react-aria/toast": "3.0.2",
"@livekit/components-react": "2.8.1",
"@livekit/components-styles": "1.1.4",
"@livekit/track-processors": "0.3.3",
"@pandacss/preset-panda": "0.53.3",
"@react-aria/toast": "3.0.1",
"@remixicon/react": "4.6.0",
"@tanstack/react-query": "5.76.0",
"@tanstack/react-query": "5.69.0",
"crisp-sdk-web": "1.0.25",
"hoofd": "1.7.3",
"i18next": "25.1.2",
"i18next-browser-languagedetector": "8.1.0",
"i18next": "24.2.3",
"i18next-browser-languagedetector": "8.0.4",
"i18next-parser": "9.3.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.11.4",
"posthog-js": "1.240.6",
"livekit-client": "2.9.8",
"posthog-js": "1.232.6",
"react": "18.3.1",
"react-aria-components": "1.8.0",
"react-aria-components": "1.7.1",
"react-dom": "18.3.1",
"react-i18next": "15.1.1",
"use-sound": "5.0.0",
"valtio": "2.1.5",
"wouter": "3.7.0"
"valtio": "2.1.4",
"wouter": "3.6.0"
},
"devDependencies": {
"@pandacss/dev": "0.53.6",
"@tanstack/eslint-plugin-query": "5.74.7",
"@tanstack/react-query-devtools": "5.76.0",
"@types/node": "22.15.17",
"@pandacss/dev": "0.53.3",
"@tanstack/eslint-plugin-query": "5.68.0",
"@tanstack/react-query-devtools": "5.69.0",
"@types/node": "22.13.13",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@typescript-eslint/eslint-plugin": "8.32.0",
"@typescript-eslint/parser": "8.32.0",
"@vitejs/plugin-react": "4.4.1",
"@typescript-eslint/eslint-plugin": "8.28.0",
"@typescript-eslint/parser": "8.28.0",
"@vitejs/plugin-react": "4.3.4",
"eslint": "8.57.0",
"eslint-config-prettier": "10.1.5",
"eslint-config-prettier": "10.1.1",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.20",
"eslint-plugin-react-refresh": "0.4.19",
"postcss": "8.5.3",
"prettier": "3.5.3",
"typescript": "5.8.3",
"vite": "6.2.7",
"typescript": "5.8.2",
"vite": "6.2.6",
"vite-tsconfig-paths": "5.1.4"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

@@ -11,7 +11,7 @@ import { useNotificationSound } from '@/features/notifications/hooks/useSoundNot
import { ToastProvider, toastQueue } from './components/ToastProvider'
import { WaitingParticipantNotification } from './components/WaitingParticipantNotification'
import {
Emoji,
EMOJIS,
Reaction,
} from '@/features/rooms/livekit/components/controls/ReactionsToggle'
import {
@@ -50,7 +50,7 @@ export const MainNotificationToast = () => {
}, [room, triggerNotificationSound])
const handleEmoji = (emoji: string, participant: Participant) => {
if (!emoji || !Object.values(Emoji).includes(emoji as Emoji)) return
if (!emoji || !EMOJIS.includes(emoji)) return
const id = instanceIdRef.current++
setReactions((prev) => [
...prev,
+1 -5
View File
@@ -1,9 +1,5 @@
export { Room as RoomRoute } from './routes/Room'
export { FeedbackRoute } from './routes/Feedback'
export {
roomIdPattern,
isRoomValid,
flexibleRoomIdPattern,
} from './utils/isRoomValid'
export { roomIdPattern, isRoomValid } from './utils/isRoomValid'
export { generateRoomId } from './utils/generateRoomId'
export { useCreateRoom } from './api/createRoom'
@@ -15,7 +15,6 @@ export const INITIAL_POSITION = 200
interface FloatingReactionProps {
emoji: string
name?: string
isLocal?: boolean
speed?: number
scale?: number
}
@@ -23,7 +22,6 @@ interface FloatingReactionProps {
export function FloatingReaction({
emoji,
name,
isLocal = false,
speed = 1,
scale = 1,
}: FloatingReactionProps) {
@@ -62,6 +60,7 @@ export function FloatingReaction({
return (
<div
className={css({
fontSize: '3rem',
position: 'absolute',
display: 'flex',
alignItems: 'center',
@@ -70,27 +69,23 @@ export function FloatingReaction({
style={{
left: left,
bottom: INITIAL_POSITION + deltaY,
transform: `scale(${scale})`,
opacity: opacity,
}}
>
<img
src={`/assets/reactions/${emoji}.png`}
alt={''}
<span
className={css({
height: '50px',
lineHeight: '57px',
})}
style={{
transform: `scale(${scale})`,
transformOrigin: 'center bottom',
}}
/>
>
{emoji}
</span>
{name && (
<Text
variant="sm"
className={css({
backgroundColor: isLocal ? 'primary.100' : 'primaryDark.100',
color: isLocal ? 'black' : 'white',
fontWeight: 500,
backgroundColor: 'primaryDark.100',
color: 'white',
textAlign: 'center',
borderRadius: '20px',
paddingX: '0.5rem',
@@ -133,7 +128,6 @@ export function ReactionPortal({
speed={speed}
scale={scale}
name={participant?.isLocal ? t('you') : participant.name}
isLocal={participant?.isLocal}
/>
</div>,
document.body
@@ -1,4 +1,4 @@
import { ProcessorOptions, Track, TrackProcessor } from 'livekit-client'
import { ProcessorOptions, Track } from 'livekit-client'
import posthog from 'posthog-js'
import {
FilesetResolver,
@@ -11,20 +11,19 @@ import {
TIMEOUT_TICK,
timerWorkerScript,
} from './TimerWorker'
import { ProcessorType } from '.'
import {
BackgroundProcessorInterface,
BackgroundOptions,
ProcessorType,
} from '.'
const PROCESSING_WIDTH = 256 * 3
const PROCESSING_HEIGHT = 144 * 3
const FACE_LANDMARKS_CANVAS_ID = 'face-landmarks-local'
export type FaceLandmarksOptions = {
showGlasses: boolean
showFrench: boolean
}
export class FaceLandmarksProcessor implements TrackProcessor<Track.Kind> {
options: FaceLandmarksOptions
export class FaceLandmarksProcessor implements BackgroundProcessorInterface {
options: BackgroundOptions
name: string
processedTrack?: MediaStreamTrack | undefined
@@ -51,7 +50,7 @@ export class FaceLandmarksProcessor implements TrackProcessor<Track.Kind> {
glassesImage?: HTMLImageElement
mustacheImage?: HTMLImageElement
beretImage?: HTMLImageElement
constructor(opts: FaceLandmarksOptions) {
constructor(opts: BackgroundOptions) {
this.name = 'face_landmarks'
this.options = opts
this.type = ProcessorType.FACE_LANDMARKS
@@ -315,7 +314,7 @@ export class FaceLandmarksProcessor implements TrackProcessor<Track.Kind> {
return element
}
update(opts: FaceLandmarksOptions): void {
update(opts: BackgroundOptions): void {
this.options = opts
}
@@ -3,10 +3,13 @@ import { Track, TrackProcessor } from 'livekit-client'
import { BackgroundBlurTrackProcessorJsWrapper } from './BackgroundBlurTrackProcessorJsWrapper'
import { BackgroundCustomProcessor } from './BackgroundCustomProcessor'
import { BackgroundVirtualTrackProcessorJsWrapper } from './BackgroundVirtualTrackProcessorJsWrapper'
import { FaceLandmarksProcessor } from './FaceLandmarksProcessor'
export type BackgroundOptions = {
blurRadius?: number
imagePath?: string
showGlasses?: boolean
showFrench?: boolean
}
export interface ProcessorSerialized {
@@ -29,12 +32,12 @@ export enum ProcessorType {
}
export class BackgroundProcessorFactory {
static hasModernApiSupport() {
return ProcessorWrapper.hasModernApiSupport
}
static isSupported() {
return ProcessorWrapper.isSupported || BackgroundCustomProcessor.isSupported
return (
ProcessorWrapper.isSupported ||
BackgroundCustomProcessor.isSupported ||
FaceLandmarksProcessor.isSupported
)
}
static getProcessor(
@@ -55,6 +58,10 @@ export class BackgroundProcessorFactory {
if (BackgroundCustomProcessor.isSupported) {
return new BackgroundCustomProcessor(opts)
}
} else if (type === ProcessorType.FACE_LANDMARKS) {
if (FaceLandmarksProcessor.isSupported) {
return new FaceLandmarksProcessor(opts)
}
}
return undefined
}
@@ -15,16 +15,7 @@ import { Participant } from 'livekit-client'
import useRateLimiter from '@/hooks/useRateLimiter'
// eslint-disable-next-line react-refresh/only-export-components
export enum Emoji {
THUMBS_UP = 'thumbs-up',
THUMBS_DOWN = 'thumbs-down',
CLAP = 'clapping-hands',
HEART = 'red-heart',
LAUGHING = 'face-with-tears-of-joy',
SURPRISED = 'face-with-open-mouth',
CELEBRATION = 'party-popper',
PLEASE = 'folded-hands',
}
export const EMOJIS = ['👍', '👎', '👏', '❤️', '😂', '😮', '🎉']
export interface Reaction {
id: number
@@ -121,10 +112,10 @@ export const ReactionsToggle = () => {
<div
className={css({
position: 'absolute',
top: -63,
left: -162,
top: -55,
left: -114,
borderRadius: '8px',
padding: '0.35rem',
padding: '0.25rem',
backgroundColor: 'primaryDark.50',
opacity: opacity,
transition: 'opacity 0.2s ease',
@@ -138,29 +129,24 @@ export const ReactionsToggle = () => {
<RACToolbar
className={css({
display: 'flex',
gap: '0.5rem',
})}
>
{Object.values(Emoji).map((emoji, index) => (
{EMOJIS.map((emoji, index) => (
<Button
key={index}
onPress={() => debouncedSendReaction(emoji)}
aria-label={t('send', { emoji })}
variant="primaryTextDark"
size="sm"
square
data-attr={`send-reaction-${emoji}`}
>
<img
src={`/assets/reactions/${emoji}.png`}
alt=""
<span
className={css({
minHeight: '28px',
minWidth: '28px',
pointerEvents: 'none',
userSelect: 'none',
fontSize: '20px',
})}
/>
>
{emoji}
</span>
</Button>
))}
</RACToolbar>
@@ -15,9 +15,12 @@ import { BlurOnStrong } from '@/components/icons/BlurOnStrong'
import { useTrackToggle } from '@livekit/components-react'
import { Loader } from '@/primitives/Loader'
import { useSyncAfterDelay } from '@/hooks/useSyncAfterDelay'
import { RiProhibited2Line } from '@remixicon/react'
import { FunnyEffects } from './FunnyEffects'
import { useHasFunnyEffectsAccess } from '../../hooks/useHasFunnyEffectsAccess'
import {
RiProhibited2Line,
RiGlassesLine,
RiGoblet2Fill,
} from '@remixicon/react'
import { useHasFaceLandmarksAccess } from '../../hooks/useHasFaceLandmarksAccess'
enum BlurRadius {
NONE = 0,
@@ -52,7 +55,7 @@ export const EffectsConfiguration = ({
const { toggle, enabled } = useTrackToggle({ source: Track.Source.Camera })
const [processorPending, setProcessorPending] = useState(false)
const processorPendingReveal = useSyncAfterDelay(processorPending)
const hasFunnyEffectsAccess = useHasFunnyEffectsAccess()
const hasFaceLandmarksAccess = useHasFaceLandmarksAccess()
useEffect(() => {
const videoElement = videoRef.current
@@ -111,13 +114,6 @@ export const EffectsConfiguration = ({
type,
options
)!
// IMPORTANT: Must explicitly stop previous processor before setting a new one
// in browsers without modern API support to prevent UI crashes.
// This workaround is needed until this issue is resolved:
// https://github.com/livekit/track-processors-js/issues/85
if (!BackgroundProcessorFactory.hasModernApiSupport()) {
await videoTrack.stopProcessor()
}
await videoTrack.setProcessor(newProcessor)
onSubmit?.(newProcessor)
} else {
@@ -149,9 +145,42 @@ export const EffectsConfiguration = ({
}
const tooltipLabel = (type: ProcessorType, options: BackgroundOptions) => {
if (type === ProcessorType.FACE_LANDMARKS) {
const effect = options.showGlasses ? 'glasses' : 'french'
return t(
`faceLandmarks.${effect}.${isSelected(type, options) ? 'clear' : 'apply'}`
)
}
return t(`${type}.${isSelected(type, options) ? 'clear' : 'apply'}`)
}
const getFaceLandmarksOptions = () => {
const processor = getProcessor()
if (processor?.serialize().type === ProcessorType.FACE_LANDMARKS) {
return processor.serialize().options as {
showGlasses?: boolean
showFrench?: boolean
}
}
return { showGlasses: false, showFrench: false }
}
const toggleFaceLandmarkEffect = async (effect: 'glasses' | 'french') => {
const currentOptions = getFaceLandmarksOptions()
const newOptions = {
...currentOptions,
[effect === 'glasses' ? 'showGlasses' : 'showFrench']:
!currentOptions[effect === 'glasses' ? 'showGlasses' : 'showFrench'],
}
if (!newOptions.showGlasses && !newOptions.showFrench) {
// If both effects are off stop the processor
await clearEffect()
} else {
await toggleEffect(ProcessorType.FACE_LANDMARKS, newOptions)
}
}
return (
<div
className={css(
@@ -186,7 +215,7 @@ export const EffectsConfiguration = ({
muted
style={{
transform: 'rotateY(180deg)',
[layout === 'vertical' ? 'height' : 'minHeight']: '175px',
minHeight: '175px',
borderRadius: '8px',
}}
/>
@@ -239,13 +268,6 @@ export const EffectsConfiguration = ({
: {}
)}
>
{hasFunnyEffectsAccess && (
<FunnyEffects
videoTrack={videoTrack}
isPending={processorPendingReveal}
onPending={setProcessorPending}
/>
)}
{isSupported ? (
<>
<div>
@@ -318,6 +340,8 @@ export const EffectsConfiguration = ({
<BlurOnStrong />
</ToggleButton>
</div>
</div>
{hasFaceLandmarksAccess && (
<div
className={css({
marginTop: '1.5rem',
@@ -330,51 +354,114 @@ export const EffectsConfiguration = ({
}}
variant="bodyXsBold"
>
{t('virtual.title')}
{t('faceLandmarks.title')}
</H>
<div
className={css({
display: 'flex',
gap: '1.25rem',
flexWrap: 'wrap',
})}
>
{[...Array(8).keys()].map((i) => {
const imagePath = `/assets/backgrounds/${i + 1}.jpg`
const thumbnailPath = `/assets/backgrounds/thumbnails/${i + 1}.jpg`
return (
<ToggleButton
key={i}
variant="bigSquare"
aria-label={tooltipLabel(ProcessorType.VIRTUAL, {
imagePath,
})}
tooltip={tooltipLabel(ProcessorType.VIRTUAL, {
imagePath,
})}
isDisabled={processorPendingReveal}
onChange={async () =>
await toggleEffect(ProcessorType.VIRTUAL, {
imagePath,
})
}
isSelected={isSelected(ProcessorType.VIRTUAL, {
imagePath,
})}
className={css({
bgSize: 'cover',
})}
style={{
backgroundImage: `url(${thumbnailPath})`,
}}
data-attr={`toggle-virtual-${i}`}
/>
)
})}
<ToggleButton
variant="bigSquare"
aria-label={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
showGlasses: true,
showFrench: false,
})}
tooltip={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
showGlasses: true,
showFrench: false,
})}
isDisabled={processorPendingReveal}
onChange={async () =>
await toggleFaceLandmarkEffect('glasses')
}
isSelected={getFaceLandmarksOptions().showGlasses}
data-attr="toggle-glasses"
>
<RiGlassesLine />
</ToggleButton>
<ToggleButton
variant="bigSquare"
aria-label={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
showGlasses: false,
showFrench: true,
})}
tooltip={tooltipLabel(ProcessorType.FACE_LANDMARKS, {
showGlasses: false,
showFrench: true,
})}
isDisabled={processorPendingReveal}
onChange={async () =>
await toggleFaceLandmarkEffect('french')
}
isSelected={getFaceLandmarksOptions().showFrench}
data-attr="toggle-french"
>
<RiGoblet2Fill />
</ToggleButton>
</div>
</div>
)}
<div
className={css({
marginTop: '1.5rem',
})}
>
<H
lvl={3}
style={{
marginBottom: '1rem',
}}
variant="bodyXsBold"
>
{t('virtual.title')}
</H>
<div
className={css({
display: 'flex',
gap: '1.25rem',
flexWrap: 'wrap',
})}
>
{[...Array(8).keys()].map((i) => {
const imagePath = `/assets/backgrounds/${i + 1}.jpg`
const thumbnailPath = `/assets/backgrounds/thumbnails/${i + 1}.jpg`
return (
<ToggleButton
key={i}
variant="bigSquare"
aria-label={tooltipLabel(ProcessorType.VIRTUAL, {
imagePath,
})}
tooltip={tooltipLabel(ProcessorType.VIRTUAL, {
imagePath,
})}
isDisabled={processorPendingReveal}
onChange={async () =>
await toggleEffect(ProcessorType.VIRTUAL, {
imagePath,
})
}
isSelected={isSelected(ProcessorType.VIRTUAL, {
imagePath,
})}
className={css({
bgSize: 'cover',
})}
style={{
backgroundImage: `url(${thumbnailPath})`,
}}
data-attr={`toggle-virtual-${i}`}
/>
)
})}
</div>
</div>
</div>
<Information className={css({ marginTop: '1rem' })}>
<Text variant="sm"> {t('experimental')}</Text>
</Information>
</>
) : (
<Information>
@@ -1,120 +0,0 @@
import { css } from '@/styled-system/css'
import { H, ToggleButton } from '@/primitives'
import { ProcessorType } from '../blur'
import { RiGlassesLine, RiGoblet2Fill } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { FaceLandmarksProcessor } from '../blur/FaceLandmarksProcessor'
import { LocalVideoTrack } from 'livekit-client'
export type FunnyEffectsProps = {
videoTrack: LocalVideoTrack
isPending?: boolean
onPending: (value: boolean) => void
}
export const FunnyEffects = ({
videoTrack,
isPending,
onPending,
}: FunnyEffectsProps) => {
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
const getOptions = () => {
const processor = videoTrack?.getProcessor() as FaceLandmarksProcessor
if (!processor || processor.type != ProcessorType.FACE_LANDMARKS) {
return {
showGlasses: false,
showFrench: false,
}
}
return processor.serialize().options
}
const options = getOptions()
const toggleFaceLandmarkEffect = async (
showEffect: 'showGlasses' | 'showFrench'
) => {
const options = getOptions()
const processor = videoTrack?.getProcessor() as FaceLandmarksProcessor
const newOptions = {
...options,
[showEffect]: !options[showEffect],
}
onPending(true)
try {
if (!newOptions.showGlasses && !newOptions.showFrench) {
await videoTrack.stopProcessor()
} else if (options.showGlasses || options.showFrench) {
await processor?.update(newOptions)
} else {
const newProcessor = new FaceLandmarksProcessor(newOptions)
await videoTrack.setProcessor(newProcessor)
}
} catch (e) {
console.error('could not update processor', e)
} finally {
onPending(false)
}
}
const getLabelAction = (enabled: boolean) => (enabled ? 'clear' : 'apply')
return (
<div
className={css({
marginBottom: '1.5rem',
})}
>
<H
lvl={3}
style={{
marginBottom: '1rem',
}}
variant="bodyXsBold"
>
{t('faceLandmarks.title')}
</H>
<div
className={css({
display: 'flex',
gap: '1.25rem',
})}
>
<ToggleButton
variant="bigSquare"
aria-label={t(
`faceLandmarks.glasses.${getLabelAction(options.showGlasses)}`
)}
tooltip={t(
`faceLandmarks.glasses.${getLabelAction(options.showGlasses)}`
)}
isDisabled={isPending}
onChange={async () => await toggleFaceLandmarkEffect('showGlasses')}
isSelected={options.showGlasses}
data-attr="toggle-glasses"
>
<RiGlassesLine />
</ToggleButton>
<ToggleButton
variant="bigSquare"
aria-label={t(
`faceLandmarks.french.${getLabelAction(options.showFrench)}`
)}
tooltip={t(
`faceLandmarks.french.${getLabelAction(options.showFrench)}`
)}
isDisabled={isPending}
onChange={async () => await toggleFaceLandmarkEffect('showFrench')}
isSelected={options.showFrench}
data-attr="toggle-french"
>
<RiGoblet2Fill />
</ToggleButton>
</div>
</div>
)
}
@@ -0,0 +1,10 @@
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
import { FeatureFlags } from '@/features/analytics/enums'
export const useHasFaceLandmarksAccess = () => {
const featureEnabled = useFeatureFlagEnabled(FeatureFlags.faceLandmarks)
const isAnalyticsEnabled = useIsAnalyticsEnabled()
return featureEnabled || !isAnalyticsEnabled
}
@@ -1,22 +0,0 @@
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
import { FeatureFlags } from '@/features/analytics/enums'
import useKonami from '@/features/rooms/livekit/hooks/useKonami'
import { konamiStore } from '@/stores/konami'
import { useSnapshot } from 'valtio'
export const useHasFunnyEffectsAccess = () => {
const featureEnabled = useFeatureFlagEnabled(FeatureFlags.faceLandmarks)
const isAnalyticsEnabled = useIsAnalyticsEnabled()
const konamiSnap = useSnapshot(konamiStore)
useKonami(
() =>
(konamiStore.areFunnyEffectsEnabled = !konamiSnap.areFunnyEffectsEnabled)
)
return (
(featureEnabled || !isAnalyticsEnabled) && konamiSnap.areFunnyEffectsEnabled
)
}
@@ -1,35 +0,0 @@
/**
* Konami Code Detector Component
* This implementation is taken from: vmarchesin/react-konami-code
*/
import { useCallback, useEffect, useState } from 'react'
export const KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]
function useKonami(action: () => void, { code = KONAMI_CODE } = {}) {
const [input, setInput] = useState<number[]>([])
const onKeyUp = useCallback(
(e: KeyboardEvent) => {
const newInput = input
newInput.push(e.keyCode)
newInput.splice(-code.length - 1, input.length - code.length)
setInput(newInput)
if (newInput.join('').includes(code.join(''))) {
action()
}
},
[input, setInput, code, action]
)
useEffect(() => {
document.addEventListener('keyup', onKeyUp)
return () => {
document.removeEventListener('keyup', onKeyUp)
}
}, [onKeyUp])
}
export default useKonami
@@ -19,38 +19,23 @@ export function usePersistentUserChoices(
)
const saveAudioInputEnabled = React.useCallback((isEnabled: boolean) => {
setSettings((prev: LocalUserChoices) => ({
...prev,
audioEnabled: isEnabled,
}))
setSettings((prev) => ({ ...prev, audioEnabled: isEnabled }))
}, [])
const saveVideoInputEnabled = React.useCallback((isEnabled: boolean) => {
setSettings((prev: LocalUserChoices) => ({
...prev,
videoEnabled: isEnabled,
}))
setSettings((prev) => ({ ...prev, videoEnabled: isEnabled }))
}, [])
const saveAudioInputDeviceId = React.useCallback((deviceId: string) => {
setSettings((prev: LocalUserChoices) => ({
...prev,
audioDeviceId: deviceId,
}))
setSettings((prev) => ({ ...prev, audioDeviceId: deviceId }))
}, [])
const saveVideoInputDeviceId = React.useCallback((deviceId: string) => {
setSettings((prev: LocalUserChoices) => ({
...prev,
videoDeviceId: deviceId,
}))
setSettings((prev) => ({ ...prev, videoDeviceId: deviceId }))
}, [])
const saveUsername = React.useCallback((username: string) => {
setSettings((prev: LocalUserChoices) => ({ ...prev, username: username }))
setSettings((prev) => ({ ...prev, username: username }))
}, [])
const saveProcessorSerialized = React.useCallback(
(processorSerialized?: ProcessorSerialized) => {
setSettings((prev: LocalUserChoices) => ({
...prev,
processorSerialized,
}))
setSettings((prev) => ({ ...prev, processorSerialized }))
},
[]
)
@@ -3,17 +3,13 @@ import {
usePersistentUserChoices,
type LocalUserChoices as LocalUserChoicesLK,
} from '@livekit/components-react'
import { useLocation, useParams } from 'wouter'
import { useParams } from 'wouter'
import { ErrorScreen } from '@/components/ErrorScreen'
import { useUser, UserAware } from '@/features/auth'
import { Conference } from '../components/Conference'
import { Join } from '../components/Join'
import { useKeyboardShortcuts } from '@/features/shortcuts/useKeyboardShortcuts'
import { ProcessorSerialized } from '../livekit/components/blur'
import {
isRoomValid,
normalizeRoomId,
} from '@/features/rooms/utils/isRoomValid'
export type LocalUserChoices = LocalUserChoicesLK & {
processorSerialized?: ProcessorSerialized
@@ -25,7 +21,6 @@ export const Room = () => {
const [userConfig, setUserConfig] = useState<LocalUserChoices | null>(null)
const { roomId } = useParams()
const [location, setLocation] = useLocation()
const initialRoomData = history.state?.initialRoomData
const mode = isLoggedIn && history.state?.create ? 'create' : 'join'
const skipJoinScreen = isLoggedIn && mode === 'create'
@@ -45,12 +40,6 @@ export const Room = () => {
}
}, [])
useEffect(() => {
if (roomId && !isRoomValid(roomId)) {
setLocation(normalizeRoomId(roomId))
}
}, [roomId, setLocation, location])
if (!roomId) {
return <ErrorScreen />
}
@@ -1,17 +1,5 @@
export const roomIdPattern = '[a-z]{3}-[a-z]{4}-[a-z]{3}'
// Case-insensitive and with optional hyphens
export const flexibleRoomIdPattern =
'(?:[a-zA-Z0-9]{3}-?[a-zA-Z0-9]{4}-?[a-zA-Z0-9]{3})'
export const isRoomValid = (roomIdOrUrl: string) =>
new RegExp(`^${roomIdPattern}$`).test(roomIdOrUrl) ||
new RegExp(`^${window.location.origin}/${roomIdPattern}$`).test(roomIdOrUrl)
export const normalizeRoomId = (roomId: string) => {
const cleanId = roomId.toLowerCase().replace(/-/g, '')
if (cleanId.length === 10) {
return `${cleanId.slice(0, 3)}-${cleanId.slice(3, 7)}-${cleanId.slice(7, 10)}`
}
return roomId
}
+1 -1
View File
@@ -14,7 +14,7 @@ i18n
.use(initReactI18next)
.use(LanguageDetector)
.init({
supportedLngs: ['en', 'fr', 'nl', 'de'],
supportedLngs: ['en', 'fr', 'nl'],
fallbackLng: 'fr',
ns: i18nDefaultNamespace,
detection: {
+18 -18
View File
@@ -1,33 +1,33 @@
{
"accessibility": {
"title": "Barrierefreiheit",
"introduction": "<i>Visio</i> verpflichtet sich, seine digitalen Dienste barrierefrei zu gestalten, gemäß Artikel 47 des Gesetzes Nr. 2005-102 vom 11. Februar 2005.",
"title": "",
"introduction": "",
"declaration": {
"title": "Erklärung zur Barrierefreiheit",
"date": "Erstellt am 4. Dezember 2024."
"title": "",
"date": ""
},
"scope": "Diese Erklärung zur Barrierefreiheit gilt für die Website visio.numerique.gouv.fr",
"scope": "",
"complianceStatus": {
"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."
"title": "",
"body": ""
},
"improvement": {
"title": "Verbesserung und Kontakt",
"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.",
"title": "",
"body": "",
"contact": {
"email": "E-Mail: visio@numerique.gouv.fr",
"address": "Adresse: DINUM, 20 avenue de Ségur, 75007 Paris"
"email": "",
"address": ""
},
"response": "Wir bemühen uns, innerhalb von 2 Werktagen zu antworten."
"response": ""
},
"recourse": {
"title": "Rechtsbehelfe",
"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.",
"title": "",
"introduction": "",
"options": {
"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"
"intro": "",
"option1": "",
"option2": "",
"option3": ""
}
}
}
+33 -33
View File
@@ -1,55 +1,55 @@
{
"app": "Visio",
"backToHome": "Zurück zur Startseite",
"cancel": "Abbrechen",
"closeDialog": "Dialogfenster schließen",
"backToHome": "",
"cancel": "",
"closeDialog": "",
"error": {
"heading": "Beim Laden der Seite ist ein Fehler aufgetreten"
"heading": ""
},
"feedback": {
"context": "Produkt in Entwicklung — Ihr Feedback ist wichtig!",
"cta": "Teilen Sie uns Ihre Meinung mit"
"context": "",
"cta": ""
},
"forbidden": {
"heading": "Zugriff verweigert"
"heading": ""
},
"loading": "Ladevorgang…",
"loggedInUserTooltip": "Angemeldet als…",
"loading": "",
"loggedInUserTooltip": "",
"login": {
"buttonLabel": "Mit ProConnect anmelden",
"linkLabel": "Was ist ProConnect? neues Fenster",
"link": "Was ist ProConnect?"
"buttonLabel": "",
"linkLabel": "",
"link": ""
},
"logout": "Abmelden",
"logout": "",
"notFound": {
"heading": "Überprüfen Sie Ihren Meeting-Code",
"body": "Stellen Sie sicher, dass Sie den richtigen Meeting-Code in der URL eingegeben haben. Beispiel:"
"heading": "",
"body": ""
},
"submit": "OK",
"footer": {
"links": {
"legifrance": "legifrance.gouv.fr",
"infogouv": "info.gouv.fr",
"servicepublic": "service-public.fr",
"datagouv": "data.gouv.fr",
"legalsTerms": "Rechtliche Hinweise",
"data": "Datenschutz und Cookies",
"accessibility": "Barrierefreiheit: nicht konform",
"ariaLabel": "neues Fenster",
"codeAnnotation": "Unser Code ist offen und verfügbar in diesem",
"code": "Open-Source-Code-Repository",
"technicalDetails": "Technisches Datenblatt",
"termsOfService": "Nutzungsbedingungen"
"legifrance": "",
"infogouv": "",
"servicepublic": "",
"datagouv": "",
"legalsTerms": "",
"data": "",
"accessibility": "",
"ariaLabel": "",
"codeAnnotation": "",
"code": "",
"technicalDetails": "",
"termsOfService": ""
},
"mentions": "Sofern nicht anders angegeben, sind die Inhalte dieser Website verfügbar unter der",
"license": "Etalab 2.0 Lizenz"
"mentions": "",
"license": ""
},
"loginHint": {
"title": "Melden Sie sich mit Ihrem ProConnect-Konto an",
"body": "Statt zu warten, melden Sie sich mit Ihrem ProConnect-Konto an.",
"title": "",
"body": "",
"button": {
"ariaLabel": "Hinweis schließen",
"label": "OK"
"ariaLabel": "",
"label": ""
}
}
}
+38 -38
View File
@@ -1,56 +1,56 @@
{
"createMeeting": "Meeting erstellen",
"heading": "Einfache und sichere Videokonferenzen",
"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": "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 Visio",
"createMeeting": "",
"heading": "",
"intro": "",
"joinInputError": "",
"joinInputExample": "",
"joinInputLabel": "",
"joinInputSubmit": "",
"joinMeeting": "",
"joinMeetingTipContent": "",
"joinMeetingTipHeading": "",
"loginToCreateMeeting": "",
"moreLinkLabel": "",
"moreLink": "",
"moreAbout": "",
"createMenu": {
"laterOption": "Meeting für später planen",
"instantOption": "Sofort-Meeting starten"
"laterOption": "",
"instantOption": ""
},
"laterMeetingDialog": {
"heading": "Ihre Zugangsdaten",
"description": "Senden Sie diesen Link an die Personen, die Sie zum Meeting einladen möchten. Sie können ohne ProConnect teilnehmen.",
"copy": "Meeting-Link kopieren",
"copied": "Link in die Zwischenablage kopiert",
"permissions": "Personen mit diesem Link benötigen keine Genehmigung, um diesem Meeting beizutreten."
"heading": "",
"description": "",
"copy": "",
"copied": "",
"permissions": ""
},
"introSlider": {
"previous": {
"label": "Zurück",
"tooltip": "Zurück"
},
"next": {
"label": "Weiter",
"tooltip": "Weiter"
"label": "",
"tooltip": ""
},
"beta": {
"text": "An der Beta teilnehmen",
"tooltip": "Formular ausfüllen"
"text": "",
"tooltip": ""
},
"next": {
"label": "",
"tooltip": ""
},
"slide1": {
"title": "Testen Sie Visio für Ihre täglichen Aufgaben",
"body": "Entdecken Sie eine intuitive und zugängliche Lösung, entwickelt für alle Mitarbeitenden im öffentlichen Dienst, ihre Partner und viele weitere.",
"imgAlt": "Illustration einer benutzerfreundlichen und barrierefreien Kollaborationsplattform"
"title": "",
"body": "",
"imgAlt": ""
},
"slide2": {
"title": "Gruppenanrufe ohne Einschränkungen",
"body": "Unbegrenzte Meeting-Dauer mit flüssiger, hochwertiger Kommunikation unabhängig von der Gruppengröße.",
"imgAlt": "Bild eines virtuellen Meetings mit mehreren nahtlos zusammenarbeitenden Teilnehmenden"
"title": "",
"body": "",
"imgAlt": ""
},
"slide3": {
"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!",
"imgAlt": "Illustration KI-gestützter Notizen in einem virtuellen Meeting"
"title": "",
"body": "",
"imgAlt": ""
}
}
}
+23 -23
View File
@@ -1,37 +1,37 @@
{
"title": "Rechtliche Hinweise",
"title": "",
"creator": {
"title": "Herausgeber",
"body": "Der Visio-Dienst wird herausgegeben von der Interministeriellen Digitaldirektion des Staates (DINUM) mit Sitz:",
"title": "",
"body": "",
"contact": {
"title": "Kontaktdaten",
"address": "20 avenue de Ségur",
"city": "75007 Paris",
"phone": "Tel. Zentrale: 01.71.21.01.70",
"siret": "SIRET: 12000101100010 (Generalsekretariat der Regierung)",
"siren": "SIREN: 120 001 011"
"title": "",
"address": "",
"city": "",
"phone": "",
"siret": "",
"siren": ""
}
},
"director": {
"title": "Verantwortliche für die Veröffentlichung",
"body": "Die Verantwortliche für die Veröffentlichung ist Frau Stéphanie Schaer, Interministerielle Direktorin für Digitalisierung."
"title": "",
"body": ""
},
"hosting": {
"title": "Hosting",
"body": "Der Dienst wird gehostet von Outscale West-1 CloudGouv SecNumCloud mit Standort in Frankreich."
"title": "",
"body": ""
},
"accessibility": {
"title": "Barrierefreiheit",
"body": "Die Einhaltung der Standards für digitale Barrierefreiheit ist ein zukünftiges Ziel. Die Website wurde noch nicht auditiert. Das Team bemüht sich jedoch, eine für alle zugängliche Website zu erstellen, unter Berücksichtigung der Empfehlungen des RGAA.",
"more": "Mehr erfahren: ",
"link": "Link zur Seite über Barrierefreiheit",
"status": "Barrierefreiheitsstatus: nicht konform"
"title": "",
"body": "",
"more": "",
"link": "",
"status": ""
},
"reuse": {
"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 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."
"title": "",
"body1": "",
"license": "",
"body2": "",
"body3": ""
}
}
+17 -17
View File
@@ -1,33 +1,33 @@
{
"defaultName": "Ein Mitwirkender",
"defaultName": "",
"joined": {
"description": "{{name}} ist dem Raum beigetreten"
"description": ""
},
"raised": {
"description": "{{name}} hat die Hand gehoben.",
"cta": "Warteliste öffnen"
"description": "",
"cta": ""
},
"muted": "{{name}} hat dein Mikrofon stummgeschaltet. Kein Teilnehmer kann dich hören.",
"openChat": "Chat öffnen",
"muted": "",
"openChat": "",
"lowerHand": {
"auto": "Es scheint, dass du angefangen hast zu sprechen, daher wird deine Hand gesenkt.",
"dismiss": "Hand oben lassen"
"auto": "",
"dismiss": ""
},
"reaction": {
"description": "{{name}} hat mit {{emoji}} reagiert"
"description": ""
},
"waitingParticipants": {
"one": "Eine Person möchte diesem Anruf beitreten.",
"several": "Mehrere Personen möchten diesem Anruf beitreten.",
"open": "Öffnen",
"accept": "Annehmen"
"one": "",
"several": "",
"open": "",
"accept": ""
},
"transcript": {
"started": "{{name}} hat die Transkription des Treffens gestartet.",
"stopped": "{{name}} hat die Transkription des Treffens gestoppt."
"started": "",
"stopped": ""
},
"screenRecording": {
"started": "{{name}} hat die Aufzeichnung des Treffens gestartet.",
"stopped": "{{name}} hat die Aufzeichnung des Treffens gestoppt."
"started": "",
"stopped": ""
}
}
+12 -12
View File
@@ -1,24 +1,24 @@
{
"error": {
"title": "Aufzeichnung nicht verfügbar",
"body": "Diese Aufzeichnung konnte nicht gefunden werden oder wurde gelöscht."
"title": "",
"body": ""
},
"expired": {
"title": "Aufzeichnung abgelaufen",
"body": "Diese Aufzeichnung wurde am {{date}} gelöscht."
"title": "",
"body": ""
},
"authentication": {
"title": "Authentifizierung erforderlich",
"body": "Bitte melden Sie sich an, um auf diese Aufzeichnung zuzugreifen."
"title": "",
"body": ""
},
"unsaved": {
"title": "Download nicht verfügbar",
"body": "Diese Aufzeichnung ist noch nicht zum Herunterladen bereit. Bitte versuchen Sie es später erneut."
"title": "",
"body": ""
},
"success": {
"title": "Ihre Aufzeichnung ist bereit!",
"body": "Aufzeichnung des Treffens <b>{{room}}</b> vom {{created_at}}.",
"expiration": "Achtung, diese Aufzeichnung wird nach {{expiration_days}} Tag(en) gelöscht.",
"button": "Herunterladen"
"title": "",
"body": "",
"expiration": "",
"button": ""
}
}
+207 -207
View File
@@ -1,145 +1,146 @@
{
"feedback": {
"heading": "Sie haben das Meeting verlassen",
"home": "Zur Startseite zurückkehren",
"back": "Dem Meeting erneut beitreten"
"heading": "",
"home": "",
"back": ""
},
"join": {
"videoinput": {
"choose": "Kamera auswählen",
"disable": "Kamera deaktivieren",
"enable": "Kamera aktivieren",
"label": "Kamera",
"placeholder": "Kamera aktivieren, um die Vorschau zu sehen"
"choose": "",
"disable": "",
"enable": "",
"label": "",
"placeholder": ""
},
"audioinput": {
"choose": "Mikrofon auswählen",
"disable": "Mikrofon deaktivieren",
"enable": "Mikrofon aktivieren",
"label": "Mikrofon"
"choose": "",
"disable": "",
"enable": "",
"label": ""
},
"heading": "",
"effects": {
"description": "Effekte anwenden",
"title": "Effekte",
"subTitle": "Konfigurieren Sie die Effekte Ihrer Kamera."
"description": "",
"title": "",
"subTitle": ""
},
"heading": "Dem Meeting beitreten?",
"joinLabel": "Beitreten",
"joinMeeting": "Meeting beitreten",
"toggleOff": "Klicken, um auszuschalten",
"toggleOn": "Klicken, um einzuschalten",
"usernameHint": "Wird anderen Teilnehmern angezeigt",
"usernameLabel": "Ihr Name",
"joinLabel": "",
"joinMeeting": "",
"toggleOff": "",
"toggleOn": "",
"usernameHint": "",
"usernameLabel": "",
"errors": {
"usernameEmpty": "Ihr Name darf nicht leer sein"
"usernameEmpty": ""
},
"cameraDisabled": "Kamera ist deaktiviert.",
"cameraStarting": "Kamera wird gestartet.",
"cameraDisabled": "",
"cameraStarting": "",
"waiting": {
"title": "Beitrittsanfrage wird gesendet...",
"body": "Sie können diesem Anruf beitreten, sobald jemand Sie autorisiert"
"title": "",
"body": ""
},
"denied": {
"title": "Sie können diesem Anruf nicht beitreten",
"body": "Ihre Beitrittsanfrage wurde abgelehnt."
"title": "",
"body": ""
},
"timeoutInvite": {
"title": "Sie können diesem Anruf nicht beitreten",
"body": "Niemand hat auf Ihre Anfrage reagiert"
"title": "",
"body": ""
}
},
"leaveRoomPrompt": "Dadurch verlassen Sie das Meeting.",
"leaveRoomPrompt": "",
"shareDialog": {
"copy": "Meeting-Link kopieren",
"copyButton": "Link kopieren",
"copied": "Link in die Zwischenablage 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."
"copy": "",
"copied": "",
"heading": "",
"description": "",
"permissions": ""
},
"error": {
"createRoom": {
"heading": "Authentifizierung erforderlich",
"body": "Dieser Raum wurde noch nicht erstellt. Bitte authentifizieren Sie sich, um ihn zu erstellen, oder warten Sie, bis ein authentifizierter Benutzer dies tut."
"heading": "",
"body": ""
},
"screenShare": {
"title": "Bildschirmfreigabe nicht möglich",
"ariaLabel": "Bildschirmfreigabe nicht möglich",
"message": "Ihr Browser darf möglicherweise den Bildschirm Ihres Computers nicht aufzeichnen.",
"macInstructions": "Gehen Sie zu Ihren",
"macSystemPreferences": "Systemeinstellungen",
"helpLinkText": "Weitere Informationen finden Sie unter",
"helpLinkLabel": "Präsentationsproblem",
"closeButton": "Schließen",
"newTab": "Neues Fenster"
"title": "",
"ariaLabel": "",
"message": "",
"macInstructions": "",
"macSystemPreferences": "",
"helpLinkText": "",
"helpLinkLabel": "",
"closeButton": "",
"newTab": ""
}
},
"controls": {
"microphone": "Mikrofon",
"camera": "Kamera",
"microphone": "",
"camera": "",
"shareScreen": "",
"stopScreenShare": "",
"chat": {
"open": "Chat schließen",
"closed": "Chat öffnen",
"open": "",
"closed": "",
"input": {
"textArea": {
"label": "Nachricht eingeben",
"placeholder": "Nachricht eingeben"
"label": "",
"placeholder": ""
},
"button": {
"label": "Nachricht senden"
"label": ""
}
}
},
"info": {
"open": "Informationen ausblenden",
"closed": "Informationen anzeigen"
"open": "",
"closed": ""
},
"hand": {
"raise": "Hand heben",
"lower": "Hand senken"
"raise": "",
"lower": ""
},
"screenShare": {
"start": "Bildschirm freigeben",
"stop": "Bildschirmfreigabe beenden"
"start": "",
"stop": ""
},
"leave": "Verlassen",
"leave": "",
"participants": {
"open": "Alle ausblenden",
"closed": "Alle anzeigen"
"open": "",
"closed": ""
},
"tools": {
"open": "Weitere Tools ausblenden",
"closed": "Weitere Tools anzeigen"
"open": "",
"closed": ""
},
"admin": {
"open": "Admin ausblenden",
"closed": "Admin öffnen"
"open": "",
"closed": ""
},
"moreOptions": "Weitere Optionen",
"moreOptions": "",
"reactions": {
"button": "Reaktion senden",
"send": "Reaktion {{emoji}} senden",
"you": "Sie"
"button": "",
"send": "",
"you": ""
}
},
"options": {
"buttonLabel": "Weitere Optionen",
"buttonLabel": "",
"items": {
"feedback": "Feedback geben",
"settings": "Einstellungen",
"username": "Ihren Namen aktualisieren",
"effects": "Effekte anwenden",
"switchCamera": "Kamera wechseln",
"feedback": "",
"settings": "",
"username": "",
"effects": "",
"switchCamera": "",
"fullscreen": {
"enter": "Vollbild",
"exit": "Vollbildmodus verlassen"
"enter": "",
"exit": ""
},
"support": "Support kontaktieren"
"support": ""
}
},
"effects": {
"activateCamera": "Ihre Kamera ist deaktiviert. Wählen Sie eine Option, um sie zu aktivieren.",
"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 :(",
"notAvailable": "Videoeffekte werden in Kürze in Ihrem Browser verfügbar sein. Wir arbeiten daran! In der Zwischenzeit können Sie Google Chrome für beste Leistung oder Firefox verwenden :(",
"heading": "Unschärfe",
"blur": {
"title": "Hintergrundunschärfe",
@@ -160,227 +161,226 @@
"clear": "Brille entfernen"
},
"french": {
"apply": "Französischen Touch hinzufügen",
"clear": "Französischen Touch entfernen"
"apply": "Französische Touch hinzufügen",
"clear": "Französische Touch entfernen"
}
}
},
"experimental": "Experimentelle Funktion. Eine v2 kommt für vollständige Browserunterstützung und verbesserte Qualität."
},
"sidePanel": {
"heading": {
"participants": "Teilnehmer",
"effects": "Effekte",
"chat": "Nachrichten im Chat",
"transcript": "Transkription",
"screenRecording": "Aufzeichnung",
"admin": "Admin-Einstellungen",
"tools": "Weitere Tools",
"info": "Meeting-Informationen"
"participants": "",
"effects": "",
"chat": "",
"transcript": "",
"screenRecording": "",
"admin": "",
"tools": "",
"info": ""
},
"content": {
"participants": "Teilnehmer",
"effects": "Effekte",
"chat": "Nachrichten",
"transcript": "Transkription",
"screenRecording": "Aufzeichnung",
"admin": "Admin-Einstellungen",
"tools": "Weitere Tools",
"info": "Meeting-Informationen"
"participants": "",
"effects": "",
"chat": "",
"transcript": "",
"screenRecording": "",
"admin": "",
"tools": "",
"info": ""
},
"closeButton": "{{content}} ausblenden"
"closeButton": ""
},
"chat": {
"disclaimer": "Die Nachrichten sind nur für Teilnehmer zum Zeitpunkt des Sendens sichtbar. Alle Nachrichten werden am Ende des Anrufs gelöscht."
"disclaimer": ""
},
"moreTools": {
"body": "Greifen Sie auf weitere Tools in Visio zu, um Ihre Meetings zu verbessern,",
"moreLink": "mehr erfahren",
"body": "",
"moreLink": "",
"tools": {
"transcript": {
"title": "Transkription",
"body": "Führen Sie ein schriftliches Protokoll Ihres Meetings."
"title": "",
"body": ""
},
"screenRecording": {
"title": "Aufzeichnung",
"body": "Zeichnen Sie Ihr Meeting auf, um es später erneut anzusehen."
"title": "",
"body": ""
}
}
},
"info": {
"roomInformation": {
"title": "Verbindungsinformationen",
"title": "",
"button": {
"ariaLabel": "Kopiere deine Meeting-Adresse",
"copy": "Adresse kopieren",
"copied": "Adresse kopiert"
"ariaLabel": "",
"copy": "",
"copied": ""
}
}
},
"transcript": {
"start": {
"heading": "Dieses Gespräch transkribieren",
"body": "Dieses Gespräch automatisch transkribieren und die Zusammenfassung in Docs erhalten.",
"button": "Transkription starten",
"loading": "Transkription wird gestartet",
"linkMore": "Mehr erfahren"
"heading": "",
"body": "",
"button": "",
"linkMore": ""
},
"stop": {
"heading": "Transkription läuft...",
"body": "Die Transkription deines Meetings läuft. Du erhältst das Ergebnis per E-Mail, sobald das Meeting beendet ist.",
"button": "Transkription stoppen"
"heading": "",
"body": "",
"button": ""
},
"stopping": {
"heading": "Daten werden gespeichert…",
"body": "Dieser Vorgang kann einige Minuten dauern. Danke für deine Geduld."
"heading": "",
"body": ""
},
"beta": {
"heading": "Werde Beta-Tester",
"body": "Zeichne dein Meeting auf. Du erhältst eine Zusammenfassung per E-Mail nach dem Meeting.",
"button": "Anmelden"
"heading": "",
"body": "",
"button": ""
},
"alert": {
"title": "Transkription fehlgeschlagen",
"title": "",
"body": {
"stop": "Die Transkription konnte nicht gestoppt werden. Bitte versuche es in einem Moment erneut.",
"start": "Die Transkription konnte nicht gestartet werden. Bitte versuche es in einem Moment erneut."
"stop": "",
"start": ""
},
"button": "OK"
"button": ""
}
},
"screenRecording": {
"start": {
"heading": "Dieses Gespräch aufzeichnen",
"body": "Zeichne dieses Gespräch auf, um es später anzusehen. Du erhältst die Videoaufnahme per E-Mail.",
"button": "Aufzeichnung starten",
"loading": "Aufzeichnung wird gestartet",
"linkMore": "Mehr erfahren"
"heading": "",
"body": "",
"button": "",
"linkMore": ""
},
"stopping": {
"heading": "Daten werden gespeichert…",
"body": "Dieser Vorgang kann einige Minuten dauern. Danke für deine Geduld."
"heading": "",
"body": ""
},
"stop": {
"heading": "Aufzeichnung läuft…",
"body": "Du erhältst das Ergebnis per E-Mail, sobald die Aufzeichnung abgeschlossen ist.",
"button": "Aufzeichnung stoppen"
"heading": "",
"body": "",
"button": ""
},
"alert": {
"title": "Aufzeichnung fehlgeschlagen",
"title": "",
"body": {
"stop": "Die Aufzeichnung konnte nicht gestoppt werden. Bitte versuche es in einem Moment erneut.",
"start": "Die Aufzeichnung konnte nicht gestartet werden. Bitte versuche es in einem Moment erneut."
"stop": "",
"start": ""
},
"button": "OK"
"button": ""
}
},
"admin": {
"description": "Diese Einstellungen für Organisatoren ermöglichen dir die Kontrolle über dein Meeting. Nur Organisatoren haben Zugriff auf diese Optionen.",
"description": "",
"access": {
"title": "Raumzugang",
"description": "Diese Einstellungen gelten auch für zukünftige Ausgaben dieses Meetings.",
"type": "Zugangsarten für Meetings",
"title": "",
"description": "",
"type": "",
"levels": {
"public": {
"label": "Offen",
"description": "Niemand muss um Zugang zum Meeting bitten."
"label": "",
"description": ""
},
"trusted": {
"label": "Offen für vertrauenswürdige Personen",
"description": "Authentifizierte Personen müssen nicht um Zugang bitten."
"label": "",
"description": ""
},
"restricted": {
"label": "Eingeschränkt",
"description": "Nicht eingeladene Personen müssen um Zugang bitten."
"label": "",
"description": ""
}
}
}
},
"rating": {
"submit": "Absenden",
"question": "Wie bewertest du die Qualität deines Gesprächs?",
"submit": "",
"question": "",
"levels": {
"min": "sehr schlecht",
"max": "ausgezeichnet"
"min": "",
"max": ""
}
},
"openFeedback": {
"question": "Was können wir tun, um Visio zu verbessern?",
"placeholder": "Beschreibe deine Fehler oder teile deine Vorschläge…",
"submit": "Absenden",
"skip": "Überspringen"
"question": "",
"placeholder": "",
"submit": "",
"skip": ""
},
"confirmationMessage": {
"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."
"heading": "",
"body": ""
},
"authenticationMessage": {
"heading": "Wie können wir dich kontaktieren?",
"placeholder": "Deine E-Mail-Adresse",
"submit": "Senden",
"ignore": "Ignorieren"
"heading": "",
"placeholder": "",
"submit": "",
"ignore": ""
},
"participants": {
"subheading": "Im Raum",
"you": "Du",
"contributors": "Mitwirkende",
"subheading": "",
"contributors": "",
"collapsable": {
"open": "{{name}}-Liste öffnen",
"close": "{{name}}-Liste schließen"
"open": "",
"close": ""
},
"muteYourself": "Mikrofon ausschalten",
"muteParticipant": "{{name}}s Mikrofon ausschalten",
"you": "",
"muteYourself": "",
"muteParticipant": "",
"muteParticipantAlert": {
"heading": "{{name}} stummschalten",
"description": "{{name}} für alle Teilnehmer stummschalten? {{name}} kann sich nur selbst wieder einschalten.",
"confirm": "Stummschalten",
"cancel": "Abbrechen"
"heading": "",
"description": "",
"confirm": "",
"cancel": ""
},
"raisedHands": "Meldungen",
"lowerParticipantHand": "{{name}}s Hand senken",
"lowerParticipantsHand": "Alle Hände senken",
"raisedHands": "",
"lowerParticipantHand": "",
"lowerParticipantsHand": "",
"waiting": {
"title": "Warteraum",
"title": "",
"accept": {
"button": "Zulassen",
"label": "{{name}} zum Meeting zulassen",
"all": "Alle zulassen"
"button": "",
"label": "",
"all": ""
},
"deny": {
"button": "Ablehnen",
"label": "{{name}} vom Meeting ablehnen",
"all": "Alle ablehnen"
"button": "",
"label": "",
"all": ""
}
}
},
"recordingStateToast": {
"recordingBadge": {
"transcript": {
"started": "Transkription läuft",
"starting": "Transkription wird gestartet",
"stopping": "Transkription wird gestoppt"
"started": "",
"starting": "",
"stopping": ""
},
"screenRecording": {
"started": "Aufzeichnung läuft",
"starting": "Aufzeichnung wird gestartet",
"stopping": "Aufzeichnung wird gestoppt"
"started": "",
"starting": "",
"stopping": ""
},
"any": {
"started": "Aufzeichnung läuft"
"started": ""
}
},
"participantTileFocus": {
"pin": {
"enable": "Anheften",
"disable": "Lösen"
"enable": "",
"disable": ""
},
"effects": "Visuelle Effekte anwenden",
"muteParticipant": "{{name}} stummschalten",
"fullScreen": "Vollbild"
"effects": "",
"muteParticipant": "",
"fullScreen": ""
},
"fullScreenWarning": {
"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"
"message": "",
"stop": "",
"ignore": ""
},
"participantTile": {
"screenShare": "{{name}}s Bildschirm"
"screenShare": ""
}
}
+6 -6
View File
@@ -1,10 +1,10 @@
{
"createMeeting": {
"createButton": "Erstellen",
"joinButton": "Mit Visio beitreten",
"copyLinkTooltip": "Link kopieren",
"resetLabel": "Zurücksetzen",
"participantLimit": "Bis zu 150 Teilnehmer.",
"popupBlocked": "Popup wurde blockiert. Bitte erlauben Sie Popups für diese Website."
"createButton": "",
"joinButton": "",
"copyLinkTooltip": "",
"resetLabel": "",
"participantLimit": "",
"popupBlocked": ""
}
}
+27 -27
View File
@@ -1,49 +1,49 @@
{
"account": {
"currentlyLoggedAs": "Sie sind derzeit angemeldet als <0>{{user}}</0>",
"heading": "Konto",
"youAreNotLoggedIn": "Sie sind nicht angemeldet.",
"nameLabel": "Ihr Name",
"authentication": "Authentifizierung"
"currentlyLoggedAs": "",
"heading": "",
"youAreNotLoggedIn": "",
"nameLabel": "",
"authentication": ""
},
"audio": {
"microphone": {
"heading": "Mikrofon",
"label": "Wählen Sie Ihre Audioeingabe"
"heading": "",
"label": ""
},
"speakers": {
"heading": "Lautsprecher",
"label": "Wählen Sie Ihre Audioausgabe",
"test": "Testen",
"ongoingTest": "Soundtest läuft…"
"heading": "",
"label": "",
"test": "",
"ongoingTest": ""
},
"permissionsRequired": "Berechtigungen erforderlich"
"permissionsRequired": ""
},
"notifications": {
"heading": "Tonbenachrichtigungen",
"label": "Tonbenachrichtigungen für",
"heading": "",
"label": "",
"actions": {
"disable": "Deaktivieren",
"enable": "Aktivieren"
"disable": "",
"enable": ""
},
"items": {
"participantJoined": "Teilnehmer beigetreten",
"handRaised": "Hand gehoben",
"messageReceived": "Nachricht erhalten"
"participantJoined": "",
"handRaised": "",
"messageReceived": ""
}
},
"dialog": {
"heading": "Einstellungen"
"heading": ""
},
"language": {
"heading": "Sprache",
"label": "Sprache"
"heading": "",
"label": ""
},
"settingsButtonLabel": "Einstellungen",
"settingsButtonLabel": "",
"tabs": {
"account": "Profil",
"audio": "Audio",
"general": "Allgemein",
"notifications": "Benachrichtigungen"
"account": "",
"audio": "",
"general": "",
"notifications": ""
}
}
+34 -56
View File
@@ -1,94 +1,72 @@
{
"title": "Nutzungsbedingungen für Visio",
"title": "",
"articles": {
"article1": {
"title": "1. Geltungsbereich",
"content": "Dieses Dokument definiert die Nutzungsbedingungen für Visio, den Videokonferenzdienst der Verwaltung."
"title": "",
"content": ""
},
"article2": {
"title": "2. Zweck der Plattform",
"content": "Der Dienst fördert die Zusammenarbeit im Team und das Arbeiten aus der Ferne und erleichtert die Organisation von Besprechungen, Konferenzen oder Schulungen.",
"purposes": "Visio wird von DINUM entwickelt und betrieben. Jede Nutzung des Dienstes muss mit diesen Nutzungsbedingungen übereinstimmen."
"title": "",
"content": "",
"purposes": ""
},
"article3": {
"title": "3. Zugang zum Dienst",
"definition": "Visio wird öffentlichen Bediensteten zur Verfügung gestellt, die über ProConnect verbunden sind, um einen Videokonferenzraum zu verwalten. Der verbundene öffentliche Bedienstete kann dann einen Link übermitteln, der den Zugang zum Raum unter seiner Verantwortung ermöglicht."
"title": "",
"definition": ""
},
"article4": {
"title": "4. Nutzung",
"content": "Die Nutzung des Dienstes ist kostenlos. Um einen Videokonferenzraum zu verwalten, ist eine Verbindung über ProConnect erforderlich."
"title": "",
"content": ""
},
"article5": {
"title": "5. Funktionen",
"title": "",
"sections": {
"section1": {
"title": "5.1. Funktionen für öffentliche Bedienstete",
"content": "Visio bietet öffentlichen Bediensteten die Möglichkeit, von einem Standardarbeitsplatz aus einen Videokonferenzraum zu erstellen, der mit ihrem ProConnect-Konto verknüpft ist.",
"paragraph1": "Jeder Staatsbedienstete, dessen Tätigkeiten mit der Nutzung von Visio vereinbar sind oder der keinen spezifischen Vertraulichkeitsverpflichtungen oder dem Berufsgeheimnis unterliegt, kann eine Videokonferenz erstellen. Er kann dann den Besprechungslink mit anderen Teilnehmern teilen, unabhängig davon, ob sie Teil der Verwaltung sind oder nicht.",
"paragraph2": "Zu diesem Zweck kann jeder Raumorganisator:",
"capabilities": [
"Den Zugang zum Raum mit einem Warteraum schützen;",
"Den Eintritt jedes Teilnehmers in den Raum autorisieren;",
"Die Mikrofone und Kameras der Teilnehmer stummschalten;",
"Einen Teilnehmer aus dem Raum entfernen."
],
"paragraph3": "Nur Bedienstete, deren Tätigkeitsbereich zur Nutzung von Visio berechtigt ist oder die <u>keinen spezifischen Vertraulichkeitsverpflichtungen oder dem Berufsgeheimnis unterliegen</u>, dürfen diesen Dienst und seine Funktionen nutzen oder kontrollieren."
"title": "",
"content": "",
"paragraph1": "",
"paragraph2": "",
"capabilities": ["", "", "", ""],
"paragraph3": ""
},
"section2": {
"title": "5.2. Funktionen für Nutzer",
"content": "Visio ermöglicht es jedem Nutzer, ob öffentlicher Bediensteter oder nicht, an jedem Visio-Raum teilzunehmen, zu dem er von einem Staatsbediensteten eingeladen wurde, und alle Funktionen zu nutzen.",
"paragraph": "Zu diesem Zweck kann jeder Teilnehmer:",
"capabilities": [
"Seine angezeigte Identität beim Betreten des Raums festlegen;",
"Über Audio und/oder Video aus der Ferne kommunizieren;",
"Seinen Bildschirm teilen;",
"Während der Sitzung öffentlich mit allen Nachrichten austauschen;",
"Informationen über die Konferenz einsehen;",
"Seine Kamera und/oder sein Mikrofon jederzeit ein- oder ausschalten;",
"Die Privatsphäre seines Arbeitsplatzes schützen, indem er vor dem Betreten des Raums oder sogar während der Videokonferenz einen Hintergrund auswählt und aktiviert."
]
"title": "",
"content": "",
"paragraph": "",
"capabilities": ["", "", "", "", "", "", ""]
}
}
},
"article6": {
"title": "6. Verpflichtungen und Verantwortlichkeiten der Nutzer",
"title": "",
"sections": {
"section1": {
"title": "6.1. Konforme Nutzung",
"paragraphs": [
"Der Dienst wird öffentlichen Bediensteten über ProConnect zur Verfügung gestellt.",
"Der Nutzer ist für die Daten oder Inhalte verantwortlich, die er im Nachrichtendienst des Videokonferenzraums eingibt. Er muss sicherstellen, dass er nur angemessene Nachrichten eingibt."
]
"title": "",
"paragraphs": ["", ""]
},
"section2": {
"title": "6.2. Verbotene Nutzung",
"paragraphs": [
"Der Nutzer verpflichtet sich, keine Inhalte oder Informationen in den Nachrichtendienst des Visio-Raums einzugeben, die gegen geltende gesetzliche und regulatorische Bestimmungen verstoßen."
]
"title": "",
"paragraphs": [""]
}
}
},
"article7": {
"title": "7. Verpflichtungen und Verantwortlichkeiten von DINUM",
"title": "",
"sections": {
"section1": {
"title": "7.1. Sicherheit und Plattformzugang",
"content": "DINUM verpflichtet sich, Visio zu sichern, insbesondere durch alle notwendigen Maßnahmen zur Gewährleistung der Sicherheit und Vertraulichkeit der bereitgestellten Informationen.",
"paragraphs": [
"DINUM verpflichtet sich, die notwendigen und angemessenen Mittel bereitzustellen, um einen kontinuierlichen Zugang zu Visio zu gewährleisten.",
"DINUM behält sich das Recht vor, den Dienst aus Wartungsgründen oder aus anderen als notwendig erachteten Gründen ohne Vorankündigung zu ändern, zu modifizieren oder auszusetzen.",
"DINUM behält sich das Recht vor, ein Nutzerkonto des Dienstes zu sperren oder zu löschen, das gegen diese Nutzungsbedingungen verstoßen hat, unbeschadet etwaiger strafrechtlicher und zivilrechtlicher Maßnahmen, die gegen den Nutzer ergriffen werden könnten."
]
"title": "",
"content": "",
"paragraphs": ["", "", ""]
},
"section2": {
"title": "7.2. Open Source und Lizenzen",
"content": "Der Quellcode von Visio ist frei und hier verfügbar: https://github.com/suitenumerique/meet\nDie von DINUM angebotenen Inhalte stehen unter einer Open License, mit Ausnahme von Logos sowie ikonografischen und fotografischen Darstellungen, die möglicherweise eigenen Lizenzen unterliegen."
"title": "",
"content": ""
}
}
},
"article8": {
"title": "8. Änderung der Nutzungsbedingungen",
"content": "Die Bedingungen dieser Nutzungsbedingungen können jederzeit ohne Vorankündigung geändert oder ergänzt werden, abhängig von Änderungen am Dienst, Gesetzesänderungen oder aus anderen als notwendig erachteten Gründen.\nDiese Änderungen und Aktualisierungen sind für den Nutzer verbindlich, der daher regelmäßig diesen Abschnitt konsultieren muss, um die geltenden Bedingungen zu überprüfen."
"title": "",
"content": ""
}
}
}
+2 -1
View File
@@ -163,7 +163,8 @@
"apply": "Add French touch",
"clear": "Remove French touch"
}
}
},
"experimental": "Experimental feature. A v2 is coming for full browser support and improved quality."
},
"sidePanel": {
"heading": {
+2 -1
View File
@@ -163,7 +163,8 @@
"apply": "Ajouter la touche française",
"clear": "Retirer la touche française"
}
}
},
"experimental": "Fonctionnalité expérimentale. Une v2 arrive pour un support complet des navigateurs et une meilleure qualité."
},
"sidePanel": {
"heading": {
+2 -1
View File
@@ -163,7 +163,8 @@
"apply": "Franse stijl toevoegen",
"clear": "Franse stijl verwijderen"
}
}
},
"experimental": "Experimentele functie. Een v2 komt eraan voor volledige browserondersteuning en verbeterde kwaliteit."
},
"sidePanel": {
"heading": {
+2 -6
View File
@@ -1,8 +1,4 @@
import {
FeedbackRoute,
RoomRoute,
flexibleRoomIdPattern,
} from '@/features/rooms'
import { FeedbackRoute, RoomRoute, roomIdPattern } from '@/features/rooms'
import { HomeRoute } from '@/features/home'
import { LegalTermsRoute } from '@/features/legalsTerms/LegalTermsRoute'
import { AccessibilityRoute } from '@/features/legalsTerms/Accessibility'
@@ -36,8 +32,8 @@ export const routes: Record<
},
room: {
name: 'room',
path: new RegExp(`^[/](?<roomId>${roomIdPattern})$`),
to: (roomId: string) => `/${roomId.trim()}`,
path: new RegExp(`^[/](?<roomId>${flexibleRoomIdPattern})$`),
Component: RoomRoute,
},
feedback: {
-9
View File
@@ -1,9 +0,0 @@
import { proxy } from 'valtio'
type State = {
areFunnyEffectsEnabled: boolean
}
export const konamiStore = proxy<State>({
areFunnyEffectsEnabled: false,
})
@@ -67,7 +67,6 @@ backend:
SUMMARY_SERVICE_ENDPOINT: http://meet-summary:80/api/v1/tasks/
SUMMARY_SERVICE_API_TOKEN: password
SCREEN_RECORDING_BASE_URL: https://meet.127.0.0.1.nip.io/recordings
ROOM_TELEPHONY_ENABLED: True
SSL_CERT_FILE: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
@@ -149,7 +148,7 @@ summary:
AWS_S3_SECRET_ACCESS_KEY: password
OPENAI_API_KEY: password
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
OPENAI_ASR_MODEL: large-v2
OPENAI_ASR_MODEL: openai/whisper-large-v3
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
AWS_S3_SECURE_ACCESS: False
WEBHOOK_API_TOKEN: password
@@ -182,7 +181,7 @@ celery:
AWS_S3_SECRET_ACCESS_KEY: password
OPENAI_API_KEY: password
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
OPENAI_ASR_MODEL: large-v2
OPENAI_ASR_MODEL: openai/whisper-large-v3
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
AWS_S3_SECURE_ACCESS: False
WEBHOOK_API_TOKEN: password
@@ -94,7 +94,6 @@ backend:
name: backend
key: BREVO_API_KEY
BREVO_API_CONTACT_LIST_IDS: 8
ROOM_TELEPHONY_ENABLED: True
SSL_CERT_FILE: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.7
version: 0.0.5
@@ -75,6 +75,10 @@ spec:
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.backend.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.backend.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
@@ -75,6 +75,10 @@ spec:
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.backend.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.backend.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
+2 -2
View File
@@ -60,7 +60,7 @@ spec:
serviceName: {{ include "meet.frontend.fullname" . }}
servicePort: {{ .Values.frontend.service.port }}
{{- end }}
- path: /api/v
- path: /api
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: Prefix
{{- end }}
@@ -96,7 +96,7 @@ spec:
serviceName: {{ include "meet.frontend.fullname" $ }}
servicePort: {{ $.Values.frontend.service.port }}
{{- end }}
- path: /api/v
- path: /api
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: Prefix
{{- end }}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "0.1.22",
"version": "0.1.21",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "0.1.22",
"version": "0.1.21",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.5.4",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "0.1.22",
"version": "0.1.21",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
@@ -14,7 +14,7 @@
"build": "npm run build-mjml-to-html && npm run build-html-to-plain-text"
},
"volta": {
"node": "22.15.0"
"node": "22.14.0"
},
"repository": "https://github.com/numerique-gouv/meet",
"author": "DINUM",
+175
View File
@@ -853,6 +853,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/js": {
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/object-schema": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
@@ -1606,6 +1616,26 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@vitejs/plugin-react": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
"integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.26.0",
"@babel/plugin-transform-react-jsx-self": "^7.25.9",
"@babel/plugin-transform-react-jsx-source": "^7.25.9",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.14.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
}
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@@ -1993,6 +2023,29 @@
}
}
},
"node_modules/eslint-plugin-react-hooks": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
"integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-react-refresh": {
"version": "0.4.18",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
"integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"eslint": ">=8.40"
}
},
"node_modules/eslint-scope": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
@@ -2250,6 +2303,19 @@
"node": ">=10.13.0"
}
},
"node_modules/globals": {
"version": "15.14.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
"integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2965,6 +3031,43 @@
"node": ">= 0.8.0"
}
},
"node_modules/typescript": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/typescript-eslint": {
"version": "8.22.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.22.0.tgz",
"integrity": "sha512-Y2rj210FW1Wb6TWXzQc5+P+EWI9/zdS57hLEc0gnyuvdzWo8+Y8brKlbj0muejonhMI/xAZCnZZwjbIfv1CkOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.22.0",
"@typescript-eslint/parser": "8.22.0",
"@typescript-eslint/utils": "8.22.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/update-browserslist-db": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
@@ -3006,6 +3109,78 @@
"punycode": "^2.1.0"
}
},
"node_modules/vite": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz",
"integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.24.2",
"postcss": "^8.4.49",
"rollup": "^4.23.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"jiti": ">=1.21.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+8 -8
View File
@@ -14,16 +14,16 @@
"react-dom": "18.3.1"
},
"devDependencies": {
"@eslint/js": "9.26.0",
"@eslint/js": "9.17.0",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@vitejs/plugin-react": "4.4.1",
"@vitejs/plugin-react": "4.3.4",
"eslint": "8.57.0",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.20",
"globals": "16.1.0",
"typescript": "5.8.3",
"typescript-eslint": "8.32.0",
"vite": "6.3.5"
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-react-refresh": "0.4.18",
"globals": "15.14.0",
"typescript": "5.7.3",
"typescript-eslint": "8.18.2",
"vite": "6.2.5"
}
}
+14 -14
View File
@@ -4,7 +4,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.1.22",
"version": "0.1.21",
"type": "module",
"main": "./dist/visio-sdk.umd.cjs",
"module": "./dist/visio-sdk.js",
@@ -31,22 +31,22 @@
"react-dom": "18.3.1"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "5.74.7",
"@types/node": "22.15.17",
"@tanstack/eslint-plugin-query": "5.64.2",
"@types/node": "20.14.2",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.32.0",
"@typescript-eslint/parser": "8.32.0",
"@vitejs/plugin-react-swc": "3.9.0",
"@typescript-eslint/eslint-plugin": "7.15.0",
"@typescript-eslint/parser": "7.15.0",
"@vitejs/plugin-react-swc": "3.5.0",
"eslint": "8.57.0",
"eslint-config-prettier": "10.1.5",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.20",
"eslint-config-prettier": "10.0.1",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-refresh": "0.4.7",
"eslint-plugin-jsx-a11y": "6.10.2",
"sass": "1.88.0",
"prettier": "3.5.3",
"typescript": "5.8.3",
"vite": "5.4.19",
"vite-plugin-dts": "4.5.3"
"sass": "1.83.4",
"prettier": "3.4.2",
"typescript": "5.4.5",
"vite": "5.4.18",
"vite-plugin-dts": "3.9.1"
}
}
+1026 -863
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "0.1.22",
"version": "0.1.21",
"author": "",
"license": "ISC",
"description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "0.1.22"
version = "0.1.21"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",