Compare commits

..

12 Commits

Author SHA1 Message Date
Florent Chehab c21b30585f 🔨(docker) set file permissions to docker user
Set the app related files as owned by the docker user
the image was build with. This fixes a sync permission
error in tilt.

Also improved tilt setup so that correct uid / gid are infered
for the build args.
2026-03-10 13:21:53 +01:00
Florent Chehab 574f575626 🔨(python-env) migrate meet main app to UV
Migrate main meet app to use UV for dependancy management.
Also optimized the backend image build sequence for faster rebuilds
when dependencies don't change.
Also removed compiled django translations files are they are done in the build
process now.

Changes inspired by drive repo.
2026-03-10 12:58:23 +01:00
Cyril d5c53c7dd4 ️(frontend) improve ui and qria labels for help article links
Screen readers now announce full link text instead of "Learn more".
2026-03-10 11:36:10 +01:00
Cyril f43ac2e4eb ♻️(refactor) apply caption text size to subtitles
Use accessibilityStore.captionTextSize in Transcription component.
2026-03-10 11:15:52 +01:00
Cyril ea5dd5bc0e (feat) add caption text size setting in Accessibility tab
Extract CaptionsSettings component, Settings > Accessibility > Captions.
2026-03-10 11:14:40 +01:00
Cyril 86427fa2b7 🌐(i18n) add captions text size setting translations
EN, FR, DE, NL for Accessibility > Captions > Text size.
2026-03-10 11:14:39 +01:00
Cyril 3959c3657c ️(frontend) add caption text size to accessibility store
Persist captionTextSize (small/medium/large) in user preferences.
2026-03-10 11:14:08 +01:00
Cyril 191f8abbcc ️(frontend) improve chat a11y for screen readers
Chat messages announced via role="log" when open, toast+announce when closed.
2026-03-09 16:06:08 +01:00
Cyril d91f8bb6e1 Merge branch 'fix/homepage-knowmore-link' 2026-03-09 14:41:26 +01:00
lebaudantoine d612f9b26b 🩹(changelog) fix changelog organisation for v1.10 2026-03-09 14:32:30 +01:00
Cyril 042be17cfa ️(frontend) improve MoreLink a11y and UX on home page
Render MoreLink once in LeftColumn, make full phrase clickable and add icon.
2026-03-09 14:27:39 +01:00
Cyril d00f4fa695 ️(frontend) sync html lang attribute with i18n for screen readers
Set document.documentElement.lang in i18n init and on languageChanged,
2026-03-09 13:32:10 +01:00
40 changed files with 2729 additions and 137 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ __pycache__
**/__pycache__
**/*.pyc
venv
.venv
**/.venv
# System-specific files
.DS_Store
+16 -14
View File
@@ -123,16 +123,18 @@ jobs:
- name: Install Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
cache: "pip"
- name: Install development dependencies
run: pip install --user .[dev]
python-version-file: "src/backend/pyproject.toml"
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install the project
run: uv sync --locked --all-extras
- name: Check code formatting with ruff
run: ~/.local/bin/ruff format . --diff
run: uv run ruff format . --diff
- name: Lint code with ruff
run: ~/.local/bin/ruff check .
run: uv run ruff check .
- name: Lint code with pylint
run: ~/.local/bin/pylint meet demo core
run: uv run pylint meet demo core
lint-agents:
runs-on: ubuntu-latest
@@ -278,11 +280,11 @@ jobs:
- name: Install Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
cache: "pip"
- name: Install development dependencies
run: pip install --user .[dev]
python-version-file: "src/backend/pyproject.toml"
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install the dependencies
run: uv sync --locked --all-extras
- name: Install gettext (required to compile messages)
run: |
@@ -290,10 +292,10 @@ jobs:
sudo apt-get install -y gettext
- name: Generate a MO file from strings extracted from the project
run: python manage.py compilemessages
run: uv run python manage.py compilemessages
- name: Run tests
run: ~/.local/bin/pytest -n 2
run: uv run pytest -n 2
lint-front:
runs-on: ubuntu-latest
+10 -7
View File
@@ -10,17 +10,16 @@ and this project adheres to
### Changed
- ♿️(frontend) Caption text size setting for accessibility #1062
- ♿️(frontend) sync html lang attribute with i18n for screen readers #1111
- ♿️(frontend) improve MoreLink a11y and UX on home page #1112
- ♿(frontend) improve chat toast a11y for screen readers #1109
- ♿(frontend) improve ui and qria labels for help article links #1108
- 🔨(python-env) migrate meet main app to UV #1120
- 🔨(docker) set file permissions to docker user #1121
## [1.10.0] - 2026-03-05
### Fixed
- 🩹(frontend) remove incorrect reference to ProConnect on the prejoin #1080
- ✨(frontend) add Ctrl+Shift+/ to open shortcuts settings #1050
- ♿(frontend) announce selected state to screen readers #1081
- 💄(frontend) truncate long names with ellipsis in reaction overlay #1099
### Changed
- 🔒️(backend) enhance API input validation to strengthen security #1053
@@ -34,6 +33,10 @@ and this project adheres to
- 💄(frontend) truncate pinned participant name with ellipsis on overflow #1056
- ♿(frontend) prevent focus ring clipping on invite dialog #1078
- ♿(frontend) dynamic tab title when connected to meeting #1060
- 🩹(frontend) remove incorrect reference to ProConnect on the prejoin #1080
- ✨(frontend) add Ctrl+Shift+/ to open shortcuts settings #1050
- ♿(frontend) announce selected state to screen readers #1081
- 💄(frontend) truncate long names with ellipsis in reaction overlay #1099
### Added
+40 -23
View File
@@ -13,14 +13,28 @@ RUN apk update && \
# ---- Back-end builder image ----
FROM base AS back-builder
WORKDIR /builder
# Copy required python dependencies
COPY ./src/backend /builder
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
RUN mkdir /install && \
pip install --prefix=/install .
# Disable Python downloads, because we want to use the system interpreter
# across both images. If using a managed Python version, it needs to be
# copied from the build image into the final image;
ENV UV_PYTHON_DOWNLOADS=0
# install uv
COPY --from=ghcr.io/astral-sh/uv:0.10.9 /uv /uvx /bin/
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=src/backend/uv.lock,target=uv.lock \
--mount=type=bind,source=src/backend/pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --no-dev
COPY src/backend /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
# ---- mails ----
FROM node:20 AS mail-builder
@@ -30,7 +44,7 @@ COPY ./src/mail /mail/app
WORKDIR /mail/app
RUN yarn install --frozen-lockfile && \
yarn build
yarn build
# ---- static link collector ----
@@ -42,17 +56,17 @@ RUN apk add \
libmagic \
rdfind
# Copy installed python dependencies
COPY --from=back-builder /install /usr/local
# Copy Meet application (see .dockerignore)
COPY ./src/backend /app/
WORKDIR /app
# Copy the application from the builder
COPY --from=back-builder /app /app
ENV PATH="/app/.venv/bin:$PATH"
# collectstatic
RUN DJANGO_CONFIGURATION=Build DJANGO_JWT_PRIVATE_SIGNING_KEY=Dummy \
python manage.py collectstatic --noinput
python manage.py collectstatic --noinput
# Replace duplicated file by a symlink to decrease the overall size of the
# final image
@@ -81,14 +95,18 @@ COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
# docker user (see entrypoint).
RUN chmod g=u /etc/passwd
# Copy installed python dependencies
COPY --from=back-builder /install /usr/local
# Copy Meet application (see .dockerignore)
COPY ./src/backend /app/
# Copy the application from the builder
ARG DOCKER_USER
COPY --chown=${DOCKER_USER} --from=back-builder /app /app
WORKDIR /app
ENV PATH="/app/.venv/bin:$PATH"
# Generate compiled translation messages
RUN DJANGO_CONFIGURATION=Build \
python manage.py compilemessages --ignore=".venv/**/*"
# We wrap commands run in this container by the following entrypoint that
# creates a user on-the-fly with the container user ID (see USER) and root group
# ID.
@@ -103,10 +121,9 @@ USER root:root
# Install psql
RUN apk add postgresql-client
# Uninstall Meet and re-install it in editable mode along with development
# dependencies
RUN pip uninstall -y meet
RUN pip install -e .[dev]
# Install development dependencies
RUN --mount=from=ghcr.io/astral-sh/uv:0.10.9,source=/uv,target=/bin/uv \
uv sync --all-extras --locked
# Restore the un-privileged user running the application
ARG DOCKER_USER
@@ -115,7 +132,7 @@ USER ${DOCKER_USER}
# Target database host (e.g. database engine following docker compose services
# name) & port
ENV DB_HOST=postgresql \
DB_PORT=5432
DB_PORT=5432
# Run django development server
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
+9 -4
View File
@@ -14,11 +14,16 @@ if DEV_ENV == 'dev-keycloak':
def clean_old_images(image_name):
local('docker images -q %s | tail -n +2 | xargs -r docker rmi' % image_name)
uid = str(local('id -u', quiet=True)).strip()
gid = str(local('id -g', quiet=True)).strip()
DOCKER_USER = uid + ":" + gid
docker_build(
'localhost:5001/meet-backend:latest',
context='..',
dockerfile='../Dockerfile',
build_args={'DOCKER_USER': '1001:127'},
build_args={'DOCKER_USER': DOCKER_USER},
only=['./src/backend', './src/mail', './docker'],
target = 'backend-production',
live_update=[
@@ -34,7 +39,7 @@ clean_old_images('localhost:5001/meet-backend')
docker_build(
'localhost:5001/meet-frontend-dinum:latest',
context='..',
build_args={'DOCKER_USER': '1001:127'},
build_args={'DOCKER_USER': DOCKER_USER},
dockerfile='../docker/dinum-frontend/Dockerfile',
only=['./src/frontend', './docker', './.dockerignore'],
target = 'frontend-production',
@@ -59,7 +64,7 @@ clean_old_images('localhost:5001/meet-frontend-generic')
docker_build(
'localhost:5001/meet-summary:latest',
context='../src/summary',
build_args={'DOCKER_USER': '1001:127'},
build_args={'DOCKER_USER': DOCKER_USER},
dockerfile='../src/summary/Dockerfile',
only=['.'],
target = 'production',
@@ -72,7 +77,7 @@ clean_old_images('localhost:5001/meet-summary')
docker_build(
'localhost:5001/meet-agents:latest',
context='../src/agents',
build_args={'DOCKER_USER': '1001:127'},
build_args={'DOCKER_USER': DOCKER_USER},
dockerfile='../src/agents/Dockerfile',
only=['.'],
target = 'production',
+2
View File
@@ -80,6 +80,7 @@ services:
volumes:
- ./src/backend:/app
- ./data/static:/data/static
- /app/.venv
depends_on:
- postgresql
- mailcatcher
@@ -105,6 +106,7 @@ services:
volumes:
- ./src/backend:/app
- ./data/static:/data/static
- /app/.venv
depends_on:
- app-dev
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -11
View File
@@ -2,8 +2,8 @@
# Meet package
#
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
requires = ["uv_build>=0.10.9,<0.11.0"]
build-backend = "uv_build"
[project]
name = "meet"
@@ -21,8 +21,7 @@ classifiers = [
]
description = "A simple video and phone conferencing tool, powered by LiveKit"
keywords = ["Django", "Contacts", "Templates", "RBAC"]
license = { file = "LICENSE" }
readme = "README.md"
license = "MIT"
requires-python = ">=3.13"
dependencies = [
"boto3==1.42.49",
@@ -70,7 +69,7 @@ dependencies = [
"Homepage" = "https://github.com/suitenumerique/meet"
"Repository" = "https://github.com/suitenumerique/meet"
[project.optional-dependencies]
[dependency-groups]
dev = [
"django-extensions==4.1",
"drf-spectacular-sidecar==2026.1.1",
@@ -90,12 +89,13 @@ dev = [
"types-requests==2.32.4.20260107",
]
[tool.setuptools]
packages = { find = { where = ["."], exclude = ["tests"] } }
zip-safe = true
[tool.distutils.bdist_wheel]
universal = true
[tool.uv.build-backend]
module-root = ""
source-exclude = [
"**/tests/**",
"**/test_*.py",
"**/tests.py",
]
[tool.ruff]
exclude = [
+2365
View File
File diff suppressed because it is too large Load Diff
@@ -2,23 +2,25 @@ import { A, Text } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { useConfig } from '@/api/useConfig'
const appTitle = import.meta.env.VITE_APP_TITLE ?? 'LaSuite Meet'
export const MoreLink = () => {
const { t } = useTranslation('home')
const { data } = useConfig()
if (!data?.manifest_link) return
if (!data?.manifest_link) return null
return (
<Text as={'p'} variant={'sm'} style={{ padding: '1rem 0' }}>
<Text as="p" variant="sm" style={{ padding: '1rem 0' }}>
<A
href={data?.manifest_link}
target="_blank"
rel="noopener noreferrer"
aria-label={t('moreLinkLabel')}
externalIcon
aria-label={t('moreLinkLabel', { appTitle })}
>
{t('moreLink')}
</A>{' '}
{t('moreAbout', { appTitle: `${import.meta.env.VITE_APP_TITLE}` })}
{t('moreLink')} {t('moreAbout', { appTitle })}
</A>
</Text>
)
}
+1 -14
View File
@@ -258,23 +258,10 @@ export const Home = () => {
</DialogTrigger>
</div>
<Separator />
<div
className={css({
display: { base: 'none', lg: 'inline' },
})}
>
<MoreLink />
</div>
<MoreLink />
</LeftColumn>
<RightColumn>
<IntroSlider />
<div
className={css({
display: { base: 'inline', lg: 'none' },
})}
>
<MoreLink />
</div>
</RightColumn>
</Columns>
<LaterMeetingDialog
@@ -18,10 +18,15 @@ import {
ANIMATION_DURATION,
ReactionPortals,
} from '@/features/rooms/livekit/components/ReactionPortal'
import { layoutStore } from '@/stores/layout'
import { PanelId } from '@/features/rooms/livekit/hooks/useSidePanel'
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
export const MainNotificationToast = () => {
const room = useRoomContext()
const { triggerNotificationSound } = useNotificationSound()
const { t } = useTranslation('notifications')
const announce = useScreenReaderAnnounce()
const [reactions, setReactions] = useState<Reaction[]>([])
const instanceIdRef = useRef(0)
@@ -41,12 +46,21 @@ export const MainNotificationToast = () => {
},
{ timeout: NotificationDuration.MESSAGE }
)
if (layoutStore.activePanelId !== PanelId.CHAT) {
announce(
t('chatMessageReceived', {
name: participant.name || t('defaultName'),
message: chatMessage.message,
}),
'polite'
)
}
}
room.on(RoomEvent.ChatMessage, handleChatMessage)
return () => {
room.off(RoomEvent.ChatMessage, handleChatMessage)
}
}, [room, triggerNotificationSound])
}, [room, triggerNotificationSound, announce, t])
const handleEmoji = (emoji: string, participant: Participant) => {
if (!emoji || !Object.values(Emoji).includes(emoji as Emoji)) return
@@ -79,11 +79,20 @@ export const NoAccessView = ({
})}
>
{t(`${i18nKey}.body`)}
<br />
{helpArticle && (
<A href={helpArticle} target="_blank">
{t(`${i18nKey}.linkMore`)}
</A>
<>
{' '}
<A
href={helpArticle}
target="_blank"
rel="noopener noreferrer"
externalIcon
color="note"
aria-label={t(`${i18nKey}.linkAriaLabel`)}
>
{t(`${i18nKey}.linkMore`)}
</A>
</>
)}
</Text>
</VStack>
@@ -151,12 +151,16 @@ export const ScreenRecordingSidePanel = () => {
</H>
<Text variant="body" fullWidth>
{recordingMaxDuration
? t('body', {
max_duration: recordingMaxDuration,
})
? t('body', { max_duration: recordingMaxDuration })
: t('bodyWithoutMaxDuration')}{' '}
{data?.support?.help_article_recording && (
<A href={data.support.help_article_recording} target="_blank">
<A
href={data.support.help_article_recording}
target="_blank"
rel="noopener noreferrer"
externalIcon
aria-label={t('linkAriaLabel')}
>
{t('linkMore')}
</A>
)}
@@ -187,12 +187,16 @@ export const TranscriptSidePanel = () => {
</H>
<Text variant="body" fullWidth>
{recordingMaxDuration
? t('body', {
max_duration: recordingMaxDuration,
})
? t('body', { max_duration: recordingMaxDuration })
: t('bodyWithoutMaxDuration')}{' '}
{data?.support?.help_article_transcript && (
<A href={data.support.help_article_transcript} target="_blank">
<A
href={data.support.help_article_transcript}
target="_blank"
rel="noopener noreferrer"
externalIcon
aria-label={t('linkAriaLabel')}
>
{t('linkMore')}
</A>
)}
@@ -208,6 +212,7 @@ export const TranscriptSidePanel = () => {
href={data.transcription_destination}
target="_blank"
rel="noopener noreferrer"
externalIcon
>
{data.transcription_destination.replace('https://', '')}
</A>
@@ -148,17 +148,22 @@ export const Tools = () => {
className={css({
textStyle: 'sm',
paddingX: '0.75rem',
paddingTop: '0.25rem',
marginBottom: '1rem',
})}
>
{t('body')}{' '}
{data?.support?.help_article_more_tools && (
<>
<A href={data?.support?.help_article_more_tools} target="_blank">
{t('moreLink')}
</A>
.
</>
<A
href={data.support.help_article_more_tools}
target="_blank"
rel="noopener noreferrer"
externalIcon
color="note"
aria-label={t('linkAriaLabel')}
>
{t('moreLink')}
</A>
)}
</Text>
{isTranscriptEnabled && (
@@ -151,7 +151,12 @@ export function Chat({ ...props }: ChatProps) {
minHeight={0}
overflowY="scroll"
>
<ul className="lk-list lk-chat-messages" ref={ulRef}>
<ul
className="lk-list lk-chat-messages"
ref={ulRef}
role="log"
aria-relevant="additions"
>
{renderedMessages}
</ul>
</Div>
@@ -4,6 +4,7 @@ import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import { accessibilityStore } from '@/stores/accessibility'
import { CaptionsSettings } from '@/features/subtitle/component/CaptionsSettings'
export type AccessibilityTabProps = Pick<TabPanelProps, 'id'>
@@ -32,6 +33,7 @@ export const AccessibilityTab = ({ id }: AccessibilityTabProps) => {
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
</li>
<CaptionsSettings />
</ul>
</TabPanel>
)
@@ -0,0 +1,49 @@
import { Field, H } from '@/primitives'
import { css } from '@/styled-system/css'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import {
accessibilityStore,
type CaptionTextSize,
CAPTION_TEXT_SIZE_OPTIONS,
} from '@/stores/accessibility'
export const CaptionsSettings = () => {
const { t } = useTranslation('settings', {
keyPrefix: 'accessibility.captions',
})
const snap = useSnapshot(accessibilityStore)
const captionTextSizeItems = useMemo(
() =>
CAPTION_TEXT_SIZE_OPTIONS.map((size) => ({
value: size,
label: t(`textSize.options.${size}`),
})),
[t]
)
return (
<li>
<H
lvl={3}
className={css({
marginBottom: '0.5rem',
})}
>
{t('heading')}
</H>
<Field
type="select"
label={t('textSize.label')}
items={captionTextSizeItems}
selectedKey={snap.captionTextSize}
onSelectionChange={(key) => {
accessibilityStore.captionTextSize = key as CaptionTextSize
}}
wrapperProps={{ noMargin: true, fullWidth: true }}
/>
</li>
)
}
@@ -8,6 +8,25 @@ import { useRoomContext } from '@livekit/components-react'
import { getParticipantColor } from '@/features/rooms/utils/getParticipantColor'
import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
import { Participant, RoomEvent } from 'livekit-client'
import { useSnapshot } from 'valtio'
import {
accessibilityStore,
CAPTION_TEXT_SIZE_OPTIONS,
type CaptionTextSize,
} from '@/stores/accessibility'
const FONT_SIZE_CONFIG: Record<
CaptionTextSize,
{ fontSize: string; lineHeight: string }
> = {
small: { fontSize: '0.875rem', lineHeight: '1.2rem' },
medium: { fontSize: '1.5rem', lineHeight: '1.7rem' },
large: { fontSize: '2.25rem', lineHeight: '2.5rem' },
}
const CAPTION_FONT_SIZES = Object.fromEntries(
CAPTION_TEXT_SIZE_OPTIONS.map((size) => [size, FONT_SIZE_CONFIG[size]])
) as Record<CaptionTextSize, { fontSize: string; lineHeight: string }>
export interface TranscriptionSegment {
id: string
@@ -73,8 +92,10 @@ const useTranscriptionState = () => {
}
const Transcription = ({ row }: { row: TranscriptionRow }) => {
const { captionTextSize } = useSnapshot(accessibilityStore)
const participantColor = getParticipantColor(row.participant)
const participantName = getParticipantName(row.participant)
const { fontSize, lineHeight } = CAPTION_FONT_SIZES[captionTextSize]
const getDisplayText = (row: TranscriptionRow): string => {
return row.segments
@@ -116,10 +137,9 @@ const Transcription = ({ row }: { row: TranscriptionRow }) => {
</Text>
<p
className={css({
fontSize: '1.5rem',
lineHeight: '1.7rem',
fontWeight: '400',
})}
style={{ fontSize, lineHeight }}
>
{displayText}
</p>
@@ -198,7 +218,6 @@ export const Subtitles = () => {
)
}
}
return rows
}, [transcriptionSegments])
+1 -1
View File
@@ -10,7 +10,7 @@
"joinMeetingTipContent": "Sie können einem Meeting beitreten, indem Sie den vollständigen Link in die Adressleiste Ihres Browsers einfügen.",
"joinMeetingTipHeading": "Wussten Sie schon?",
"loginToCreateMeeting": "Melden Sie sich an, um ein Meeting zu erstellen",
"moreLinkLabel": "Mehr erfahren neues Tab",
"moreLinkLabel": "Mehr erfahren über {{appTitle}} neues Tab",
"moreLink": "Mehr erfahren",
"moreAbout": "über {{appTitle}}",
"createMenu": {
@@ -9,6 +9,7 @@
},
"muted": "{{name}} hat dein Mikrofon stummgeschaltet. Kein Teilnehmer kann dich hören.",
"openChat": "Chat öffnen",
"chatMessageReceived": "{{name}} sagt: {{message}}",
"lowerHand": {
"auto": "Es scheint, dass du angefangen hast zu sprechen, daher wird deine Hand gesenkt.",
"dismiss": "Hand oben lassen"
+14 -7
View File
@@ -322,11 +322,13 @@
"closeButton": "{{content}} ausblenden"
},
"chat": {
"disclaimer": "Die Nachrichten sind nur für Teilnehmer zum Zeitpunkt des Sendens sichtbar. Alle Nachrichten werden am Ende des Anrufs gelöscht."
"disclaimer": "Die Nachrichten sind nur für Teilnehmer zum Zeitpunkt des Sendens sichtbar. Alle Nachrichten werden am Ende des Anrufs gelöscht.",
"messagesLog": "Chat-Nachrichten"
},
"moreTools": {
"body": "Greifen Sie auf weitere Tools zu, um Ihre Meetings zu verbessern.",
"moreLink": "Mehr erfahren",
"linkAriaLabel": "Dokumentation zu den Tools öffnen - öffnet in neuem Fenster",
"moreLink": "Dokumentation öffnen",
"tools": {
"transcript": {
"title": "Transkribieren",
@@ -370,11 +372,13 @@
"starting": "Wird gestartet…",
"anotherModeStarted": "Eine Aufnahme läuft. <br/> Wechseln Sie das Menü und stoppen Sie sie."
},
"linkMore": "Mehr erfahren",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zur Transkription öffnen - öffnet in neuem Fenster",
"notAdminOrOwner": {
"heading": "Zugriff eingeschränkt",
"body": "Aus Sicherheitsgründen kann nur der Ersteller oder ein Administrator des Meetings eine Transkription (Beta) starten.",
"linkMore": "Mehr erfahren",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zum Transkriptionszugang öffnen - öffnet in neuem Fenster",
"dividerLabel": "ODER",
"login": {
"heading": "Anmeldung erforderlich",
@@ -389,7 +393,8 @@
"premium": {
"heading": "Premium-Funktion",
"body": "Diese Funktion ist öffentlichen Bediensteten vorbehalten. Wenn Ihre E-Mail-Adresse nicht autorisiert ist, kontaktieren Sie bitte den Support, um Zugriff zu erhalten.",
"linkMore": "Mehr erfahren",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zum Premium-Zugang öffnen - öffnet in neuem Fenster",
"dividerLabel": "ODER",
"login": {
"heading": "Anmeldung erforderlich",
@@ -406,7 +411,8 @@
"heading": "Diesen Anruf für später aufzeichnen",
"body": "Zeichnen Sie bis zu {{max_duration}} des Meetings auf.",
"bodyWithoutMaxDuration": "Zeichnen Sie Ihr Meeting unbegrenzt auf.",
"linkMore": "Mehr erfahren",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zur Aufzeichnung öffnen - öffnet in neuem Fenster",
"details": {
"receiver": "Die Aufzeichnung wird an den Organisator und die Mitorganisatoren gesendet.",
"destination": "Diese Aufzeichnung wird vorübergehend auf unseren Servern gespeichert",
@@ -424,7 +430,8 @@
"notAdminOrOwner": {
"heading": "Zugriff eingeschränkt",
"body": "Aus Sicherheitsgründen kann nur der Ersteller oder ein Administrator des Meetings eine Videoaufnahme (Beta) starten.",
"linkMore": "Mehr erfahren",
"linkMore": "Dokumentation öffnen",
"linkAriaLabel": "Dokumentation zum Aufzeichnungszugang öffnen - öffnet in neuem Fenster",
"dividerLabel": "ODER",
"login": {
"heading": "Anmeldung erforderlich",
+11
View File
@@ -116,6 +116,17 @@
"accessibility": {
"announceReactions": {
"label": "Reaktionen vorlesen"
},
"captions": {
"heading": "Untertitel",
"textSize": {
"label": "Untertitel-Schriftgröße",
"options": {
"small": "Klein",
"medium": "Mittel",
"large": "Groß"
}
}
}
},
"tabs": {
+1 -1
View File
@@ -10,7 +10,7 @@
"joinMeetingTipContent": "You can join a meeting by pasting its full link in the browser's address bar.",
"joinMeetingTipHeading": "Did you know?",
"loginToCreateMeeting": "Login to create a meeting",
"moreLinkLabel": "Learn more - new tab",
"moreLinkLabel": "Learn more about {{appTitle}} - new tab",
"moreLink": "Learn more",
"moreAbout": "about {{appTitle}}",
"createMenu": {
@@ -9,6 +9,7 @@
},
"muted": "{{name}} has muted your microphone. No participant can hear you.",
"openChat": "Open chat",
"chatMessageReceived": "{{name}} says: {{message}}",
"lowerHand": {
"auto": "It seems you have started speaking, so your hand will be lowered.",
"dismiss": "Keep hand raised"
+14 -7
View File
@@ -322,11 +322,13 @@
"closeButton": "Hide {{content}}"
},
"chat": {
"disclaimer": "The messages are visible to participants only at the time they are sent. All messages are deleted at the end of the call."
"disclaimer": "The messages are visible to participants only at the time they are sent. All messages are deleted at the end of the call.",
"messagesLog": "Chat messages"
},
"moreTools": {
"body": "Access more tools to enhance your meetings.",
"moreLink": "Learn more",
"linkAriaLabel": "Open documentation about tools - opens in new window",
"moreLink": "Open documentation",
"tools": {
"transcript": {
"title": "Transcribe",
@@ -370,11 +372,13 @@
"starting": "Starting…",
"anotherModeStarted": "Screen recording is running. Switch panels and stop it."
},
"linkMore": "Learn more",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about transcription - opens in new window",
"notAdminOrOwner": {
"heading": "Restricted Access",
"body": "For security reasons, only the meeting creator or an admin can start a transcription (beta).",
"linkMore": "Learn more",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about transcription access - opens in new window",
"dividerLabel": "OR",
"login": {
"heading": "Login Required",
@@ -389,7 +393,8 @@
"premium": {
"heading": "Premium feature",
"body": "This feature is reserved for public agents. If your email address is not authorized, please contact support to get access.",
"linkMore": "Learn more",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about premium access - opens in new window",
"dividerLabel": "OR",
"login": {
"heading": "You are not logged in!",
@@ -406,7 +411,8 @@
"heading": "Record this call for later",
"body": "Record up to {{max_duration}} of meeting.",
"bodyWithoutMaxDuration": "Record your meeting without limit.",
"linkMore": "Learn more",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about recording - opens in new window",
"details": {
"receiver": "The recording will be sent to the host and co-hosts.",
"destination": "This recording will be temporarily stored on our servers",
@@ -424,7 +430,8 @@
"notAdminOrOwner": {
"heading": "Restricted Access",
"body": "For security reasons, only the meeting creator or an admin can start a recording (beta).",
"linkMore": "Learn more",
"linkMore": "Open documentation",
"linkAriaLabel": "Open documentation about recording access - opens in new window",
"dividerLabel": "OR",
"login": {
"heading": "Login Required",
+11
View File
@@ -116,6 +116,17 @@
"accessibility": {
"announceReactions": {
"label": "Announce reactions aloud"
},
"captions": {
"heading": "Captions",
"textSize": {
"label": "Caption text size",
"options": {
"small": "Small",
"medium": "Medium",
"large": "Large"
}
}
}
},
"tabs": {
+1 -1
View File
@@ -10,7 +10,7 @@
"joinMeetingTipContent": "Vous pouvez rejoindre une réunion en copiant directement son lien complet dans la barre d'adresse du navigateur.",
"joinMeetingTipHeading": "Astuce",
"loginToCreateMeeting": "Connectez-vous pour créer une réunion",
"moreLinkLabel": "En savoir plus - nouvelle fenêtre",
"moreLinkLabel": "En savoir plus sur {{appTitle}} - nouvelle fenêtre",
"moreLink": "En savoir plus",
"moreAbout": "sur {{appTitle}}",
"createMenu": {
@@ -9,6 +9,7 @@
},
"muted": "{{name}} a coupé votre micro. Aucun participant ne peut l'entendre.",
"openChat": "Ouvrir le chat",
"chatMessageReceived": "{{name}} dit : {{message}}",
"lowerHand": {
"auto": "Il semblerait que vous ayez pris la parole, donc la main va être baissée.",
"dismiss": "Laisser la main levée"
+16 -9
View File
@@ -322,11 +322,13 @@
"closeButton": "Masquer {{content}}"
},
"chat": {
"disclaimer": "Les messages sont visibles par les participants uniquement au moment de\nleur envoi. Tous les messages sont supprimés à la fin de l'appel."
"disclaimer": "Les messages sont visibles par les participants uniquement au moment de\nleur envoi. Tous les messages sont supprimés à la fin de l'appel.",
"messagesLog": "Messages du chat"
},
"moreTools": {
"body": "Accèder à d'avantage d'outils pour améliorer vos réunions.",
"moreLink": "En savoir plus",
"body": "Accéder à davantage d'outils pour améliorer vos réunions.",
"linkAriaLabel": "Ouvrir la documentation sur les outils - ouvre dans une nouvelle fenêtre",
"moreLink": "Ouvrir la documentation",
"tools": {
"transcript": {
"title": "Transcrire",
@@ -370,11 +372,13 @@
"starting": "Démarrage…",
"anotherModeStarted": "Un enregistrement est en cours. <br/> Changez de menu et arrêtez le."
},
"linkMore": "En savoir plus",
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur la transcription - ouvre dans une nouvelle fenêtre",
"notAdminOrOwner": {
"heading": "Accès restreint",
"body": "Pour des raisons de sécurité, seul le créateur ou un administrateur de la réunion peut lancer une transcription (beta).",
"linkMore": "En savoir plus",
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur l'accès à la transcription - ouvre dans une nouvelle fenêtre",
"dividerLabel": "OU",
"login": {
"heading": "Connexion requise",
@@ -389,7 +393,7 @@
"premium": {
"heading": "Fonctionnalité premium",
"body": "Cette fonctionnalité est réservée aux agents publics. Si votre adresse email nest pas autorisée, contactez le support pour obtenir l'accès.",
"linkMore": "En savoir plus",
"linkMore": "Ouvrir la documentation",
"dividerLabel": "OU",
"login": {
"heading": "Vous n'êtes pas connecté !",
@@ -399,14 +403,16 @@
"heading": "Demander à l'organisateur",
"body": "L'hôte recevra une notification et pourra démarrer la transcription pour vous.",
"buttonLabel": "Demander"
}
},
"linkAriaLabel": "Ouvrir la documentation sur l'accès premium - ouvre dans une nouvelle fenêtre"
}
},
"screenRecording": {
"heading": "Enregistrez cet appel pour plus tard",
"body": "Enregistrez jusqu'à {{max_duration}} de réunion.",
"bodyWithoutMaxDuration": "Enregistrez votre réunion sans limite.",
"linkMore": "En savoir plus",
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur l'enregistrement - ouvre dans une nouvelle fenêtre",
"details": {
"receiver": "L'enregistrement sera envoyé à l'organisateur et aux coorganisateurs.",
"destination": "Cet enregistrement sera conservé temporairement sur nos serveurs",
@@ -424,7 +430,8 @@
"notAdminOrOwner": {
"heading": "Accès restreint",
"body": "Pour des raisons de sécurité, seul le créateur ou un administrateur de la réunion peut lancer un enregistrement (beta).",
"linkMore": "En savoir plus",
"linkMore": "Ouvrir la documentation",
"linkAriaLabel": "Ouvrir la documentation sur l'accès à l'enregistrement - ouvre dans une nouvelle fenêtre",
"dividerLabel": "OU",
"login": {
"heading": "Connexion requise",
+11
View File
@@ -116,6 +116,17 @@
"accessibility": {
"announceReactions": {
"label": "Vocaliser les réactions"
},
"captions": {
"heading": "Sous-titres",
"textSize": {
"label": "Taille du texte des sous-titres",
"options": {
"small": "Petit",
"medium": "Moyen",
"large": "Grand"
}
}
}
},
"tabs": {
+1 -1
View File
@@ -10,7 +10,7 @@
"joinMeetingTipContent": "U kunt deelnemen aan een vergadering door de volledige link in de adresbalk van de browser te plakken.",
"joinMeetingTipHeading": "Wist u dat?",
"loginToCreateMeeting": "Log in om een vergadering te maken",
"moreLinkLabel": "Meer informatie - nieuw tabblad",
"moreLinkLabel": "Meer informatie over {{appTitle}} - nieuw tabblad",
"moreLink": "Meer informatie",
"moreAbout": "over {{appTitle}}",
"createMenu": {
@@ -9,6 +9,7 @@
},
"muted": "{{name}} heeft uw microfoon gedempt. Deelnemers kunnen u niet horen.",
"openChat": "Open chat",
"chatMessageReceived": "{{name}} zegt: {{message}}",
"lowerHand": {
"auto": "Het lijkt erop dat u bent begonnen te spreken, dus we laten uw hand zakken.",
"dismiss": "Houdt uw hand opgestoken"
+14 -7
View File
@@ -322,11 +322,13 @@
"closeButton": "Verberg {{content}}"
},
"chat": {
"disclaimer": "De berichten zijn alleen voor de deelnemers zichtbaar op het moment dat ze worden verzonden. Alle berichten worden verwijderd aan het einde van het gesprek."
"disclaimer": "De berichten zijn alleen voor de deelnemers zichtbaar op het moment dat ze worden verzonden. Alle berichten worden verwijderd aan het einde van het gesprek.",
"messagesLog": "Chatberichten"
},
"moreTools": {
"body": "Je krijgt toegang tot meer tools om je vergaderingen te verbeteren.",
"moreLink": "Lees meer",
"linkAriaLabel": "Documentatie over tools openen - opent in nieuw venster",
"moreLink": "Documentatie openen",
"tools": {
"transcript": {
"title": "Transcriberen",
@@ -370,11 +372,13 @@
"starting": "Wordt gestart…",
"anotherModeStarted": "Er loopt een opname. <br/> Ga naar het menu en stop deze."
},
"linkMore": "Meer informatie",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over transcriptie openen - opent in nieuw venster",
"notAdminOrOwner": {
"heading": "Toegang beperkt",
"body": "Om veiligheidsredenen kan alleen de maker of een beheerder van de vergadering een transcriptie starten (beta).",
"linkMore": "Meer informatie",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over transcriptietoegang openen - opent in nieuw venster",
"dividerLabel": "OF",
"login": {
"heading": "Inloggen vereist",
@@ -389,7 +393,8 @@
"premium": {
"heading": "Premiumfunctie",
"body": "Deze functie is voorbehouden aan openbare medewerkers. Als uw e-mailadres niet is toegestaan, neem dan contact op met de support om toegang te krijgen.",
"linkMore": "Meer informatie",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over premiumtoegang openen - opent in nieuw venster",
"dividerLabel": "OF",
"login": {
"heading": "Inloggen vereist",
@@ -406,7 +411,8 @@
"heading": "Neem dit gesprek op voor later",
"body": "Neem tot {{max_duration}} van de vergadering op.",
"bodyWithoutMaxDuration": "Neem je vergadering onbeperkt op.",
"linkMore": "Meer informatie",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over opname openen - opent in nieuw venster",
"details": {
"receiver": "De opname wordt verzonden naar de organisator en co-organisatoren.",
"destination": "Deze opname wordt tijdelijk op onze servers bewaard",
@@ -424,7 +430,8 @@
"notAdminOrOwner": {
"heading": "Toegang beperkt",
"body": "Om veiligheidsredenen kan alleen de maker of een beheerder van de vergadering een opname starten (beta).",
"linkMore": "Meer informatie",
"linkMore": "Documentatie openen",
"linkAriaLabel": "Documentatie over opnametoegang openen - opent in nieuw venster",
"dividerLabel": "OF",
"login": {
"heading": "Inloggen vereist",
+17
View File
@@ -113,12 +113,29 @@
"label": "Taal"
},
"settingsButtonLabel": "Instellingen",
"accessibility": {
"announceReactions": {
"label": "Reacties hardop aankondigen"
},
"captions": {
"heading": "Ondertitels",
"textSize": {
"label": "Ondertiteltekstgrootte",
"options": {
"small": "Klein",
"medium": "Gemiddeld",
"large": "Groot"
}
}
}
},
"tabs": {
"account": "Profiel",
"audio": "Audio",
"video": "Video",
"general": "Algemeen",
"notifications": "Meldingen",
"accessibility": "Toegankelijkheid",
"transcription": "Transcriptie",
"shortcuts": "Sneltoetsen"
}
+15
View File
@@ -2,12 +2,22 @@ import { proxy, subscribe } from 'valtio'
import { STORAGE_KEYS } from '@/utils/storageKeys'
import { deserializeToProxyMap } from '@/utils/valtio'
export type CaptionTextSize = 'small' | 'medium' | 'large'
export const CAPTION_TEXT_SIZE_OPTIONS: CaptionTextSize[] = [
'small',
'medium',
'large',
]
type AccessibilityState = {
announceReactions: boolean
captionTextSize: CaptionTextSize
}
const DEFAULT_STATE: AccessibilityState = {
announceReactions: false,
captionTextSize: 'medium',
}
function getAccessibilityState(): AccessibilityState {
@@ -15,6 +25,10 @@ function getAccessibilityState(): AccessibilityState {
const stored = localStorage.getItem(STORAGE_KEYS.ACCESSIBILITY)
if (stored) {
const parsed = JSON.parse(stored)
const validCaptionSizes = CAPTION_TEXT_SIZE_OPTIONS
const captionTextSize = validCaptionSizes.includes(parsed.captionTextSize)
? parsed.captionTextSize
: DEFAULT_STATE.captionTextSize
return {
...DEFAULT_STATE,
...parsed,
@@ -22,6 +36,7 @@ function getAccessibilityState(): AccessibilityState {
typeof parsed.announceReactions === 'boolean'
? parsed.announceReactions
: DEFAULT_STATE.announceReactions,
captionTextSize,
}
}