From 2c8f39273cef88102c2663229c3e18dde844f971 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Sat, 3 Oct 2026 02:08:52 +0200 Subject: [PATCH] release: v2.6.2-beta English and French interface --- docs/release-notes.md | 1 + docs/releases/v2.6.2-beta.md | 29 +++++++++++++++++++++++++++++ web/src/i18n/spa.spec.ts | 2 ++ web/src/views/MediaView.vue | 10 +++++++--- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 docs/releases/v2.6.2-beta.md diff --git a/docs/release-notes.md b/docs/release-notes.md index 59ffe41..34886ae 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -28,3 +28,4 @@ Each body lives in `docs/releases/.md`. Publish with `tea releases create - | `v2.4.0-beta` | v2.4.0-beta - Owner role, invite management & Settings page | | `v2.5.0-beta` | v2.5.0-beta - Media library & image picker | | `v2.6.0-beta` | v2.6.0-beta - Signed updates and browser restart | +| `v2.6.2-beta` | v2.6.2-beta - English and French interface | diff --git a/docs/releases/v2.6.2-beta.md b/docs/releases/v2.6.2-beta.md new file mode 100644 index 0000000..a6decdd --- /dev/null +++ b/docs/releases/v2.6.2-beta.md @@ -0,0 +1,29 @@ +This release adds English and French throughout the SPA, with an instant language switch and a preference remembered on this browser. + +## New + +**English and French interface** + +- Choose English or Français from the Language control in the desktop or mobile profile submenu. +- Switch interface language immediately while keeping your current page and unsaved work. +- Keep your selected language after navigation, reload and logout, including on authentication screens. +- See translated forms, validation, notifications, dialogs and accessibility labels, with localized dates, numbers and counts. + +## Improvements and fixes + +- Keep partial bulk-delete failure notifications available after retrying, including when switching languages. + +## Under the hood + +- Add translation catalog parity, fallback, persistence and SPA interaction tests. + +## Upgrading + +- Install the signed update through Settings on a prepared Linux/systemd installation, or replace the binary and restart the service. +- No database migration or new environment variable is required. +- English remains the default until a language is selected; the preference is stored locally in the browser. +- User content, API data and transactional emails retain their existing language. + +## Known limitations + +- Visual parity checks for this feature were skipped under the accepted slice waiver. diff --git a/web/src/i18n/spa.spec.ts b/web/src/i18n/spa.spec.ts index cc9888a..78e1269 100644 --- a/web/src/i18n/spa.spec.ts +++ b/web/src/i18n/spa.spec.ts @@ -199,6 +199,8 @@ describe('Switch language without losing work', () => { .find((button) => button.textContent?.trim() === 'Retry')! .click() await flushPromises() + await new Promise((resolve) => setTimeout(resolve, 250)) + await flushPromises() await captureBothWays( '2 images were deleted. kitchen-before.webp is still in your library. Try again.', '2 images ont été supprimées. kitchen-before.webp est toujours dans votre bibliothèque. Réessayez.', diff --git a/web/src/views/MediaView.vue b/web/src/views/MediaView.vue index 411c255..20e968a 100644 --- a/web/src/views/MediaView.vue +++ b/web/src/views/MediaView.vue @@ -100,6 +100,8 @@ let opener: HTMLElement | null = null let skipFocusRestore = false let pendingSelectId: number | null = null let bulkDeletedTotal = 0 +let bulkFailureRevision = 0 +let bulkFailureToastId = BULK_FAILED_TOAST_ID let failureToastShown = false const activeItem = computed( @@ -645,17 +647,17 @@ function bulkFailedToast(failed: MediaItem[]) { ], } if (failureToastShown) { - toast.update(BULK_FAILED_TOAST_ID, () => content) + toast.update(bulkFailureToastId, () => content) return } failureToastShown = true - toast.add(() => ({ id: BULK_FAILED_TOAST_ID, ...content })) + toast.add(() => ({ id: bulkFailureToastId, ...content })) } function dismissBulkFailedToast() { if (failureToastShown) { failureToastShown = false - toast.remove(BULK_FAILED_TOAST_ID) + toast.remove(bulkFailureToastId) } } @@ -708,6 +710,8 @@ async function bulkDeleteAction() { } async function retryBulkDelete(failed: MediaItem[]) { + dismissBulkFailedToast() + bulkFailureToastId = `${BULK_FAILED_TOAST_ID}-${++bulkFailureRevision}` const firstIndex = firstIndexOf(failed) const result = await media.deleteMany(failed) if (result.handled) {