From 7fa172d1000bd96d01f5c0577835df870c8c751f Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Thu, 28 May 2026 21:45:37 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(visio)=20use=20compatible=20with=20su?= =?UTF-8?q?mmary=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces the compatibility with summary v2. It tecnically doesn't break the compatibility with v1 as v1 params are still sent. But we advise people using the transcribe feature in their own deployments to adapt to the new v2 API, as this compatibility will be removed in a future major version. * RecordingStatusChoices now has EXTERNAL_PROCESS_SUCCESSFUL & EXTERNAL_PROCESS_FAILED Values, which are changed by a new webhook that can be called by the transcribe service. This webhook is protected by its own bearer token. * Title for the document is computed in visio, * Tests are added / updated accordingly --- CHANGELOG.md | 2 + env.d/development/common.dist | 3 +- env.d/development/summary.dist | 1 + src/backend/core/admin.py | 4 +- src/backend/core/api/serializers.py | 10 + src/backend/core/api/viewsets.py | 59 +++- ...ernal_process_id_alter_recording_status.py | 23 ++ src/backend/core/models.py | 17 ++ .../core/recording/event/authentication.py | 47 +++- .../core/recording/event/notification.py | 157 +++++++++++ .../recording/event/test_notification.py | 165 +++++++++++ ...st_api_recordings_external_process_hook.py | 134 +++++++++ src/backend/core/utils.py | 10 +- .../locale/de_DE/LC_MESSAGES/django.po | 260 +++++++++++------- .../locale/en_US/LC_MESSAGES/django.po | 259 ++++++++++------- .../locale/fr_FR/LC_MESSAGES/django.po | 259 ++++++++++------- .../locale/nl_NL/LC_MESSAGES/django.po | 260 +++++++++++------- src/backend/meet/settings.py | 18 ++ .../recording/routes/RecordingDownload.tsx | 6 +- src/frontend/src/features/recording/types.ts | 2 + src/helm/env.d/common.yaml.gotmpl | 2 +- 21 files changed, 1282 insertions(+), 416 deletions(-) create mode 100644 src/backend/core/migrations/0021_recording_external_process_id_alter_recording_status.py create mode 100644 src/backend/core/tests/recording/test_api_recordings_external_process_hook.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ff3daa7..bafcbd2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to - ⬆️(agents) upgrade to python 3.14 slim - ⬆️(dependencies) update python dependencies +- 💥(summary) remove v1 related code #1362 +- ✨(meet) use compatible with summary v2 #1362 ### Fixed diff --git a/env.d/development/common.dist b/env.d/development/common.dist index a045e99a..c48d903e 100644 --- a/env.d/development/common.dist +++ b/env.d/development/common.dist @@ -65,8 +65,9 @@ ALLOW_UNREGISTERED_ROOMS=False RECORDING_ENABLE=True RECORDING_STORAGE_EVENT_ENABLE=True RECORDING_STORAGE_EVENT_TOKEN=password -SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v1/tasks/ +SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v2/async-jobs/transcribe/ SUMMARY_SERVICE_API_TOKEN=password +SUMMARY_SERVICE_WEBHOOK_API_TOKEN=webhook-password RECORDING_DOWNLOAD_BASE_URL=http://localhost:3000/recording # Recording encoding (LiveKit Egress advanced options). diff --git a/env.d/development/summary.dist b/env.d/development/summary.dist index 5122504a..bfd56404 100644 --- a/env.d/development/summary.dist +++ b/env.d/development/summary.dist @@ -30,3 +30,4 @@ POSTHOG_ENABLED="False" # Transcription TRANSCRIPTION_SATISFACTION_FORM_BASE_URL= +AUTHORIZED_TENANTS='[{"id": "meet","api_key": "password","webhook_url": "https://configure-your-url.com/api/v1.0/recordings/external-process-hook/","webhook_api_key": "webhook-password","allowed_push_to_docs": true}]' diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index afcea086..0722c181 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -11,7 +11,7 @@ from core.recording.event import notification from . import models from .tasks.file import process_file_deletion -from .utils import generate_download_file_url +from .utils import generate_download_s3_url def hard_delete_file(file): @@ -242,7 +242,7 @@ class FileAdmin(admin.ModelAdmin): """Return a clickable preview URL for the file.""" if not obj.is_ready: return "-" - url = generate_download_file_url(obj, expires_in=60 * 60) + url = generate_download_s3_url(obj.key, expires_in=60 * 60) return format_html( 'Open File', url diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py index 3379a550..4a7a5637 100644 --- a/src/backend/core/api/serializers.py +++ b/src/backend/core/api/serializers.py @@ -563,3 +563,13 @@ class RenameParticipantSerializer(BaseValidationOnlySerializer): """Serializer for renaming a participant in a room.""" name = serializers.CharField(min_length=1, max_length=255, allow_blank=False) + + +class ExternalProcessEventSerializer(BaseValidationOnlySerializer): + """Validate external process event data.""" + + job_id = serializers.CharField(required=True) + # We are not strict on purpose on those fields to avoid + # useless bad requests + type = serializers.CharField(required=False, allow_null=True, allow_blank=True) + status = serializers.CharField(required=False, allow_null=True, allow_blank=True) diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index ee25b82d..a8def5a5 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -39,7 +39,10 @@ from core import analytics, enums, models, utils from core.api.filters import ListFileFilter from core.enums import MEDIA_STORAGE_URL_PATTERN from core.recording.enums import FileExtension -from core.recording.event.authentication import StorageEventAuthentication +from core.recording.event.authentication import ( + RecordingProcessWebhookAuthentication, + StorageEventAuthentication, +) from core.recording.event.exceptions import ( InvalidBucketError, InvalidFilepathError, @@ -999,6 +1002,60 @@ class RecordingViewSet( {"message": "Event processed."}, ) + @decorators.action( + detail=False, + methods=["post"], + url_path="external-process-hook", + authentication_classes=[RecordingProcessWebhookAuthentication], + serializer_class=serializers.ExternalProcessEventSerializer, + ) + def on_external_process_event_received(self, request, pk=None): # pylint: disable=unused-argument + """Handle incoming external process events for recordings.""" + logger.debug("Processing external process event %s", request.data) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + ok_response = drf_response.Response( + {"message": "Event processed."}, + ) + + validated_data = serializer.validated_data + job_id = validated_data["job_id"] + try: + recording = models.Recording.objects.get(external_process_id=job_id) + except models.Recording.DoesNotExist as e: + logger.warning("No recording found for job_id %s: %s", job_id, e) + return ok_response + + if validated_data.get("type") == "transcript": + if validated_data.get("status") == "success": + logger.info( + "External process transcript success received for recording %s", + job_id, + ) + recording.status = ( + models.RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL + ) + recording.save() + return ok_response + + if validated_data.get("status") == "failure": + logger.info( + "External process transcript failure received for recording %s", + job_id, + ) + recording.status = models.RecordingStatusChoices.EXTERNAL_PROCESS_FAILED + recording.save() + return ok_response + + logger.info( + "No changes to save for external process id %s and payload %s", + job_id, + validated_data, + ) + + return ok_response + def _auth_get_original_url(self, request): """ Extracts and parses the original URL from the "HTTP_X_ORIGINAL_URL" header. diff --git a/src/backend/core/migrations/0021_recording_external_process_id_alter_recording_status.py b/src/backend/core/migrations/0021_recording_external_process_id_alter_recording_status.py new file mode 100644 index 00000000..62a7fbd0 --- /dev/null +++ b/src/backend/core/migrations/0021_recording_external_process_id_alter_recording_status.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.14 on 2026-06-22 08:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0020_alter_file_upload_state'), + ] + + operations = [ + migrations.AddField( + model_name='recording', + name='external_process_id', + field=models.CharField(blank=True, help_text='ID of the external process associated with the recording.', max_length=255, null=True, unique=True, verbose_name='External Process ID'), + ), + migrations.AlterField( + model_name='recording', + name='status', + field=models.CharField(choices=[('initiated', 'Initiated'), ('active', 'Active'), ('stopped', 'Stopped'), ('saved', 'Saved'), ('aborted', 'Aborted'), ('failed_to_start', 'Failed to Start'), ('failed_to_stop', 'Failed to Stop'), ('notification_succeeded', 'Notification succeeded'), ('external_process_successful', 'External process successful'), ('external_process_failed', 'External process failed')], default='initiated', max_length=50), + ), + ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 1a7ec3c9..fbfcf6be 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -60,6 +60,11 @@ class RecordingStatusChoices(models.TextChoices): FAILED_TO_START = "failed_to_start", _("Failed to Start") FAILED_TO_STOP = "failed_to_stop", _("Failed to Stop") NOTIFICATION_SUCCEEDED = "notification_succeeded", _("Notification succeeded") + EXTERNAL_PROCESS_SUCCESSFUL = ( + "external_process_successful", + _("External process successful"), + ) + EXTERNAL_PROCESS_FAILED = "external_process_failed", _("External process failed") @classmethod def is_final(cls, status): @@ -73,6 +78,8 @@ class RecordingStatusChoices(models.TextChoices): cls.STOPPED, cls.SAVED, cls.ABORTED, + cls.EXTERNAL_PROCESS_SUCCESSFUL, + cls.EXTERNAL_PROCESS_FAILED, cls.FAILED_TO_START, cls.FAILED_TO_STOP, } @@ -598,6 +605,14 @@ class Recording(BaseModel): verbose_name=_("Recording options"), help_text=_("Recording options"), ) + external_process_id = models.CharField( + max_length=255, + null=True, + blank=True, + unique=True, + verbose_name=_("External Process ID"), + help_text=_("ID of the external process associated with the recording."), + ) class Meta: db_table = "meet_recording" @@ -653,6 +668,8 @@ class Recording(BaseModel): return self.status in { RecordingStatusChoices.NOTIFICATION_SUCCEEDED, RecordingStatusChoices.SAVED, + RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL, + RecordingStatusChoices.EXTERNAL_PROCESS_FAILED, } @property diff --git a/src/backend/core/recording/event/authentication.py b/src/backend/core/recording/event/authentication.py index 28551468..a7f08522 100644 --- a/src/backend/core/recording/event/authentication.py +++ b/src/backend/core/recording/event/authentication.py @@ -14,9 +14,9 @@ logger = logging.getLogger(__name__) class MachineUser: """Represent a non-interactive system user for automated storage operations.""" - def __init__(self) -> None: + def __init__(self, username: str = "storage_event_user") -> None: self.pk = None - self.username = "storage_event_user" + self.username = username self.is_active = True @property @@ -91,3 +91,46 @@ class StorageEventAuthentication(BaseAuthentication): def authenticate_header(self, request): """Return the WWW-Authenticate header value.""" return f"{self.TOKEN_TYPE} realm='Storage event API'" + + +class RecordingProcessWebhookAuthentication(BaseAuthentication): + """ + Custom authentication class for recording process webhook requests. + Validates the API key in the Authorization header. + """ + + AUTH_HEADER = "Authorization" + TOKEN_TYPE = "Bearer" # noqa S105 + + def authenticate(self, request): + """ + Authenticate the request and return a two-tuple of (user, token). + """ + required_token = settings.SUMMARY_SERVICE_WEBHOOK_API_TOKEN + if not required_token: + raise AuthenticationFailed("Webhook authentication is not configured.") + + auth_header: str = request.headers.get("Authorization") or "" + if not auth_header.startswith("Bearer "): + logger.warning( + "Authentication failed: Invalid authorization header format (ip: %s)", + request.META.get("REMOTE_ADDR"), + ) + raise AuthenticationFailed("Invalid authorization header format.") + token = auth_header[7:] # len("Bearer ") == 7 + + if not secrets.compare_digest( + token, + required_token, + ): + logger.warning( + "Authentication failed: Bad Authorization header (ip: %s)", + request.META.get("REMOTE_ADDR"), + ) + raise AuthenticationFailed() + + return MachineUser("external_process_user"), None + + def authenticate_header(self, request): + """Return the WWW-Authenticate header value.""" + return f"{self.TOKEN_TYPE} realm='External process webhook API'" diff --git a/src/backend/core/recording/event/notification.py b/src/backend/core/recording/event/notification.py index 2d389c4c..cdbc253d 100644 --- a/src/backend/core/recording/event/notification.py +++ b/src/backend/core/recording/event/notification.py @@ -4,6 +4,7 @@ import asyncio import logging import smtplib from datetime import datetime, timezone +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from django.conf import settings from django.core.mail import send_mail @@ -17,6 +18,7 @@ from asgiref.sync import async_to_sync from livekit import api as livekit_api from core import models, utils +from core.utils import generate_download_s3_url logger = logging.getLogger(__name__) @@ -178,8 +180,49 @@ class NotificationService: return _ns_to_utc(file_result.started_at), _ns_to_utc(file_result.ended_at) + @staticmethod + def _generate_title( + *, + locale: str, + room: str, + recording_datetime: datetime | None, + owner_timezone: str | None, + ) -> str: + """Generate title from context or return default.""" + if recording_datetime is None: + with override(locale): + return _("Transcription") + + dt = recording_datetime + if owner_timezone: + try: + dt = recording_datetime.astimezone(ZoneInfo(owner_timezone)) + except (KeyError, ZoneInfoNotFoundError): + pass # Keep the original UTC datetime + + with override(locale): + translated_template = _( + 'Meeting "{room}" on {room_recording_date} at {room_recording_time}' + ) + return translated_template.format( + room=room, + room_recording_date=dt.strftime("%Y-%m-%d"), + room_recording_time=dt.strftime("%H:%M"), + ) + @staticmethod def _notify_summary_service(recording: models.Recording): + if settings.SUMMARY_SERVICE_VERSION == 1: + return NotificationService._notify_summary_service_v1(recording) + if settings.SUMMARY_SERVICE_VERSION == 2: + return NotificationService._notify_summary_service_v2(recording) + + raise NotImplementedError( + f"Unknown summary service version: {settings.SUMMARY_SERVICE_VERSION}" + ) + + @staticmethod + def _notify_summary_service_v1(recording: models.Recording): """Notify summary service about a new recording.""" if ( @@ -253,5 +296,119 @@ class NotificationService: return True + @staticmethod + def _notify_summary_service_v2(recording: models.Recording): + """Notify summary service about a new recording.""" + + if ( + not settings.SUMMARY_SERVICE_ENDPOINT + or not settings.SUMMARY_SERVICE_API_TOKEN + ): + logger.error("Summary service not configured") + return False + + owner_access = ( + models.RecordingAccess.objects.select_related("user") + .filter( + role=models.RoleChoices.OWNER, + recording_id=recording.id, + ) + .first() + ) + metadata_filename: None | str = None + if settings.METADATA_COLLECTOR_ENABLED and recording.options.get( + "collect_metadata", False + ): + output_folder = settings.METADATA_COLLECTOR_OUTPUT_FOLDER + metadata_filename = f"{output_folder}/{recording.id}-metadata.json" + + if not owner_access: + logger.error("No owner found for recording %s", recording.id) + return False + + started_at, ended_at = async_to_sync( + NotificationService._get_recording_timestamps + )(recording.worker_id) + + form_base_url = settings.TRANSCRIPTION_SATISFACTION_FORM_BASE_URL + form_link = ( + f"{form_base_url}?room_id={recording.room.id}" + if (form_base_url and metadata_filename is not None) + else None + ) + metadata_payload = None + if started_at and ended_at and metadata_filename: + metadata_payload = { + "cloud_storage_url": generate_download_s3_url( + metadata_filename, + expires_in=settings.SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS, + override_domain=False, + ), + "started_at": started_at.isoformat(), + "ended_at": ended_at.isoformat(), + } + + payload = { + "user_sub": owner_access.user.sub, + "user_email": owner_access.user.email, + "cloud_storage_url": generate_download_s3_url( + recording.key, + expires_in=settings.SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS, + override_domain=False, + ), + "language": recording.options.get( + "language", get_language().split("-")[0].lower() + ), + "context_language": owner_access.user.language, + "push_to_docs_config": { + "user_email": owner_access.user.email, + "title": NotificationService._generate_title( + locale=owner_access.user.language + or recording.options.get("language", get_language()), + room=recording.room.name, + recording_datetime=started_at, + owner_timezone=str(owner_access.user.timezone), + ), + "download_link": f"{get_recording_download_base_url()}/{recording.id}", + "form_link": form_link, + # For now the feature flag logic is handled on summary side + "auto_create_summary": True, + }, + "metadata": metadata_payload, + } + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {settings.SUMMARY_SERVICE_API_TOKEN}", + } + + try: + response = requests.post( + settings.SUMMARY_SERVICE_ENDPOINT, + json=payload, + headers=headers, + timeout=30, + ) + response.raise_for_status() + response_json = response.json() + # We do not require a job_id to avoid a breaking change + job_id = response_json.get("job_id") + if not isinstance(job_id, str): + raise ValueError("job_id is not a string") + + recording.external_process_id = job_id + recording.save() + + except requests.RequestException as exc: + logger.exception( + "Summary service error for recording %s. URL: %s. Exception: %s", + recording.id, + settings.SUMMARY_SERVICE_ENDPOINT, + exc, + ) + return False + + return True + notification_service = NotificationService() diff --git a/src/backend/core/tests/recording/event/test_notification.py b/src/backend/core/tests/recording/event/test_notification.py index 4198f1e4..1d50a4f1 100644 --- a/src/backend/core/tests/recording/event/test_notification.py +++ b/src/backend/core/tests/recording/event/test_notification.py @@ -243,3 +243,168 @@ def test_notify_user_by_email_smtp_exception(mocked_current_site, caplog): assert result is False assert mock_send_mail.call_count == 2 assert "notification could not be sent:" in caplog.text + + +@mock.patch("core.recording.event.notification.requests.post") +@mock.patch("core.recording.event.notification.generate_download_s3_url") +@mock.patch.object( + NotificationService, "_get_recording_timestamps", new_callable=mock.AsyncMock +) +def test_notify_summary_service_post_args_with_metadata( + mock_get_recording_timestamps, + mock_generate_download_s3_url, + mock_post, + settings, +): + """Test summary notification computed request args when metadata is enabled.""" + settings.SUMMARY_SERVICE_VERSION = 2 + settings.SUMMARY_SERVICE_ENDPOINT = "https://summary.test/api/v2/tasks" + settings.SUMMARY_SERVICE_API_TOKEN = "summary-token" + settings.RECORDING_DOWNLOAD_BASE_URL = "https://app.test/recordings" + settings.SCREEN_RECORDING_BASE_URL = None + settings.METADATA_COLLECTOR_ENABLED = True + settings.METADATA_COLLECTOR_OUTPUT_FOLDER = "recordings-metadata" + + recording = factories.RecordingFactory( + room__name="Engineering Sync", + worker_id="egress-1", + options={"collect_metadata": True, "language": "en-us"}, + ) + owner = factories.UserFactory( + email="owner@test.com", + sub="owner-sub", + language="fr-fr", + timezone="Europe/Paris", + ) + factories.UserRecordingAccessFactory( + recording=recording, role=models.RoleChoices.OWNER, user=owner + ) + + started_at = datetime.datetime(2026, 1, 2, 10, 30, tzinfo=datetime.timezone.utc) + ended_at = datetime.datetime(2026, 1, 2, 11, 45, tzinfo=datetime.timezone.utc) + mock_get_recording_timestamps.return_value = (started_at, ended_at) + mock_generate_download_s3_url.side_effect = [ + "https://storage.test/metadata.json", + "https://storage.test/recording.ogg", + ] + + mock_response = mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"job_id": "job-42"} + mock_post.return_value = mock_response + + result = NotificationService._notify_summary_service(recording) + recording.refresh_from_db() + + assert result is True + assert recording.external_process_id == "job-42" + + metadata_filename = ( + f"{settings.METADATA_COLLECTOR_OUTPUT_FOLDER}/{recording.id}-metadata.json" + ) + expected_payload = { + "user_sub": owner.sub, + "user_email": owner.email, + "cloud_storage_url": "https://storage.test/recording.ogg", + "language": "en-us", + "context_language": owner.language, + "push_to_docs_config": { + "user_email": owner.email, + "title": 'Réunion "Engineering Sync" du 2026-01-02 à 11:30', + "download_link": f"{settings.RECORDING_DOWNLOAD_BASE_URL}/{recording.id}", + "auto_create_summary": True, + "form_link": None, + }, + "metadata": { + "cloud_storage_url": "https://storage.test/metadata.json", + "started_at": started_at.isoformat(), + "ended_at": ended_at.isoformat(), + }, + } + expected_headers = { + "Content-Type": "application/json", + "Authorization": "Bearer summary-token", + } + mock_post.assert_called_once_with( + "https://summary.test/api/v2/tasks", + json=expected_payload, + headers=expected_headers, + timeout=30, + ) + assert mock_generate_download_s3_url.call_args_list == [ + mock.call(metadata_filename, expires_in=60 * 60 * 24, override_domain=False), + mock.call(recording.key, expires_in=60 * 60 * 24, override_domain=False), + ] + mock_get_recording_timestamps.assert_awaited_once_with("egress-1") + + +@mock.patch("core.recording.event.notification.requests.post") +@mock.patch("core.recording.event.notification.generate_download_s3_url") +@mock.patch.object( + NotificationService, "_get_recording_timestamps", new_callable=mock.AsyncMock +) +def test_notify_summary_service_post_args_without_metadata( + mock_get_recording_timestamps, + mock_generate_download_s3_url, + mock_post, + settings, +): + """Test summary notification computed request args when metadata is not available.""" + settings.SUMMARY_SERVICE_VERSION = 2 + settings.SUMMARY_SERVICE_ENDPOINT = "https://summary.test/api/v2/tasks" + settings.SUMMARY_SERVICE_API_TOKEN = "summary-token" + settings.RECORDING_DOWNLOAD_BASE_URL = "https://app.test/recordings" + settings.SCREEN_RECORDING_BASE_URL = None + settings.METADATA_COLLECTOR_ENABLED = False + + recording = factories.RecordingFactory(room__name="Daily") + owner = factories.UserFactory( + email="owner@test.com", + sub="owner-sub", + language="en-us", + timezone="UTC", + ) + factories.UserRecordingAccessFactory( + recording=recording, role=models.RoleChoices.OWNER, user=owner + ) + + mock_get_recording_timestamps.return_value = (None, None) + mock_generate_download_s3_url.return_value = "https://storage.test/recording.mp4" + + mock_response = mock.Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"job_id": "job-51"} + mock_post.return_value = mock_response + + result = NotificationService._notify_summary_service(recording) + + assert result is True + expected_payload = { + "user_sub": owner.sub, + "user_email": owner.email, + "cloud_storage_url": "https://storage.test/recording.mp4", + "language": "en", + "context_language": owner.language, + "push_to_docs_config": { + "user_email": owner.email, + "title": "Transcription", + "download_link": f"{settings.RECORDING_DOWNLOAD_BASE_URL}/{recording.id}", + "auto_create_summary": True, + "form_link": None, + }, + "metadata": None, + } + expected_headers = { + "Content-Type": "application/json", + "Authorization": "Bearer summary-token", + } + mock_post.assert_called_once_with( + "https://summary.test/api/v2/tasks", + json=expected_payload, + headers=expected_headers, + timeout=30, + ) + mock_generate_download_s3_url.assert_called_once_with( + recording.key, expires_in=60 * 60 * 24, override_domain=False + ) + mock_get_recording_timestamps.assert_awaited_once_with(recording.worker_id) diff --git a/src/backend/core/tests/recording/test_api_recordings_external_process_hook.py b/src/backend/core/tests/recording/test_api_recordings_external_process_hook.py new file mode 100644 index 00000000..71aae54d --- /dev/null +++ b/src/backend/core/tests/recording/test_api_recordings_external_process_hook.py @@ -0,0 +1,134 @@ +""" +Test recordings API endpoints: external process hook. +""" + +# pylint: disable=redefined-outer-name,unused-argument + +import pytest + +from ...factories import RecordingFactory +from ...models import RecordingStatusChoices + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def external_process_settings(settings): + """Configure authentication token for the external process webhook.""" + settings.SUMMARY_SERVICE_WEBHOOK_API_TOKEN = "testWebhookToken" + return settings + + +def test_external_process_event_missing_authorization_header( + external_process_settings, client +): + """Requests without authorization must be rejected.""" + response = client.post( + "/api/v1.0/recordings/external-process-hook/", + {"job_id": "job-1", "type": "transcript", "status": "success"}, + ) + + assert response.status_code == 401 + + +def test_external_process_event_wrong_bearer_token(external_process_settings, client): + """Requests with invalid bearer token must be rejected.""" + response = client.post( + "/api/v1.0/recordings/external-process-hook/", + {"job_id": "job-1", "type": "transcript", "status": "success"}, + HTTP_AUTHORIZATION="Bearer wrongToken", + ) + + assert response.status_code == 401 + + +def test_external_process_event_missing_job_id(external_process_settings, client): + """Payload without job_id must fail validation.""" + response = client.post( + "/api/v1.0/recordings/external-process-hook/", + {"type": "transcript", "status": "success"}, + HTTP_AUTHORIZATION="Bearer testWebhookToken", + ) + + assert response.status_code == 400 + assert response.json() == {"job_id": ["This field is required."]} + + +def test_external_process_event_success_updates_recording_status( + external_process_settings, client +): + """A successful transcript process should update recording status.""" + recording = RecordingFactory( + status=RecordingStatusChoices.SAVED, + external_process_id="job-123", + ) + + response = client.post( + "/api/v1.0/recordings/external-process-hook/", + {"job_id": "job-123", "type": "transcript", "status": "success"}, + HTTP_AUTHORIZATION="Bearer testWebhookToken", + ) + + assert response.status_code == 200 + assert response.json() == {"message": "Event processed."} + + recording.refresh_from_db() + assert recording.status == RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL + + +def test_external_process_event_failure_updates_recording_status( + external_process_settings, client +): + """A failing transcript process should update recording status.""" + recording = RecordingFactory( + status=RecordingStatusChoices.SAVED, + external_process_id="job-456", + ) + + response = client.post( + "/api/v1.0/recordings/external-process-hook/", + {"job_id": "job-456", "type": "transcript", "status": "failure"}, + HTTP_AUTHORIZATION="Bearer testWebhookToken", + ) + + assert response.status_code == 200 + assert response.json() == {"message": "Event processed."} + + recording.refresh_from_db() + assert recording.status == RecordingStatusChoices.EXTERNAL_PROCESS_FAILED + + +def test_external_process_event_unknown_recording_is_ignored( + external_process_settings, client +): + """Unknown job_id should not fail the webhook processing.""" + response = client.post( + "/api/v1.0/recordings/external-process-hook/", + {"job_id": "missing-job", "type": "transcript", "status": "success"}, + HTTP_AUTHORIZATION="Bearer testWebhookToken", + ) + + assert response.status_code == 200 + assert response.json() == {"message": "Event processed."} + + +def test_external_process_event_non_transcript_event_does_not_change_status( + external_process_settings, client +): + """Only transcript events should update recording status.""" + recording = RecordingFactory( + status=RecordingStatusChoices.SAVED, + external_process_id="job-789", + ) + + response = client.post( + "/api/v1.0/recordings/external-process-hook/", + {"job_id": "job-789", "type": "thumbnail", "status": "success"}, + HTTP_AUTHORIZATION="Bearer testWebhookToken", + ) + + assert response.status_code == 200 + assert response.json() == {"message": "Event processed."} + + recording.refresh_from_db() + assert recording.status == RecordingStatusChoices.SAVED diff --git a/src/backend/core/utils.py b/src/backend/core/utils.py index 6951a99d..8a8e09fa 100644 --- a/src/backend/core/utils.py +++ b/src/backend/core/utils.py @@ -459,12 +459,14 @@ def generate_upload_policy(file): return policy -def generate_download_file_url(file, *, expires_in: int, override_domain: bool = True): +def generate_download_s3_url( + key: str, *, expires_in: int, override_domain: bool = True +): """ - Generate a S3 signed download url for a given file. + Generate a S3 signed download url for a given key. """ - - key = file.file_key + if not key: + raise ValueError("key cannot be empty") # This setting should be used if the backend application and the frontend application # can't connect to the object storage with the same domain. This is the case in the diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po index 5ca75a87..ec7e4c55 100644 --- a/src/backend/locale/de_DE/LC_MESSAGES/django.po +++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-04-21 14:09+0000\n" +"POT-Creation-Date: 2026-07-02 10:47+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,70 +17,92 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: core/admin.py:29 +#: core/admin.py:67 msgid "Personal info" msgstr "Persönliche Informationen" -#: core/admin.py:42 +#: core/admin.py:80 msgid "Permissions" msgstr "Berechtigungen" -#: core/admin.py:54 +#: core/admin.py:92 msgid "Important dates" msgstr "Wichtige Daten" -#: core/admin.py:132 core/admin.py:275 +#: core/admin.py:206 +msgid "Content" +msgstr "" + +#: core/admin.py:217 +#, fuzzy +#| msgid "Delete rooms" +msgid "Deletion" +msgstr "Räume löschen" + +#: core/admin.py:226 +msgid "Derived info" +msgstr "" + +#: core/admin.py:237 +msgid "Timestamps" +msgstr "" + +#: core/admin.py:240 +msgid "File preview" +msgstr "" + +#: core/admin.py:300 core/admin.py:443 msgid "No owner" msgstr "Kein Eigentümer" -#: core/admin.py:135 core/admin.py:278 +#: core/admin.py:303 core/admin.py:446 msgid "Multiple owners" msgstr "Mehrere Eigentümer" -#: core/admin.py:148 +#: core/admin.py:316 msgid "Resend notification to external service" msgstr "Benachrichtigung erneut an externen Dienst senden" -#: core/admin.py:171 +#: core/admin.py:339 #, python-format msgid "Failed to notify for recording %(id)s" msgstr "Benachrichtigung für Aufnahme %(id)s fehlgeschlagen" -#: core/admin.py:179 +#: core/admin.py:347 #, python-format msgid "Failed to notify for recording %(id)s: %(error)s" msgstr "Benachrichtigung für Aufnahme %(id)s fehlgeschlagen: %(error)s" -#: core/admin.py:187 +#: core/admin.py:355 #, python-format msgid "Successfully sent notifications for %(count)s recording(s)." msgstr "Benachrichtigungen für %(count)s Aufnahme(n) erfolgreich gesendet." -#: core/admin.py:195 +#: core/admin.py:363 #, python-format msgid "Skipped %(count)s expired recording(s)." msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen." -#: core/admin.py:200 +#: core/admin.py:368 msgid "Mark selected recordings as 'Failed to Stop'" msgstr "Ausgewählte Aufnahmen als ‚Fehler beim Stoppen‘ markieren" -#: core/admin.py:218 +#: core/admin.py:386 #, python-format msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'." msgstr "%(count)s Aufnahme(n) erfolgreich als ‚Fehler beim Stoppen‘ markiert." -#: core/admin.py:226 +#: core/admin.py:394 #, fuzzy, python-format #| msgid "Skipped %(count)s expired recording(s)." msgid "Skipped %(count)s recording(s) with an ineligible status." msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen." -#: core/admin.py:342 +#: core/admin.py:510 msgid "No scopes" msgstr "Keine Scopes" -#: core/admin.py:344 +#: core/admin.py:512 msgid "Scopes" msgstr "Scopes" @@ -88,17 +110,17 @@ msgstr "Scopes" msgid "Creator is me" msgstr "Ersteller bin ich" -#: core/api/serializers.py:88 +#: core/api/serializers.py:89 msgid "You must be administrator or owner of a room to add accesses to it." msgstr "" "Sie müssen Administrator oder Eigentümer eines Raums sein, um Zugriffe " "hinzuzufügen." -#: core/api/serializers.py:509 +#: core/api/serializers.py:534 msgid "This file extension is not allowed." msgstr "Diese Dateiendung ist nicht erlaubt." -#: core/api/viewsets.py:1090 +#: core/api/viewsets.py:1222 msgid "You have reached the maximum number of files for this type." msgstr "Sie haben die maximale Anzahl an Dateien dieses Typs erreicht." @@ -146,51 +168,59 @@ msgstr "Stopp fehlgeschlagen" msgid "Notification succeeded" msgstr "Benachrichtigung erfolgreich" -#: core/models.py:89 +#: core/models.py:65 +msgid "External process successful" +msgstr "Externer Prozess erfolgreich" + +#: core/models.py:67 +msgid "External process failed" +msgstr "Externer Prozess fehlgeschlagen" + +#: core/models.py:96 msgid "SCREEN_RECORDING" msgstr "BILDSCHIRMAUFZEICHNUNG" -#: core/models.py:90 +#: core/models.py:97 msgid "TRANSCRIPT" msgstr "TRANSKRIPT" -#: core/models.py:96 +#: core/models.py:103 msgid "Public Access" msgstr "Öffentlicher Zugriff" -#: core/models.py:97 +#: core/models.py:104 msgid "Trusted Access" msgstr "Vertrauenswürdiger Zugriff" -#: core/models.py:98 +#: core/models.py:105 msgid "Restricted Access" msgstr "Eingeschränkter Zugriff" -#: core/models.py:110 +#: core/models.py:117 msgid "id" msgstr "ID" -#: core/models.py:111 +#: core/models.py:118 msgid "primary key for the record as UUID" msgstr "Primärschlüssel des Eintrags als UUID" -#: core/models.py:117 +#: core/models.py:124 msgid "created on" msgstr "erstellt am" -#: core/models.py:118 +#: core/models.py:125 msgid "date and time at which a record was created" msgstr "Datum und Uhrzeit der Erstellung eines Eintrags" -#: core/models.py:123 +#: core/models.py:130 msgid "updated on" msgstr "aktualisiert am" -#: core/models.py:124 +#: core/models.py:131 msgid "date and time at which a record was last updated" msgstr "Datum und Uhrzeit der letzten Aktualisierung eines Eintrags" -#: core/models.py:144 +#: core/models.py:151 msgid "" "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/" "_ characters." @@ -198,11 +228,11 @@ msgstr "" "Geben Sie einen gültigen Sub ein. Dieser Wert darf nur Buchstaben, Zahlen " "und die Zeichen @/./+/-/_ enthalten." -#: core/models.py:150 +#: core/models.py:157 msgid "sub" msgstr "Sub" -#: core/models.py:152 +#: core/models.py:159 msgid "" "Optional for pending users; required upon account activation. 255 characters " "or fewer. Letters, numbers, and @/./+/-/_ characters only." @@ -210,55 +240,55 @@ msgstr "" "Optional für ausstehende Benutzer; erforderlich nach Kontoaktivierung. " "Maximal 255 Zeichen. Nur Buchstaben, Zahlen und @/./+/-/_ Zeichen erlaubt." -#: core/models.py:161 +#: core/models.py:168 msgid "identity email address" msgstr "Identitäts-E-Mail-Adresse" -#: core/models.py:166 +#: core/models.py:173 msgid "admin email address" msgstr "Administrator-E-Mail-Adresse" -#: core/models.py:168 +#: core/models.py:175 msgid "full name" msgstr "Vollständiger Name" -#: core/models.py:170 +#: core/models.py:177 msgid "short name" msgstr "Kurzname" -#: core/models.py:176 +#: core/models.py:183 msgid "language" msgstr "Sprache" -#: core/models.py:177 +#: core/models.py:184 msgid "The language in which the user wants to see the interface." msgstr "Die Sprache, in der der Benutzer die Oberfläche sehen möchte." -#: core/models.py:183 +#: core/models.py:190 msgid "The timezone in which the user wants to see times." msgstr "Die Zeitzone, in der der Benutzer die Zeiten sehen möchte." -#: core/models.py:186 +#: core/models.py:193 msgid "device" msgstr "Gerät" -#: core/models.py:188 +#: core/models.py:195 msgid "Whether the user is a device or a real user." msgstr "Ob es sich um ein Gerät oder einen echten Benutzer handelt." -#: core/models.py:191 +#: core/models.py:198 msgid "staff status" msgstr "Mitarbeiterstatus" -#: core/models.py:193 +#: core/models.py:200 msgid "Whether the user can log into this admin site." msgstr "Ob der Benutzer sich bei dieser Admin-Seite anmelden kann." -#: core/models.py:196 +#: core/models.py:203 msgid "active" msgstr "aktiv" -#: core/models.py:199 +#: core/models.py:206 msgid "" "Whether this user should be treated as active. Unselect this instead of " "deleting accounts." @@ -266,66 +296,66 @@ msgstr "" "Ob dieser Benutzer als aktiv behandelt werden soll. Deaktivieren Sie dies " "anstelle des Löschens des Kontos." -#: core/models.py:212 +#: core/models.py:219 msgid "user" msgstr "Benutzer" -#: core/models.py:213 +#: core/models.py:220 msgid "users" msgstr "Benutzer" -#: core/models.py:272 +#: core/models.py:286 msgid "Resource" msgstr "Ressource" -#: core/models.py:273 +#: core/models.py:287 msgid "Resources" msgstr "Ressourcen" -#: core/models.py:331 +#: core/models.py:345 msgid "Resource access" msgstr "Ressourcenzugriff" -#: core/models.py:332 +#: core/models.py:346 msgid "Resource accesses" msgstr "Ressourcenzugriffe" -#: core/models.py:338 +#: core/models.py:352 msgid "Resource access with this User and Resource already exists." msgstr "" "Ein Ressourcenzugriff mit diesem Benutzer und dieser Ressource existiert " "bereits." -#: core/models.py:394 +#: core/models.py:409 msgid "Visio room configuration" msgstr "Visio-Raumkonfiguration" -#: core/models.py:395 +#: core/models.py:410 msgid "Values for Visio parameters to configure the room." msgstr "Werte für Visio-Parameter zur Konfiguration des Raums." -#: core/models.py:402 +#: core/models.py:417 msgid "Room PIN code" msgstr "PIN-Code für den Raum" -#: core/models.py:403 +#: core/models.py:418 msgid "Unique n-digit code that identifies this room in telephony mode." msgstr "" "Eindeutiger n-stelliger Code, der diesen Raum im Telephonmodus identifiziert." -#: core/models.py:409 core/models.py:563 +#: core/models.py:424 core/models.py:578 msgid "Room" msgstr "Raum" -#: core/models.py:410 +#: core/models.py:425 msgid "Rooms" msgstr "Räume" -#: core/models.py:574 +#: core/models.py:589 msgid "Worker ID" msgstr "Worker-ID" -#: core/models.py:576 +#: core/models.py:591 msgid "" "Enter an identifier for the worker recording.This ID is retained even when " "the worker stops, allowing for easy tracking." @@ -334,141 +364,153 @@ msgstr "" "erhalten, auch wenn der Worker stoppt, was ein einfaches Nachverfolgen " "ermöglicht." -#: core/models.py:584 +#: core/models.py:599 msgid "Recording mode" msgstr "Aufzeichnungsmodus" -#: core/models.py:585 +#: core/models.py:600 msgid "Defines the mode of recording being called." msgstr "Definiert den aufgerufenen Aufzeichnungsmodus." -#: core/models.py:590 core/models.py:591 +#: core/models.py:605 core/models.py:606 msgid "Recording options" msgstr "Aufnahmeoptionen" -#: core/models.py:597 +#: core/models.py:613 +msgid "External Process ID" +msgstr "External Process ID" + +#: core/models.py:614 +msgid "ID of the external process associated with the recording." +msgstr "ID des externen Prozesses, der mit der Aufzeichnung verknüpft ist" + +#: core/models.py:620 msgid "Recording" msgstr "Aufzeichnung" -#: core/models.py:598 +#: core/models.py:621 msgid "Recordings" msgstr "Aufzeichnungen" -#: core/models.py:706 +#: core/models.py:731 msgid "Recording/user relation" msgstr "Beziehung Aufzeichnung/Benutzer" -#: core/models.py:707 +#: core/models.py:732 msgid "Recording/user relations" msgstr "Beziehungen Aufzeichnung/Benutzer" -#: core/models.py:713 +#: core/models.py:738 msgid "This user is already in this recording." msgstr "Dieser Benutzer ist bereits Teil dieser Aufzeichnung." -#: core/models.py:719 +#: core/models.py:744 msgid "This team is already in this recording." msgstr "Dieses Team ist bereits Teil dieser Aufzeichnung." -#: core/models.py:725 +#: core/models.py:750 msgid "Either user or team must be set, not both." msgstr "Entweder Benutzer oder Team muss festgelegt werden, nicht beides." -#: core/models.py:742 +#: core/models.py:767 msgid "Create rooms" msgstr "Räume erstellen" -#: core/models.py:743 +#: core/models.py:768 msgid "List rooms" msgstr "Räume auflisten" -#: core/models.py:744 +#: core/models.py:769 msgid "Retrieve room details" msgstr "Raumdetails abrufen" -#: core/models.py:745 +#: core/models.py:770 msgid "Update rooms" msgstr "Räume aktualisieren" -#: core/models.py:746 +#: core/models.py:771 msgid "Delete rooms" msgstr "Räume löschen" -#: core/models.py:759 +#: core/models.py:784 msgid "Application name" msgstr "Anwendungsname" -#: core/models.py:760 +#: core/models.py:785 msgid "Descriptive name for this application." msgstr "Beschreibender Name für diese Anwendung." -#: core/models.py:770 +#: core/models.py:795 msgid "Hashed on Save. Copy it now if this is a new secret." msgstr "" "Beim Speichern gehasht. Jetzt kopieren, wenn dies ein neues Geheimnis ist." -#: core/models.py:781 +#: core/models.py:806 msgid "Application" msgstr "Anwendung" -#: core/models.py:782 +#: core/models.py:807 msgid "Applications" msgstr "Anwendungen" -#: core/models.py:805 +#: core/models.py:830 msgid "Enter a valid domain" msgstr "Geben Sie eine gültige Domain ein" -#: core/models.py:808 +#: core/models.py:833 msgid "Domain" msgstr "Domain" -#: core/models.py:809 +#: core/models.py:834 msgid "Email domain this application can act on behalf of." msgstr "E-Mail-Domain, im Namen der diese Anwendung handeln kann." -#: core/models.py:821 +#: core/models.py:846 msgid "Application domain" msgstr "Anwendungsdomain" -#: core/models.py:822 +#: core/models.py:847 msgid "Application domains" msgstr "Anwendungsdomains" -#: core/models.py:840 +#: core/models.py:865 msgid "Pending" msgstr "Ausstehend" -#: core/models.py:848 +#: core/models.py:866 +msgid "Analyzing" +msgstr "" + +#: core/models.py:873 msgid "Ready" msgstr "Bereit" -#: core/models.py:854 +#: core/models.py:879 msgid "Background image" msgstr "Hintergrundbild" -#: core/models.py:866 +#: core/models.py:891 msgid "title" msgstr "Titel" -#: core/models.py:890 +#: core/models.py:915 msgid "Malware detection info when the analysis status is unsafe." msgstr "" "Informationen zur Malware-Erkennung, wenn der Analyse-Status unsicher ist." -#: core/models.py:895 +#: core/models.py:920 msgid "File" msgstr "Datei" -#: core/models.py:896 +#: core/models.py:921 msgid "Files" msgstr "Dateien" -#: core/models.py:1000 +#: core/models.py:1041 msgid "This file is already hard deleted." msgstr "Diese Datei wurde bereits endgültig gelöscht." -#: core/models.py:1010 +#: core/models.py:1051 #, fuzzy #| msgid "To hard delete a file, it must first be soft deleted." msgid "To hard delete a file, it must first be soft deleted." @@ -476,10 +518,20 @@ msgstr "" "Um eine Datei endgültig zu löschen, muss sie zuvor weich gelöscht worden " "sein." -#: core/recording/event/notification.py:116 +#: core/recording/event/notification.py:123 msgid "Your recording is ready" msgstr "Ihre Aufzeichnung ist bereit" +#: core/recording/event/notification.py:194 +msgid "Transcription" +msgstr "Transkription" + +#: core/recording/event/notification.py:204 +#, python-brace-format +msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}" +msgstr "" +"Besprechung \"{room}\" am {room_recording_date} um {room_recording_time}" + #: core/services/invitation.py:44 #, python-brace-format msgid "Video call in progress: {sender.email} is waiting for you to connect" @@ -575,7 +627,7 @@ msgid "To keep this recording permanently:" msgstr "So speichern Sie diese Aufzeichnung dauerhaft:" #: core/templates/mail/html/screen_recording.html:208 -#: core/templates/mail/text/screen_recording.txt:13 +#, python-format msgid "Click the \"Open\" link below " msgstr "Klicken Sie auf den Link „Öffnen\" unten " @@ -604,18 +656,24 @@ msgstr "" " Wenn Sie Fragen haben oder Unterstützung benötigen, wenden Sie sich bitte " "an unser Support-Team unter %(support_email)s. " -#: meet/settings.py:223 +#: core/templates/mail/text/screen_recording.txt:13 +#, fuzzy, python-format +#| msgid "Click the \"Open\" link below " +msgid "Click the \"Open [%(link)s]\" link below " +msgstr "Klicken Sie auf den Link „Öffnen\" unten " + +#: meet/settings.py:228 msgid "English" msgstr "Englisch" -#: meet/settings.py:224 +#: meet/settings.py:229 msgid "French" msgstr "Französisch" -#: meet/settings.py:225 +#: meet/settings.py:230 msgid "Dutch" msgstr "Niederländisch" -#: meet/settings.py:226 +#: meet/settings.py:231 msgid "German" msgstr "Deutsch" diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po index 70c973b9..2f58f6d4 100644 --- a/src/backend/locale/en_US/LC_MESSAGES/django.po +++ b/src/backend/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-04-21 14:09+0000\n" +"POT-Creation-Date: 2026-07-02 10:47+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,70 +17,92 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: core/admin.py:29 +#: core/admin.py:67 msgid "Personal info" msgstr "Personal info" -#: core/admin.py:42 +#: core/admin.py:80 msgid "Permissions" msgstr "Permissions" -#: core/admin.py:54 +#: core/admin.py:92 msgid "Important dates" msgstr "Important dates" -#: core/admin.py:132 core/admin.py:275 +#: core/admin.py:206 +msgid "Content" +msgstr "" + +#: core/admin.py:217 +#, fuzzy +#| msgid "Delete rooms" +msgid "Deletion" +msgstr "Delete rooms" + +#: core/admin.py:226 +msgid "Derived info" +msgstr "" + +#: core/admin.py:237 +msgid "Timestamps" +msgstr "" + +#: core/admin.py:240 +msgid "File preview" +msgstr "" + +#: core/admin.py:300 core/admin.py:443 msgid "No owner" msgstr "No owner" -#: core/admin.py:135 core/admin.py:278 +#: core/admin.py:303 core/admin.py:446 msgid "Multiple owners" msgstr "Multiple owners" -#: core/admin.py:148 +#: core/admin.py:316 msgid "Resend notification to external service" msgstr "Resend notification to external service" -#: core/admin.py:171 +#: core/admin.py:339 #, python-format msgid "Failed to notify for recording %(id)s" msgstr "Failed to notify for recording %(id)s" -#: core/admin.py:179 +#: core/admin.py:347 #, python-format msgid "Failed to notify for recording %(id)s: %(error)s" msgstr "Failed to notify for recording %(id)s: %(error)s" -#: core/admin.py:187 +#: core/admin.py:355 #, python-format msgid "Successfully sent notifications for %(count)s recording(s)." msgstr "Successfully sent notifications for %(count)s recording(s)." -#: core/admin.py:195 +#: core/admin.py:363 #, python-format msgid "Skipped %(count)s expired recording(s)." msgstr "Skipped %(count)s expired recording(s)." -#: core/admin.py:200 +#: core/admin.py:368 msgid "Mark selected recordings as 'Failed to Stop'" msgstr "Mark selected recordings as 'Failed to Stop'" -#: core/admin.py:218 +#: core/admin.py:386 #, python-format msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'." msgstr "%(count)s recording(s) successfully marked as 'Failed to Stop'." -#: core/admin.py:226 +#: core/admin.py:394 #, fuzzy, python-format #| msgid "Skipped %(count)s expired recording(s)." msgid "Skipped %(count)s recording(s) with an ineligible status." msgstr "Skipped %(count)s expired recording(s)." -#: core/admin.py:342 +#: core/admin.py:510 msgid "No scopes" msgstr "No scopes" -#: core/admin.py:344 +#: core/admin.py:512 msgid "Scopes" msgstr "Scopes" @@ -88,15 +110,15 @@ msgstr "Scopes" msgid "Creator is me" msgstr "Creator is me" -#: core/api/serializers.py:88 +#: core/api/serializers.py:89 msgid "You must be administrator or owner of a room to add accesses to it." msgstr "You must be administrator or owner of a room to add accesses to it." -#: core/api/serializers.py:509 +#: core/api/serializers.py:534 msgid "This file extension is not allowed." msgstr "This file extension is not allowed." -#: core/api/viewsets.py:1090 +#: core/api/viewsets.py:1222 msgid "You have reached the maximum number of files for this type." msgstr "You have reached the maximum number of files for this type." @@ -144,51 +166,59 @@ msgstr "Failed to Stop" msgid "Notification succeeded" msgstr "Notification succeeded" -#: core/models.py:89 +#: core/models.py:65 +msgid "External process successful" +msgstr "External process successful" + +#: core/models.py:67 +msgid "External process failed" +msgstr "External process failed" + +#: core/models.py:96 msgid "SCREEN_RECORDING" msgstr "SCREEN_RECORDING" -#: core/models.py:90 +#: core/models.py:97 msgid "TRANSCRIPT" msgstr "TRANSCRIPT" -#: core/models.py:96 +#: core/models.py:103 msgid "Public Access" msgstr "Public Access" -#: core/models.py:97 +#: core/models.py:104 msgid "Trusted Access" msgstr "Trusted Access" -#: core/models.py:98 +#: core/models.py:105 msgid "Restricted Access" msgstr "Restricted Access" -#: core/models.py:110 +#: core/models.py:117 msgid "id" msgstr "id" -#: core/models.py:111 +#: core/models.py:118 msgid "primary key for the record as UUID" msgstr "primary key for the record as UUID" -#: core/models.py:117 +#: core/models.py:124 msgid "created on" msgstr "created on" -#: core/models.py:118 +#: core/models.py:125 msgid "date and time at which a record was created" msgstr "date and time at which a record was created" -#: core/models.py:123 +#: core/models.py:130 msgid "updated on" msgstr "updated on" -#: core/models.py:124 +#: core/models.py:131 msgid "date and time at which a record was last updated" msgstr "date and time at which a record was last updated" -#: core/models.py:144 +#: core/models.py:151 msgid "" "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/" "_ characters." @@ -196,11 +226,11 @@ msgstr "" "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/" "_ characters." -#: core/models.py:150 +#: core/models.py:157 msgid "sub" msgstr "sub" -#: core/models.py:152 +#: core/models.py:159 msgid "" "Optional for pending users; required upon account activation. 255 characters " "or fewer. Letters, numbers, and @/./+/-/_ characters only." @@ -208,55 +238,55 @@ msgstr "" "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ " "characters only." -#: core/models.py:161 +#: core/models.py:168 msgid "identity email address" msgstr "identity email address" -#: core/models.py:166 +#: core/models.py:173 msgid "admin email address" msgstr "admin email address" -#: core/models.py:168 +#: core/models.py:175 msgid "full name" msgstr "full name" -#: core/models.py:170 +#: core/models.py:177 msgid "short name" msgstr "short name" -#: core/models.py:176 +#: core/models.py:183 msgid "language" msgstr "language" -#: core/models.py:177 +#: core/models.py:184 msgid "The language in which the user wants to see the interface." msgstr "The language in which the user wants to see the interface." -#: core/models.py:183 +#: core/models.py:190 msgid "The timezone in which the user wants to see times." msgstr "The timezone in which the user wants to see times." -#: core/models.py:186 +#: core/models.py:193 msgid "device" msgstr "device" -#: core/models.py:188 +#: core/models.py:195 msgid "Whether the user is a device or a real user." msgstr "Whether the user is a device or a real user." -#: core/models.py:191 +#: core/models.py:198 msgid "staff status" msgstr "staff status" -#: core/models.py:193 +#: core/models.py:200 msgid "Whether the user can log into this admin site." msgstr "Whether the user can log into this admin site." -#: core/models.py:196 +#: core/models.py:203 msgid "active" msgstr "active" -#: core/models.py:199 +#: core/models.py:206 msgid "" "Whether this user should be treated as active. Unselect this instead of " "deleting accounts." @@ -264,63 +294,63 @@ msgstr "" "Whether this user should be treated as active. Unselect this instead of " "deleting accounts." -#: core/models.py:212 +#: core/models.py:219 msgid "user" msgstr "user" -#: core/models.py:213 +#: core/models.py:220 msgid "users" msgstr "users" -#: core/models.py:272 +#: core/models.py:286 msgid "Resource" msgstr "Resource" -#: core/models.py:273 +#: core/models.py:287 msgid "Resources" msgstr "Resources" -#: core/models.py:331 +#: core/models.py:345 msgid "Resource access" msgstr "Resource access" -#: core/models.py:332 +#: core/models.py:346 msgid "Resource accesses" msgstr "Resource accesses" -#: core/models.py:338 +#: core/models.py:352 msgid "Resource access with this User and Resource already exists." msgstr "Resource access with this User and Resource already exists." -#: core/models.py:394 +#: core/models.py:409 msgid "Visio room configuration" msgstr "Visio room configuration" -#: core/models.py:395 +#: core/models.py:410 msgid "Values for Visio parameters to configure the room." msgstr "Values for Visio parameters to configure the room." -#: core/models.py:402 +#: core/models.py:417 msgid "Room PIN code" msgstr "Room PIN code" -#: core/models.py:403 +#: core/models.py:418 msgid "Unique n-digit code that identifies this room in telephony mode." msgstr "Unique n-digit code that identifies this room in telephony mode." -#: core/models.py:409 core/models.py:563 +#: core/models.py:424 core/models.py:578 msgid "Room" msgstr "Room" -#: core/models.py:410 +#: core/models.py:425 msgid "Rooms" msgstr "Rooms" -#: core/models.py:574 +#: core/models.py:589 msgid "Worker ID" msgstr "Worker ID" -#: core/models.py:576 +#: core/models.py:591 msgid "" "Enter an identifier for the worker recording.This ID is retained even when " "the worker stops, allowing for easy tracking." @@ -328,154 +358,175 @@ msgstr "" "Enter an identifier for the worker recording.This ID is retained even when " "the worker stops, allowing for easy tracking." -#: core/models.py:584 +#: core/models.py:599 msgid "Recording mode" msgstr "Recording mode" -#: core/models.py:585 +#: core/models.py:600 msgid "Defines the mode of recording being called." msgstr "Defines the mode of recording being called." -#: core/models.py:590 core/models.py:591 +#: core/models.py:605 core/models.py:606 msgid "Recording options" msgstr "Recording options" -#: core/models.py:597 +#: core/models.py:613 +msgid "External Process ID" +msgstr "External Process ID" + +#: core/models.py:614 +msgid "ID of the external process associated with the recording." +msgstr "ID of the external process associated with the recording." + +#: core/models.py:620 msgid "Recording" msgstr "Recording" -#: core/models.py:598 +#: core/models.py:621 msgid "Recordings" msgstr "Recordings" -#: core/models.py:706 +#: core/models.py:731 msgid "Recording/user relation" msgstr "Recording/user relation" -#: core/models.py:707 +#: core/models.py:732 msgid "Recording/user relations" msgstr "Recording/user relations" -#: core/models.py:713 +#: core/models.py:738 msgid "This user is already in this recording." msgstr "This user is already in this recording." -#: core/models.py:719 +#: core/models.py:744 msgid "This team is already in this recording." msgstr "This team is already in this recording." -#: core/models.py:725 +#: core/models.py:750 msgid "Either user or team must be set, not both." msgstr "Either user or team must be set, not both." -#: core/models.py:742 +#: core/models.py:767 #, fuzzy #| msgid "created on" msgid "Create rooms" msgstr "Create rooms" -#: core/models.py:743 +#: core/models.py:768 msgid "List rooms" msgstr "List rooms" -#: core/models.py:744 +#: core/models.py:769 msgid "Retrieve room details" msgstr "Retrieve room details" -#: core/models.py:745 +#: core/models.py:770 #, fuzzy #| msgid "updated on" msgid "Update rooms" msgstr "Update rooms" -#: core/models.py:746 +#: core/models.py:771 msgid "Delete rooms" msgstr "Delete rooms" -#: core/models.py:759 +#: core/models.py:784 msgid "Application name" msgstr "Application name" -#: core/models.py:760 +#: core/models.py:785 msgid "Descriptive name for this application." msgstr "Descriptive name for this application." -#: core/models.py:770 +#: core/models.py:795 msgid "Hashed on Save. Copy it now if this is a new secret." msgstr "Hashed on Save. Copy it now if this is a new secret." -#: core/models.py:781 +#: core/models.py:806 msgid "Application" msgstr "Application" -#: core/models.py:782 +#: core/models.py:807 msgid "Applications" msgstr "Applications" -#: core/models.py:805 +#: core/models.py:830 msgid "Enter a valid domain" msgstr "Enter a valid domain" -#: core/models.py:808 +#: core/models.py:833 msgid "Domain" msgstr "Domain" -#: core/models.py:809 +#: core/models.py:834 msgid "Email domain this application can act on behalf of." msgstr "Email domain this application can act on behalf of." -#: core/models.py:821 +#: core/models.py:846 msgid "Application domain" msgstr "Application domain" -#: core/models.py:822 +#: core/models.py:847 msgid "Application domains" msgstr "Application domains" -#: core/models.py:840 +#: core/models.py:865 #, fuzzy #| msgid "Recording" msgid "Pending" msgstr "Pending" -#: core/models.py:848 +#: core/models.py:866 +msgid "Analyzing" +msgstr "" + +#: core/models.py:873 msgid "Ready" msgstr "Ready" -#: core/models.py:854 +#: core/models.py:879 msgid "Background image" msgstr "Background image" -#: core/models.py:866 +#: core/models.py:891 msgid "title" msgstr "title" -#: core/models.py:890 +#: core/models.py:915 msgid "Malware detection info when the analysis status is unsafe." msgstr "Malware detection info when the analysis status is unsafe." -#: core/models.py:895 +#: core/models.py:920 msgid "File" msgstr "File" -#: core/models.py:896 +#: core/models.py:921 msgid "Files" msgstr "Files" -#: core/models.py:1000 +#: core/models.py:1041 #, fuzzy #| msgid "This user is already in this recording." msgid "This file is already hard deleted." msgstr "This file is already hard deleted." -#: core/models.py:1010 +#: core/models.py:1051 msgid "To hard delete a file, it must first be soft deleted." msgstr "To hard delete a file, it must first be soft deleted." -#: core/recording/event/notification.py:116 +#: core/recording/event/notification.py:123 msgid "Your recording is ready" msgstr "Your recording is ready" +#: core/recording/event/notification.py:194 +msgid "Transcription" +msgstr "Transcription" + +#: core/recording/event/notification.py:204 +#, python-brace-format +msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}" +msgstr "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}" + #: core/services/invitation.py:44 #, python-brace-format msgid "Video call in progress: {sender.email} is waiting for you to connect" @@ -571,7 +622,7 @@ msgid "To keep this recording permanently:" msgstr "To keep this recording permanently:" #: core/templates/mail/html/screen_recording.html:208 -#: core/templates/mail/text/screen_recording.txt:13 +#, python-format msgid "Click the \"Open\" link below " msgstr "Click the \"Open\" link below " @@ -600,18 +651,24 @@ msgstr "" " If you have any questions or need assistance, please contact our support " "team at %(support_email)s. " -#: meet/settings.py:223 +#: core/templates/mail/text/screen_recording.txt:13 +#, fuzzy, python-format +#| msgid "Click the \"Open\" link below " +msgid "Click the \"Open [%(link)s]\" link below " +msgstr "Click the \"Open\" link below " + +#: meet/settings.py:228 msgid "English" msgstr "English" -#: meet/settings.py:224 +#: meet/settings.py:229 msgid "French" msgstr "French" -#: meet/settings.py:225 +#: meet/settings.py:230 msgid "Dutch" msgstr "Dutch" -#: meet/settings.py:226 +#: meet/settings.py:231 msgid "German" msgstr "German" diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po index cff45d0a..d26a3db3 100644 --- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po +++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-04-21 14:09+0000\n" +"POT-Creation-Date: 2026-07-02 10:47+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n" "Language-Team: LANGUAGE \n" @@ -17,71 +17,93 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: core/admin.py:29 +#: core/admin.py:67 msgid "Personal info" msgstr "Informations personnelles" -#: core/admin.py:42 +#: core/admin.py:80 msgid "Permissions" msgstr "Permissions" -#: core/admin.py:54 +#: core/admin.py:92 msgid "Important dates" msgstr "Dates importantes" -#: core/admin.py:132 core/admin.py:275 +#: core/admin.py:206 +msgid "Content" +msgstr "" + +#: core/admin.py:217 +#, fuzzy +#| msgid "Delete rooms" +msgid "Deletion" +msgstr "Supprimer les salles" + +#: core/admin.py:226 +msgid "Derived info" +msgstr "" + +#: core/admin.py:237 +msgid "Timestamps" +msgstr "" + +#: core/admin.py:240 +msgid "File preview" +msgstr "" + +#: core/admin.py:300 core/admin.py:443 msgid "No owner" msgstr "Pas de propriétaire" -#: core/admin.py:135 core/admin.py:278 +#: core/admin.py:303 core/admin.py:446 msgid "Multiple owners" msgstr "Plusieurs propriétaires" -#: core/admin.py:148 +#: core/admin.py:316 msgid "Resend notification to external service" msgstr "Renvoyer la notification au service externe" -#: core/admin.py:171 +#: core/admin.py:339 #, python-format msgid "Failed to notify for recording %(id)s" msgstr "Échec de la notification pour l’enregistrement %(id)s" -#: core/admin.py:179 +#: core/admin.py:347 #, python-format msgid "Failed to notify for recording %(id)s: %(error)s" msgstr "Échec de la notification pour l’enregistrement %(id)s : %(error)s" -#: core/admin.py:187 +#: core/admin.py:355 #, python-format msgid "Successfully sent notifications for %(count)s recording(s)." msgstr "Notifications envoyées avec succès pour %(count)s enregistrement(s)." -#: core/admin.py:195 +#: core/admin.py:363 #, python-format msgid "Skipped %(count)s expired recording(s)." msgstr "%(count)s enregistrement(s) expiré(s) ignoré(s)." -#: core/admin.py:200 +#: core/admin.py:368 msgid "Mark selected recordings as 'Failed to Stop'" msgstr "Marquer les enregistrements sélectionnés comme « Échec d’arrêt »" -#: core/admin.py:218 +#: core/admin.py:386 #, python-format msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'." msgstr "" "%(count)s enregistrement(s) marqué(s) avec succès comme « Échec d’arrêt »." -#: core/admin.py:226 +#: core/admin.py:394 #, fuzzy, python-format #| msgid "Skipped %(count)s expired recording(s)." msgid "Skipped %(count)s recording(s) with an ineligible status." msgstr "%(count)s enregistrement(s) avec un statut inéligible ignoré(s)." -#: core/admin.py:342 +#: core/admin.py:510 msgid "No scopes" msgstr "Aucun scopes" -#: core/admin.py:344 +#: core/admin.py:512 msgid "Scopes" msgstr "Scopes" @@ -89,17 +111,17 @@ msgstr "Scopes" msgid "Creator is me" msgstr "Je suis le créateur" -#: core/api/serializers.py:88 +#: core/api/serializers.py:89 msgid "You must be administrator or owner of a room to add accesses to it." msgstr "" "Vous devez être administrateur ou propriétaire d'une salle pour y ajouter " "des accès." -#: core/api/serializers.py:509 +#: core/api/serializers.py:534 msgid "This file extension is not allowed." msgstr "Cette extension n'est pas autorisée" -#: core/api/viewsets.py:1090 +#: core/api/viewsets.py:1222 msgid "You have reached the maximum number of files for this type." msgstr "Vous avez atteint le nombre maximum de fichiers de ce type" @@ -147,53 +169,61 @@ msgstr "Échec à l'arrêt" msgid "Notification succeeded" msgstr "Notification réussie" -#: core/models.py:89 +#: core/models.py:65 +msgid "External process successful" +msgstr "Traitement externe : succès" + +#: core/models.py:67 +msgid "External process failed" +msgstr "Traitement externe : erreur" + +#: core/models.py:96 msgid "SCREEN_RECORDING" msgstr "ENREGISTREMENT_ÉCRAN" -#: core/models.py:90 +#: core/models.py:97 msgid "TRANSCRIPT" msgstr "TRANSCRIPTION" -#: core/models.py:96 +#: core/models.py:103 msgid "Public Access" msgstr "Accès public" -#: core/models.py:97 +#: core/models.py:104 msgid "Trusted Access" msgstr "Accès de confiance" -#: core/models.py:98 +#: core/models.py:105 msgid "Restricted Access" msgstr "Accès restreint" -#: core/models.py:110 +#: core/models.py:117 msgid "id" msgstr "id" -#: core/models.py:111 +#: core/models.py:118 msgid "primary key for the record as UUID" msgstr "clé primaire pour l'enregistrement sous forme d'UUID" -#: core/models.py:117 +#: core/models.py:124 msgid "created on" msgstr "créé le" -#: core/models.py:118 +#: core/models.py:125 msgid "date and time at which a record was created" msgstr "date et heure auxquelles un enregistrement a été créé" -#: core/models.py:123 +#: core/models.py:130 msgid "updated on" msgstr "mis à jour le" -#: core/models.py:124 +#: core/models.py:131 msgid "date and time at which a record was last updated" msgstr "" "date et heure auxquelles un enregistrement a été mis à jour pour la dernière " "fois" -#: core/models.py:144 +#: core/models.py:151 msgid "" "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/" "_ characters." @@ -201,11 +231,11 @@ msgstr "" "Entrez un sub valide. Cette valeur ne peut contenir que des lettres, des " "chiffres et les caractères @/./+/-/_." -#: core/models.py:150 +#: core/models.py:157 msgid "sub" msgstr "sub" -#: core/models.py:152 +#: core/models.py:159 msgid "" "Optional for pending users; required upon account activation. 255 characters " "or fewer. Letters, numbers, and @/./+/-/_ characters only." @@ -213,55 +243,55 @@ msgstr "" "Optionnel pour les utilisateurs en attente ; requis lors de l'activation du " "compte. 255 caractères maximum. Lettres, chiffres et @/./+/-/_ uniquement." -#: core/models.py:161 +#: core/models.py:168 msgid "identity email address" msgstr "adresse e-mail d'identité" -#: core/models.py:166 +#: core/models.py:173 msgid "admin email address" msgstr "adresse e-mail d'administrateur" -#: core/models.py:168 +#: core/models.py:175 msgid "full name" msgstr "nom complet" -#: core/models.py:170 +#: core/models.py:177 msgid "short name" msgstr "nom court" -#: core/models.py:176 +#: core/models.py:183 msgid "language" msgstr "langue" -#: core/models.py:177 +#: core/models.py:184 msgid "The language in which the user wants to see the interface." msgstr "La langue dans laquelle l'utilisateur souhaite voir l'interface." -#: core/models.py:183 +#: core/models.py:190 msgid "The timezone in which the user wants to see times." msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures." -#: core/models.py:186 +#: core/models.py:193 msgid "device" msgstr "appareil" -#: core/models.py:188 +#: core/models.py:195 msgid "Whether the user is a device or a real user." msgstr "Si l'utilisateur est un appareil ou un utilisateur réel." -#: core/models.py:191 +#: core/models.py:198 msgid "staff status" msgstr "statut du personnel" -#: core/models.py:193 +#: core/models.py:200 msgid "Whether the user can log into this admin site." msgstr "Si l'utilisateur peut se connecter à ce site d'administration." -#: core/models.py:196 +#: core/models.py:203 msgid "active" msgstr "actif" -#: core/models.py:199 +#: core/models.py:206 msgid "" "Whether this user should be treated as active. Unselect this instead of " "deleting accounts." @@ -269,65 +299,65 @@ msgstr "" "Si cet utilisateur doit être traité comme actif. Désélectionnez cette option " "au lieu de supprimer des comptes." -#: core/models.py:212 +#: core/models.py:219 msgid "user" msgstr "utilisateur" -#: core/models.py:213 +#: core/models.py:220 msgid "users" msgstr "utilisateurs" -#: core/models.py:272 +#: core/models.py:286 msgid "Resource" msgstr "Ressource" -#: core/models.py:273 +#: core/models.py:287 msgid "Resources" msgstr "Ressources" -#: core/models.py:331 +#: core/models.py:345 msgid "Resource access" msgstr "Accès aux ressources" -#: core/models.py:332 +#: core/models.py:346 msgid "Resource accesses" msgstr "Accès aux ressources" -#: core/models.py:338 +#: core/models.py:352 msgid "Resource access with this User and Resource already exists." msgstr "" "L'accès à la ressource avec cet utilisateur et cette ressource existe déjà." -#: core/models.py:394 +#: core/models.py:409 msgid "Visio room configuration" msgstr "Configuration de la salle de visioconférence" -#: core/models.py:395 +#: core/models.py:410 msgid "Values for Visio parameters to configure the room." msgstr "Valeurs des paramètres de visioconférence pour configurer la salle." -#: core/models.py:402 +#: core/models.py:417 msgid "Room PIN code" msgstr "Code PIN de la salle" -#: core/models.py:403 +#: core/models.py:418 msgid "Unique n-digit code that identifies this room in telephony mode." msgstr "" "Code unique à n chiffres qui identifie cette salle en mode téléphonique." -#: core/models.py:409 core/models.py:563 +#: core/models.py:424 core/models.py:578 msgid "Room" msgstr "Salle" -#: core/models.py:410 +#: core/models.py:425 msgid "Rooms" msgstr "Salles" -#: core/models.py:574 +#: core/models.py:589 msgid "Worker ID" msgstr "ID du Worker" -#: core/models.py:576 +#: core/models.py:591 msgid "" "Enter an identifier for the worker recording.This ID is retained even when " "the worker stops, allowing for easy tracking." @@ -335,153 +365,174 @@ msgstr "" "Entrez un identifiant pour l'enregistrement du Worker. Cet identifiant est " "conservé même lorsque le Worker s'arrête, permettant un suivi facile." -#: core/models.py:584 +#: core/models.py:599 msgid "Recording mode" msgstr "Mode d'enregistrement" -#: core/models.py:585 +#: core/models.py:600 msgid "Defines the mode of recording being called." msgstr "Définit le mode d'enregistrement appelé." -#: core/models.py:590 core/models.py:591 +#: core/models.py:605 core/models.py:606 msgid "Recording options" msgstr "Options d'enregistrement" -#: core/models.py:597 +#: core/models.py:613 +msgid "External Process ID" +msgstr "ID Traitement externe" + +#: core/models.py:614 +msgid "ID of the external process associated with the recording." +msgstr "ID du traitement externe associé avec l'enregistrement." + +#: core/models.py:620 msgid "Recording" msgstr "Enregistrement" -#: core/models.py:598 +#: core/models.py:621 msgid "Recordings" msgstr "Enregistrements" -#: core/models.py:706 +#: core/models.py:731 msgid "Recording/user relation" msgstr "Relation enregistrement/utilisateur" -#: core/models.py:707 +#: core/models.py:732 msgid "Recording/user relations" msgstr "Relations enregistrement/utilisateur" -#: core/models.py:713 +#: core/models.py:738 msgid "This user is already in this recording." msgstr "Cet utilisateur est déjà dans cet enregistrement." -#: core/models.py:719 +#: core/models.py:744 msgid "This team is already in this recording." msgstr "Cette équipe est déjà dans cet enregistrement." -#: core/models.py:725 +#: core/models.py:750 msgid "Either user or team must be set, not both." msgstr "Soit l'utilisateur, soit l'équipe doit être défini, pas les deux." -#: core/models.py:742 +#: core/models.py:767 msgid "Create rooms" msgstr "Créer des salles" -#: core/models.py:743 +#: core/models.py:768 msgid "List rooms" msgstr "Lister les salles" -#: core/models.py:744 +#: core/models.py:769 msgid "Retrieve room details" msgstr "Afficher les détails d’une salle" -#: core/models.py:745 +#: core/models.py:770 msgid "Update rooms" msgstr "Mettre à jour les salles" -#: core/models.py:746 +#: core/models.py:771 msgid "Delete rooms" msgstr "Supprimer les salles" -#: core/models.py:759 +#: core/models.py:784 msgid "Application name" msgstr "Nom de l’application" -#: core/models.py:760 +#: core/models.py:785 msgid "Descriptive name for this application." msgstr "Nom descriptif de cette application." -#: core/models.py:770 +#: core/models.py:795 msgid "Hashed on Save. Copy it now if this is a new secret." msgstr "" "Haché lors de l’enregistrement. Copiez-le maintenant s’il s’agit d’un " "nouveau secret." -#: core/models.py:781 +#: core/models.py:806 msgid "Application" msgstr "Application" -#: core/models.py:782 +#: core/models.py:807 msgid "Applications" msgstr "Applications" -#: core/models.py:805 +#: core/models.py:830 msgid "Enter a valid domain" msgstr "Saisissez un domaine valide" -#: core/models.py:808 +#: core/models.py:833 msgid "Domain" msgstr "Domaine" -#: core/models.py:809 +#: core/models.py:834 msgid "Email domain this application can act on behalf of." msgstr "Domaine de messagerie au nom duquel cette application peut agir." -#: core/models.py:821 +#: core/models.py:846 msgid "Application domain" msgstr "Domaine d’application" -#: core/models.py:822 +#: core/models.py:847 msgid "Application domains" msgstr "Domaines d’application" -#: core/models.py:840 +#: core/models.py:865 msgid "Pending" msgstr "En attente" -#: core/models.py:848 +#: core/models.py:866 +msgid "Analyzing" +msgstr "" + +#: core/models.py:873 msgid "Ready" msgstr "Prêt" -#: core/models.py:854 +#: core/models.py:879 msgid "Background image" msgstr "Image de fond" -#: core/models.py:866 +#: core/models.py:891 msgid "title" msgstr "Titre" -#: core/models.py:890 +#: core/models.py:915 msgid "Malware detection info when the analysis status is unsafe." msgstr "" "Information concernant la détection de Malware cand le statut n'est pas sain" -#: core/models.py:895 +#: core/models.py:920 msgid "File" msgstr "Fichier" -#: core/models.py:896 +#: core/models.py:921 msgid "Files" msgstr "Fichiers" -#: core/models.py:1000 +#: core/models.py:1041 #, fuzzy #| msgid "This user is already in this recording." msgid "This file is already hard deleted." msgstr "Ce fichier a été supprimé." -#: core/models.py:1010 +#: core/models.py:1051 msgid "To hard delete a file, it must first be soft deleted." msgstr "" "Pour supprimer définitivement un fichier il doit d'abord avoir été marqué " "comme supprimé (soft delete)" -#: core/recording/event/notification.py:116 +#: core/recording/event/notification.py:123 msgid "Your recording is ready" msgstr "Votre enregistrement est prêt" +#: core/recording/event/notification.py:194 +msgid "Transcription" +msgstr "Transcription" + +#: core/recording/event/notification.py:204 +#, python-brace-format +msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}" +msgstr "Réunion \"{room}\" du {room_recording_date} à {room_recording_time}" + #: core/services/invitation.py:44 #, python-brace-format msgid "Video call in progress: {sender.email} is waiting for you to connect" @@ -577,7 +628,7 @@ msgid "To keep this recording permanently:" msgstr "Pour conserver cet enregistrement de façon permanente :" #: core/templates/mail/html/screen_recording.html:208 -#: core/templates/mail/text/screen_recording.txt:13 +#, python-format msgid "Click the \"Open\" link below " msgstr "Cliquez sur le lien \"Ouvrir\" ci-dessous " @@ -606,18 +657,24 @@ msgstr "" " Si vous avez des questions ou besoin d'assistance, veuillez contacter notre " "équipe d'assistance à %(support_email)s. " -#: meet/settings.py:223 +#: core/templates/mail/text/screen_recording.txt:13 +#, fuzzy, python-format +#| msgid "Click the \"Open\" link below " +msgid "Click the \"Open [%(link)s]\" link below " +msgstr "Cliquez sur le lien \"Ouvrir\" ci-dessous " + +#: meet/settings.py:228 msgid "English" msgstr "Anglais" -#: meet/settings.py:224 +#: meet/settings.py:229 msgid "French" msgstr "Français" -#: meet/settings.py:225 +#: meet/settings.py:230 msgid "Dutch" msgstr "Néerlandais" -#: meet/settings.py:226 +#: meet/settings.py:231 msgid "German" msgstr "Allemand" diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po index 78328812..236a187d 100644 --- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po +++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-04-21 14:09+0000\n" +"POT-Creation-Date: 2026-07-02 10:47+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,70 +17,92 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: core/admin.py:29 +#: core/admin.py:67 msgid "Personal info" msgstr "Persoonlijke informatie" -#: core/admin.py:42 +#: core/admin.py:80 msgid "Permissions" msgstr "Rechten" -#: core/admin.py:54 +#: core/admin.py:92 msgid "Important dates" msgstr "Belangrijke datums" -#: core/admin.py:132 core/admin.py:275 +#: core/admin.py:206 +msgid "Content" +msgstr "" + +#: core/admin.py:217 +#, fuzzy +#| msgid "Delete rooms" +msgid "Deletion" +msgstr "Ruimtes verwijderen" + +#: core/admin.py:226 +msgid "Derived info" +msgstr "" + +#: core/admin.py:237 +msgid "Timestamps" +msgstr "" + +#: core/admin.py:240 +msgid "File preview" +msgstr "" + +#: core/admin.py:300 core/admin.py:443 msgid "No owner" msgstr "Geen eigenaar" -#: core/admin.py:135 core/admin.py:278 +#: core/admin.py:303 core/admin.py:446 msgid "Multiple owners" msgstr "Meerdere eigenaren" -#: core/admin.py:148 +#: core/admin.py:316 msgid "Resend notification to external service" msgstr "Melding opnieuw verzenden naar externe dienst" -#: core/admin.py:171 +#: core/admin.py:339 #, python-format msgid "Failed to notify for recording %(id)s" msgstr "Melding voor opname %(id)s mislukt" -#: core/admin.py:179 +#: core/admin.py:347 #, python-format msgid "Failed to notify for recording %(id)s: %(error)s" msgstr "Melding voor opname %(id)s mislukt: %(error)s" -#: core/admin.py:187 +#: core/admin.py:355 #, python-format msgid "Successfully sent notifications for %(count)s recording(s)." msgstr "Meldingen succesvol verzonden voor %(count)s opname(n)." -#: core/admin.py:195 +#: core/admin.py:363 #, python-format msgid "Skipped %(count)s expired recording(s)." msgstr "%(count)s verlopen opname(n) overgeslagen." -#: core/admin.py:200 +#: core/admin.py:368 msgid "Mark selected recordings as 'Failed to Stop'" msgstr "Geselecteerde opnames markeren als 'Mislukt bij stoppen'" -#: core/admin.py:218 +#: core/admin.py:386 #, python-format msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'." msgstr "%(count)s opname(s) succesvol gemarkeerd als 'Mislukt bij stoppen'." -#: core/admin.py:226 +#: core/admin.py:394 #, fuzzy, python-format #| msgid "Skipped %(count)s expired recording(s)." msgid "Skipped %(count)s recording(s) with an ineligible status." msgstr "%(count)s opname(s) met een niet-toegestane status overgeslagen." -#: core/admin.py:342 +#: core/admin.py:510 msgid "No scopes" msgstr "Geen scopes" -#: core/admin.py:344 +#: core/admin.py:512 msgid "Scopes" msgstr "Scopes" @@ -88,16 +110,16 @@ msgstr "Scopes" msgid "Creator is me" msgstr "Maker ben ik" -#: core/api/serializers.py:88 +#: core/api/serializers.py:89 msgid "You must be administrator or owner of a room to add accesses to it." msgstr "" "Je moet beheerder of eigenaar van een ruimte zijn om toegang toe te voegen." -#: core/api/serializers.py:509 +#: core/api/serializers.py:534 msgid "This file extension is not allowed." msgstr "Deze bestandsextensie is niet toegestaan." -#: core/api/viewsets.py:1090 +#: core/api/viewsets.py:1222 msgid "You have reached the maximum number of files for this type." msgstr "Het maximale aantal bestanden voor dit type is bereikt." @@ -145,51 +167,59 @@ msgstr "Stoppen mislukt" msgid "Notification succeeded" msgstr "Notificatie geslaagd" -#: core/models.py:89 +#: core/models.py:65 +msgid "External process successful" +msgstr "Externe procedure succesvol" + +#: core/models.py:67 +msgid "External process failed" +msgstr "Het externe proces is mislukt." + +#: core/models.py:96 msgid "SCREEN_RECORDING" msgstr "SCHERM_OPNAME" -#: core/models.py:90 +#: core/models.py:97 msgid "TRANSCRIPT" msgstr "TRANSCRIPT" -#: core/models.py:96 +#: core/models.py:103 msgid "Public Access" msgstr "Openbare toegang" -#: core/models.py:97 +#: core/models.py:104 msgid "Trusted Access" msgstr "Vertrouwde toegang" -#: core/models.py:98 +#: core/models.py:105 msgid "Restricted Access" msgstr "Beperkte toegang" -#: core/models.py:110 +#: core/models.py:117 msgid "id" msgstr "id" -#: core/models.py:111 +#: core/models.py:118 msgid "primary key for the record as UUID" msgstr "primaire sleutel voor het record als UUID" -#: core/models.py:117 +#: core/models.py:124 msgid "created on" msgstr "aangemaakt op" -#: core/models.py:118 +#: core/models.py:125 msgid "date and time at which a record was created" msgstr "datum en tijd waarop een record werd aangemaakt" -#: core/models.py:123 +#: core/models.py:130 msgid "updated on" msgstr "bijgewerkt op" -#: core/models.py:124 +#: core/models.py:131 msgid "date and time at which a record was last updated" msgstr "datum en tijd waarop een record voor het laatst werd bijgewerkt" -#: core/models.py:144 +#: core/models.py:151 msgid "" "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/" "_ characters." @@ -197,11 +227,11 @@ msgstr "" "Voer een geldige sub in. Deze waarde mag alleen letters, cijfers en @/./+/-/" "_ tekens bevatten." -#: core/models.py:150 +#: core/models.py:157 msgid "sub" msgstr "sub" -#: core/models.py:152 +#: core/models.py:159 msgid "" "Optional for pending users; required upon account activation. 255 characters " "or fewer. Letters, numbers, and @/./+/-/_ characters only." @@ -209,55 +239,55 @@ msgstr "" "Optioneel voor gebruikers in afwachting; vereist bij accountactivering. " "Maximum 255 tekens. Alleen letters, cijfers en @/./+/-/_ toegestaan." -#: core/models.py:161 +#: core/models.py:168 msgid "identity email address" msgstr "identiteit e-mailadres" -#: core/models.py:166 +#: core/models.py:173 msgid "admin email address" msgstr "beheerder e-mailadres" -#: core/models.py:168 +#: core/models.py:175 msgid "full name" msgstr "volledige naam" -#: core/models.py:170 +#: core/models.py:177 msgid "short name" msgstr "korte naam" -#: core/models.py:176 +#: core/models.py:183 msgid "language" msgstr "taal" -#: core/models.py:177 +#: core/models.py:184 msgid "The language in which the user wants to see the interface." msgstr "De taal waarin de gebruiker de interface wil zien." -#: core/models.py:183 +#: core/models.py:190 msgid "The timezone in which the user wants to see times." msgstr "De tijdzone waarin de gebruiker tijden wil zien." -#: core/models.py:186 +#: core/models.py:193 msgid "device" msgstr "apparaat" -#: core/models.py:188 +#: core/models.py:195 msgid "Whether the user is a device or a real user." msgstr "Of de gebruiker een apparaat is of een echte gebruiker." -#: core/models.py:191 +#: core/models.py:198 msgid "staff status" msgstr "personeelsstatus" -#: core/models.py:193 +#: core/models.py:200 msgid "Whether the user can log into this admin site." msgstr "Of de gebruiker kan inloggen op deze beheersite." -#: core/models.py:196 +#: core/models.py:203 msgid "active" msgstr "actief" -#: core/models.py:199 +#: core/models.py:206 msgid "" "Whether this user should be treated as active. Unselect this instead of " "deleting accounts." @@ -265,64 +295,64 @@ msgstr "" "Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in " "plaats van accounts te verwijderen." -#: core/models.py:212 +#: core/models.py:219 msgid "user" msgstr "gebruiker" -#: core/models.py:213 +#: core/models.py:220 msgid "users" msgstr "gebruikers" -#: core/models.py:272 +#: core/models.py:286 msgid "Resource" msgstr "Bron" -#: core/models.py:273 +#: core/models.py:287 msgid "Resources" msgstr "Bronnen" -#: core/models.py:331 +#: core/models.py:345 msgid "Resource access" msgstr "Brontoegang" -#: core/models.py:332 +#: core/models.py:346 msgid "Resource accesses" msgstr "Brontoegangsrechten" -#: core/models.py:338 +#: core/models.py:352 msgid "Resource access with this User and Resource already exists." msgstr "Brontoegang met deze gebruiker en bron bestaat al." -#: core/models.py:394 +#: core/models.py:409 msgid "Visio room configuration" msgstr "Visio-ruimteconfiguratie" -#: core/models.py:395 +#: core/models.py:410 msgid "Values for Visio parameters to configure the room." msgstr "Waarden voor Visio-parameters om de ruimte te configureren." -#: core/models.py:402 +#: core/models.py:417 msgid "Room PIN code" msgstr "Pincode van de kamer" -#: core/models.py:403 +#: core/models.py:418 msgid "Unique n-digit code that identifies this room in telephony mode." msgstr "" "Unieke n-cijferige code die deze kamer identificeert in telefonie-modus." -#: core/models.py:409 core/models.py:563 +#: core/models.py:424 core/models.py:578 msgid "Room" msgstr "Ruimte" -#: core/models.py:410 +#: core/models.py:425 msgid "Rooms" msgstr "Ruimtes" -#: core/models.py:574 +#: core/models.py:589 msgid "Worker ID" msgstr "Worker ID" -#: core/models.py:576 +#: core/models.py:591 msgid "" "Enter an identifier for the worker recording.This ID is retained even when " "the worker stops, allowing for easy tracking." @@ -330,140 +360,152 @@ msgstr "" "Voer een identificatie in voor de worker-opname. Deze ID blijft behouden, " "zelfs wanneer de worker stopt, waardoor eenvoudige tracking mogelijk is." -#: core/models.py:584 +#: core/models.py:599 msgid "Recording mode" msgstr "Opnamemodus" -#: core/models.py:585 +#: core/models.py:600 msgid "Defines the mode of recording being called." msgstr "Definieert de modus van opname die wordt aangeroepen." -#: core/models.py:590 core/models.py:591 +#: core/models.py:605 core/models.py:606 msgid "Recording options" msgstr "Opnameopties" -#: core/models.py:597 +#: core/models.py:613 +msgid "External Process ID" +msgstr "Externe proces-ID" + +#: core/models.py:614 +msgid "ID of the external process associated with the recording." +msgstr "ID van het externe proces dat aan de opname is gekoppeld." + +#: core/models.py:620 msgid "Recording" msgstr "Opname" -#: core/models.py:598 +#: core/models.py:621 msgid "Recordings" msgstr "Opnames" -#: core/models.py:706 +#: core/models.py:731 msgid "Recording/user relation" msgstr "Opname/gebruiker-relatie" -#: core/models.py:707 +#: core/models.py:732 msgid "Recording/user relations" msgstr "Opname/gebruiker-relaties" -#: core/models.py:713 +#: core/models.py:738 msgid "This user is already in this recording." msgstr "Deze gebruiker is al in deze opname." -#: core/models.py:719 +#: core/models.py:744 msgid "This team is already in this recording." msgstr "Dit team is al in deze opname." -#: core/models.py:725 +#: core/models.py:750 msgid "Either user or team must be set, not both." msgstr "Ofwel gebruiker of team moet worden ingesteld, niet beide." -#: core/models.py:742 +#: core/models.py:767 msgid "Create rooms" msgstr "Ruimtes aanmaken" -#: core/models.py:743 +#: core/models.py:768 msgid "List rooms" msgstr "Ruimtes weergeven" -#: core/models.py:744 +#: core/models.py:769 msgid "Retrieve room details" msgstr "Details van een ruimte ophalen" -#: core/models.py:745 +#: core/models.py:770 msgid "Update rooms" msgstr "Ruimtes bijwerken" -#: core/models.py:746 +#: core/models.py:771 msgid "Delete rooms" msgstr "Ruimtes verwijderen" -#: core/models.py:759 +#: core/models.py:784 msgid "Application name" msgstr "Naam van de applicatie" -#: core/models.py:760 +#: core/models.py:785 msgid "Descriptive name for this application." msgstr "Beschrijvende naam voor deze applicatie." -#: core/models.py:770 +#: core/models.py:795 msgid "Hashed on Save. Copy it now if this is a new secret." msgstr "" "Wordt gehasht bij het opslaan. Kopieer het nu als dit een nieuw geheim is." -#: core/models.py:781 +#: core/models.py:806 msgid "Application" msgstr "Applicatie" -#: core/models.py:782 +#: core/models.py:807 msgid "Applications" msgstr "Applicaties" -#: core/models.py:805 +#: core/models.py:830 msgid "Enter a valid domain" msgstr "Voer een geldig domein in" -#: core/models.py:808 +#: core/models.py:833 msgid "Domain" msgstr "Domein" -#: core/models.py:809 +#: core/models.py:834 msgid "Email domain this application can act on behalf of." msgstr "E-maildomein namens welke deze applicatie kan handelen." -#: core/models.py:821 +#: core/models.py:846 msgid "Application domain" msgstr "Applicatiedomein" -#: core/models.py:822 +#: core/models.py:847 msgid "Application domains" msgstr "Applicatiedomeinen" -#: core/models.py:840 +#: core/models.py:865 msgid "Pending" msgstr "In afwachting" -#: core/models.py:848 +#: core/models.py:866 +msgid "Analyzing" +msgstr "" + +#: core/models.py:873 msgid "Ready" msgstr "Klaar" -#: core/models.py:854 +#: core/models.py:879 msgid "Background image" msgstr "Achtergrondafbeelding" -#: core/models.py:866 +#: core/models.py:891 msgid "title" msgstr "Titel" -#: core/models.py:890 +#: core/models.py:915 msgid "Malware detection info when the analysis status is unsafe." msgstr "Informatie over malwaredetectie wanneer de analysestatus onveilig is." -#: core/models.py:895 +#: core/models.py:920 msgid "File" msgstr "Bestand" -#: core/models.py:896 +#: core/models.py:921 msgid "Files" msgstr "Bestanden" -#: core/models.py:1000 +#: core/models.py:1041 msgid "This file is already hard deleted." msgstr "Dit bestand is al definitief verwijderd." -#: core/models.py:1010 +#: core/models.py:1051 #, fuzzy #| msgid "To hard delete a file, it must first be soft deleted." msgid "To hard delete a file, it must first be soft deleted." @@ -471,10 +513,20 @@ msgstr "" "Om een bestand definitief te verwijderen, moet het eerst zacht verwijderd " "zijn." -#: core/recording/event/notification.py:116 +#: core/recording/event/notification.py:123 msgid "Your recording is ready" msgstr "Je opname is klaar" +#: core/recording/event/notification.py:194 +msgid "Transcription" +msgstr "Transcriptie" + +#: core/recording/event/notification.py:204 +#, python-brace-format +msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}" +msgstr "" +"Vergadering \"{room}\" op {room_recording_date} om {room_recording_time}" + #: core/services/invitation.py:44 #, python-brace-format msgid "Video call in progress: {sender.email} is waiting for you to connect" @@ -570,7 +622,7 @@ msgid "To keep this recording permanently:" msgstr "Om deze opname permanent te bewaren:" #: core/templates/mail/html/screen_recording.html:208 -#: core/templates/mail/text/screen_recording.txt:13 +#, python-format msgid "Click the \"Open\" link below " msgstr "Klik op de \"Openen\"-link hieronder " @@ -599,18 +651,24 @@ msgstr "" " Als je vragen hebt of hulp nodig hebt, neem dan contact op met ons support " "team via %(support_email)s. " -#: meet/settings.py:223 +#: core/templates/mail/text/screen_recording.txt:13 +#, fuzzy, python-format +#| msgid "Click the \"Open\" link below " +msgid "Click the \"Open [%(link)s]\" link below " +msgstr "Klik op de \"Openen\"-link hieronder " + +#: meet/settings.py:228 msgid "English" msgstr "Engels" -#: meet/settings.py:224 +#: meet/settings.py:229 msgid "French" msgstr "Frans" -#: meet/settings.py:225 +#: meet/settings.py:230 msgid "Dutch" msgstr "Nederlands" -#: meet/settings.py:226 +#: meet/settings.py:231 msgid "German" msgstr "Duits" diff --git a/src/backend/meet/settings.py b/src/backend/meet/settings.py index 0de934da..1aacc705 100755 --- a/src/backend/meet/settings.py +++ b/src/backend/meet/settings.py @@ -748,12 +748,30 @@ class Base(Configuration): environ_prefix=None, ) + SUMMARY_SERVICE_VERSION = values.PositiveIntegerValue( + 1, environ_name="SUMMARY_SERVICE_VERSION", environ_prefix=None + ) SUMMARY_SERVICE_ENDPOINT = values.Value( None, environ_name="SUMMARY_SERVICE_ENDPOINT", environ_prefix=None ) SUMMARY_SERVICE_API_TOKEN = SecretFileValue( None, environ_name="SUMMARY_SERVICE_API_TOKEN", environ_prefix=None ) + SUMMARY_SERVICE_WEBHOOK_API_TOKEN = SecretFileValue( + None, environ_name="SUMMARY_SERVICE_WEBHOOK_API_TOKEN", environ_prefix=None + ) + SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS = ( + values.PositiveIntegerValue( + 60 * 60 * 24, + environ_name="SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS", + environ_prefix=None, + ) + ) + TRANSCRIPTION_SATISFACTION_FORM_BASE_URL = values.Value( + None, + environ_name="TRANSCRIPTION_SATISFACTION_FORM_BASE_URL", + environ_prefix=None, + ) SCREEN_RECORDING_BASE_URL = values.Value( None, environ_name="SCREEN_RECORDING_BASE_URL", environ_prefix=None ) diff --git a/src/frontend/src/features/recording/routes/RecordingDownload.tsx b/src/frontend/src/features/recording/routes/RecordingDownload.tsx index e50c3296..bc129dc7 100644 --- a/src/frontend/src/features/recording/routes/RecordingDownload.tsx +++ b/src/frontend/src/features/recording/routes/RecordingDownload.tsx @@ -57,7 +57,9 @@ const RecordingDownload = () => { const isSaved = data?.status === RecordingStatus.Saved || data?.status === RecordingStatus.NotificationSucceed || - data?.status === RecordingStatus.FailedToStop + data?.status === RecordingStatus.FailedToStop || + data?.status === RecordingStatus.ExternalProcessFailed || + data?.status === RecordingStatus.ExternalProcessSuccessful const pageTitle = useMemo(() => { if (isError) return `${APP_TITLE} - ${t('error.title')}` @@ -91,6 +93,8 @@ const RecordingDownload = () => { } if ( + data.status !== RecordingStatus.ExternalProcessFailed && + data.status !== RecordingStatus.ExternalProcessSuccessful && data.status !== RecordingStatus.Saved && data.status !== RecordingStatus.NotificationSucceed && data.status !== RecordingStatus.FailedToStop diff --git a/src/frontend/src/features/recording/types.ts b/src/frontend/src/features/recording/types.ts index d41b6090..fc450bfe 100644 --- a/src/frontend/src/features/recording/types.ts +++ b/src/frontend/src/features/recording/types.ts @@ -12,4 +12,6 @@ export enum RecordingStatus { FailedToStart = 'failedToStart', FailedToStop = 'failedToStop', NotificationSucceed = 'notification_succeeded', + ExternalProcessSuccessful = 'external_process_successful', + ExternalProcessFailed = 'external_process_failed', } diff --git a/src/helm/env.d/common.yaml.gotmpl b/src/helm/env.d/common.yaml.gotmpl index 26fbb043..0d74ad3e 100644 --- a/src/helm/env.d/common.yaml.gotmpl +++ b/src/helm/env.d/common.yaml.gotmpl @@ -38,7 +38,7 @@ _summaryEnvVars: &summaryEnvVars "allowed_push_to_docs": false }, { - "id": "visio", + "id": "meet", "api_key": "password", "webhook_url": "https://meet.127.0.0.1.nip.io/api/v1.0/recordings/external-process-hook/", "webhook_api_key": "webhook-password",