Skip to content

Commit f5e0ddf

Browse files
cameledevlebaudantoine
authored andcommitted
✨(summary) add localization support for transcription context text
Transcription and summarization results were always generated using a French text structure (e.g. "Réunion du..."), regardless of user preference or meeting language. Introduced basic localization support to adapt generated string languages.
1 parent cd0cec7 commit f5e0ddf

13 files changed

Lines changed: 223 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to
1111
### Added
1212

1313
- 👷(docker) add arm64 platform support for image builds
14+
- ✨(summary) add localization support for transcription context text
1415

1516
### Changed
1617

gitlint/gitlint_emoji.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Gitlint extra rule to validate that the message title is of the form
33
"<gitmoji>(<scope>) <subject>"
44
"""
5+
56
from __future__ import unicode_literals
67

78
import re

src/backend/core/recording/event/notification.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ def _notify_summary_service(recording):
167167
owner_access.user.timezone
168168
).strftime("%H:%M"),
169169
"download_link": f"{get_recording_download_base_url()}/{recording.id}",
170+
"context_language": owner_access.user.language,
170171
}
171172

172173
headers = {

src/summary/summary/api/route/tasks.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
settings = get_settings()
1616

1717

18-
class TaskCreation(BaseModel):
19-
"""Task data."""
18+
class TranscribeSummarizeTaskCreation(BaseModel):
19+
"""Transcription and summarization parameters."""
2020

2121
owner_id: str
2222
filename: str
@@ -28,6 +28,7 @@ class TaskCreation(BaseModel):
2828
recording_time: Optional[str]
2929
language: Optional[str]
3030
download_link: Optional[str]
31+
context_language: Optional[str] = None
3132

3233
@field_validator("language")
3334
@classmethod
@@ -45,8 +46,8 @@ def validate_language(cls, v):
4546

4647

4748
@router.post("/")
48-
async def create_task(request: TaskCreation):
49-
"""Create a task."""
49+
async def create_transcribe_summarize_task(request: TranscribeSummarizeTaskCreation):
50+
"""Create a transcription and summarization task."""
5051
task = process_audio_transcribe_summarize_v2.apply_async(
5152
args=[
5253
request.owner_id,
@@ -59,6 +60,7 @@ async def create_task(request: TaskCreation):
5960
request.recording_time,
6061
request.language,
6162
request.download_link,
63+
request.context_language,
6264
],
6365
queue=settings.transcribe_queue,
6466
)

src/summary/summary/core/celery_worker.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from summary.core.config import get_settings
1919
from summary.core.file_service import FileService, FileServiceException
2020
from summary.core.llm_service import LLMException, LLMObservability, LLMService
21+
from summary.core.locales import get_locale
2122
from summary.core.prompt import (
2223
FORMAT_NEXT_STEPS,
2324
FORMAT_PLAN,
@@ -121,6 +122,7 @@ def process_audio_transcribe_summarize_v2(
121122
recording_time: Optional[str],
122123
language: Optional[str],
123124
download_link: Optional[str],
125+
context_language: Optional[str] = None,
124126
):
125127
"""Process an audio file by transcribing it and generating a summary.
126128
@@ -129,6 +131,19 @@ def process_audio_transcribe_summarize_v2(
129131
2. Transcribes the audio using WhisperX model
130132
3. Sends the results via webhook
131133
134+
Args:
135+
self: Celery task instance (passed on with bind=True)
136+
owner_id: Unique identifier of the recording owner.
137+
filename: Name of the audio file in MinIO storage.
138+
email: Email address of the recording owner.
139+
sub: OIDC subject identifier of the recording owner.
140+
received_at: Unix timestamp when the recording was received.
141+
room: room name where the recording took place.
142+
recording_date: Date of the recording (localized display string).
143+
recording_time: Time of the recording (localized display string).
144+
language: ISO 639-1 language code for transcription.
145+
download_link: URL to download the original recording.
146+
context_language: ISO 639-1 language code of the meeting summary context text.
132147
"""
133148
logger.info(
134149
"Notification received | Owner: %s | Room: %s",
@@ -145,6 +160,7 @@ def process_audio_transcribe_summarize_v2(
145160
max_retries=settings.whisperx_max_retries,
146161
)
147162

163+
# Transcription
148164
try:
149165
with (
150166
file_service.prepare_audio_file(filename) as (audio_file, metadata),
@@ -183,7 +199,10 @@ def process_audio_transcribe_summarize_v2(
183199

184200
metadata_manager.track_transcription_metadata(task_id, transcription)
185201

186-
formatter = TranscriptFormatter()
202+
# For locale of context, use in decreasing priority context_language,
203+
# language (of meeting), default context language
204+
locale = get_locale(context_language, language)
205+
formatter = TranscriptFormatter(locale)
187206

188207
content, title = formatter.format(
189208
transcription,
@@ -221,6 +240,7 @@ def process_audio_transcribe_summarize_v2(
221240

222241
metadata_manager.capture(task_id, settings.posthog_event_success)
223242

243+
# LLM Summarization
224244
if (
225245
analytics.is_feature_enabled("summary-enabled", distinct_id=owner_id)
226246
and settings.is_summary_enabled
@@ -336,9 +356,7 @@ def summarize_transcription(
336356
summary = tldr + "\n\n" + cleaned_summary + "\n\n" + next_steps
337357

338358
data = {
339-
"title": settings.summary_title_template.format(
340-
title=title,
341-
),
359+
"title": settings.summary_title_template.format(title=title),
342360
"content": summary,
343361
"email": email,
344362
"sub": sub,

src/summary/summary/core/config.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Application configuration and settings."""
22

33
from functools import lru_cache
4-
from typing import Annotated, List, Optional, Set
4+
from typing import Annotated, List, Literal, Optional, Set
55

66
from fastapi import Depends
77
from pydantic import SecretStr
@@ -51,7 +51,6 @@ class Settings(BaseSettings):
5151

5252
# Transcription processing
5353
hallucination_patterns: List[str] = ["Vap'n'Roll Thierry"]
54-
hallucination_replacement_text: str = "[Texte impossible à transcrire]"
5554

5655
# Webhook-related settings
5756
webhook_max_retries: int = 2
@@ -60,11 +59,10 @@ class Settings(BaseSettings):
6059
webhook_api_token: SecretStr
6160
webhook_url: str
6261

62+
# Locale
63+
default_context_language: Literal["de", "en", "fr", "nl"] = "fr"
64+
6365
# Output related settings
64-
document_default_title: Optional[str] = "Transcription"
65-
document_title_template: Optional[str] = (
66-
'Réunion "{room}" du {room_recording_date} à {room_recording_time}'
67-
)
6866
summary_title_template: Optional[str] = "Résumé de {title}"
6967

7068
# Summary related settings
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Locale support for the summary service."""
2+
3+
from typing import Optional
4+
5+
from summary.core.config import get_settings
6+
from summary.core.locales import de, en, fr, nl
7+
from summary.core.locales.strings import LocaleStrings
8+
9+
_LOCALES = {"fr": fr, "en": en, "de": de, "nl": nl}
10+
11+
12+
def get_locale(*languages: Optional[str]) -> LocaleStrings:
13+
"""Return locale strings for the first matching language candidate.
14+
15+
Accept language codes in decreasing priority order and return the
16+
locale for the first one that matches a known locale.
17+
Fall back to the configured default_context_language.
18+
"""
19+
for lang in languages:
20+
if not lang:
21+
continue
22+
if lang in _LOCALES:
23+
return _LOCALES[lang].STRINGS
24+
25+
# Provide fallback for longer formats of ISO 639-1 (e.g. "en-au" -> "en")
26+
base_lang = lang.split("-")[0]
27+
if base_lang in _LOCALES:
28+
return _LOCALES[base_lang].STRINGS
29+
30+
return _LOCALES[get_settings().default_context_language].STRINGS
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""German locale strings."""
2+
3+
from summary.core.locales.strings import LocaleStrings
4+
5+
STRINGS = LocaleStrings(
6+
empty_transcription="""
7+
**In Ihrer Transkription wurde kein Audioinhalt erkannt.**
8+
9+
*Wenn Sie glauben, dass es sich um einen Fehler handelt, zögern Sie nicht,
10+
unseren technischen Support zu kontaktieren: visio@numerique.gouv.fr*
11+
12+
.
13+
14+
.
15+
16+
.
17+
18+
Einige Punkte, die wir Ihnen empfehlen zu überprüfen:
19+
- War ein Mikrofon aktiviert?
20+
- Waren Sie nah genug am Mikrofon?
21+
- Ist das Mikrofon von guter Qualität?
22+
- Dauert die Aufnahme länger als 30 Sekunden?
23+
24+
""",
25+
download_header_template=(
26+
"\n*Laden Sie Ihre Aufnahme herunter, "
27+
"indem Sie [diesem Link folgen]({download_link})*\n"
28+
),
29+
hallucination_replacement_text="[Text konnte nicht transkribiert werden]",
30+
document_default_title="Transkription",
31+
document_title_template=(
32+
'Besprechung "{room}" am {room_recording_date} um {room_recording_time}'
33+
),
34+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""English locale strings."""
2+
3+
from summary.core.locales.strings import LocaleStrings
4+
5+
STRINGS = LocaleStrings(
6+
empty_transcription="""
7+
**No audio content was detected in your transcription.**
8+
9+
*If you believe this is an error, please do not hesitate to contact
10+
our technical support: visio@numerique.gouv.fr*
11+
12+
.
13+
14+
.
15+
16+
.
17+
18+
A few things we recommend you check:
19+
- Was a microphone enabled?
20+
- Were you close enough to the microphone?
21+
- Is the microphone of good quality?
22+
- Is the recording longer than 30 seconds?
23+
24+
""",
25+
download_header_template=(
26+
"\n*Download your recording by [following this link]({download_link})*\n"
27+
),
28+
hallucination_replacement_text="[Unable to transcribe text]",
29+
document_default_title="Transcription",
30+
document_title_template=(
31+
'Meeting "{room}" on {room_recording_date} at {room_recording_time}'
32+
),
33+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""French locale strings (default)."""
2+
3+
from summary.core.locales.strings import LocaleStrings
4+
5+
STRINGS = LocaleStrings(
6+
empty_transcription="""
7+
**Aucun contenu audio n'a été détecté dans votre transcription.**
8+
9+
*Si vous pensez qu'il s'agit d'une erreur, n'hésitez pas à contacter
10+
notre support technique : visio@numerique.gouv.fr*
11+
12+
.
13+
14+
.
15+
16+
.
17+
18+
Quelques points que nous vous conseillons de vérifier :
19+
- Un micro était-il activé ?
20+
- Étiez-vous suffisamment proche ?
21+
- Le micro est-il de bonne qualité ?
22+
- L'enregistrement dure-t-il plus de 30 secondes ?
23+
24+
""",
25+
download_header_template=(
26+
"\n*Télécharger votre enregistrement en [suivant ce lien]({download_link})*\n"
27+
),
28+
hallucination_replacement_text="[Texte impossible à transcrire]",
29+
document_default_title="Transcription",
30+
document_title_template=(
31+
'Réunion "{room}" du {room_recording_date} à {room_recording_time}'
32+
),
33+
)

0 commit comments

Comments
 (0)