From a98449d573b037479f2b604e64f0ce3739cd3903 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 1 Oct 2026 13:11:09 +0200 Subject: [PATCH] feat(planner): ask before a stray click throws away an edited place or booking (#2253) --- .../src/components/Planner/PlaceFormModal.tsx | 2 + .../components/Planner/ReservationModal.tsx | 2 + .../src/components/Planner/TransportModal.tsx | 2 + .../components/shared/DialogShell.test.tsx | 53 +++++++++++++++ client/src/components/shared/DialogShell.tsx | 66 +++++++++++++++++-- shared/src/i18n/ar/common.ts | 3 + shared/src/i18n/az/common.ts | 3 + shared/src/i18n/br/common.ts | 3 + shared/src/i18n/ca/common.ts | 3 + shared/src/i18n/cs/common.ts | 3 + shared/src/i18n/de/common.ts | 3 + shared/src/i18n/en/common.ts | 3 + shared/src/i18n/es/common.ts | 3 + shared/src/i18n/et/common.ts | 3 + shared/src/i18n/fr/common.ts | 3 + shared/src/i18n/gr/common.ts | 3 + shared/src/i18n/hu/common.ts | 3 + shared/src/i18n/id/common.ts | 3 + shared/src/i18n/it/common.ts | 3 + shared/src/i18n/ja/common.ts | 3 + shared/src/i18n/ko/common.ts | 3 + shared/src/i18n/nl/common.ts | 3 + shared/src/i18n/pl/common.ts | 3 + shared/src/i18n/ru/common.ts | 3 + shared/src/i18n/sk/common.ts | 3 + shared/src/i18n/sv/common.ts | 3 + shared/src/i18n/th/common.ts | 3 + shared/src/i18n/tr/common.ts | 3 + shared/src/i18n/uk/common.ts | 3 + shared/src/i18n/vi/common.ts | 3 + shared/src/i18n/zh-TW/common.ts | 3 + shared/src/i18n/zh/common.ts | 3 + 32 files changed, 199 insertions(+), 7 deletions(-) diff --git a/client/src/components/Planner/PlaceFormModal.tsx b/client/src/components/Planner/PlaceFormModal.tsx index 2b137b916..6db230280 100644 --- a/client/src/components/Planner/PlaceFormModal.tsx +++ b/client/src/components/Planner/PlaceFormModal.tsx @@ -995,6 +995,8 @@ export default function PlaceFormModal(props: PlaceFormModalProps) { labelledBy={titleId} width={width} align="top" + // A stray click beside the editor asks before the typing is lost (#2253). + discardGuard={{ form, files: S.pendingFiles.length }} onPaste={S.handlePaste} header={header} footer={footer} diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx index 67c4da01a..cf11e4c3b 100644 --- a/client/src/components/Planner/ReservationModal.tsx +++ b/client/src/components/Planner/ReservationModal.tsx @@ -439,6 +439,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p labelledBy={titleId} width="editor" align="top" + // A stray click beside the editor asks before the typing is lost (#2253). + discardGuard={{ form, files: pendingFiles.length, travelers: [...travelerIds] }} onSubmit={handleSubmit} header={header} footer={footer} diff --git a/client/src/components/Planner/TransportModal.tsx b/client/src/components/Planner/TransportModal.tsx index b0be23237..98bbebeaa 100644 --- a/client/src/components/Planner/TransportModal.tsx +++ b/client/src/components/Planner/TransportModal.tsx @@ -1055,6 +1055,8 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel width="editor" align="top" blocked={showDeleteConfirm} + // A stray click beside the editor asks before the typing is lost (#2253). + discardGuard={{ form, waypoints, trainWaypoints, carStops, files: pendingFiles.length, travelers: [...travelerIds] }} onSubmit={automated ? undefined : handleSubmit} header={header} footer={footer} diff --git a/client/src/components/shared/DialogShell.test.tsx b/client/src/components/shared/DialogShell.test.tsx index 6a4b3df58..dafd14b32 100644 --- a/client/src/components/shared/DialogShell.test.tsx +++ b/client/src/components/shared/DialogShell.test.tsx @@ -1,5 +1,6 @@ // FE-COMP-DIALOGSHELL-001 to FE-COMP-DIALOGSHELL-013 import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { useState } from 'react'; import { render, screen, fireEvent } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { resetBodyScrollLock } from '../../utils/bodyScrollLock'; @@ -59,6 +60,58 @@ describe('DialogShell', () => { }); }); +describe('DialogShell unsaved-changes question (#2253)', () => { + function Editor() { + const [name, setName] = useState(''); + return ( + }> + setName(e.target.value)} /> + + ); + } + const backdrop = () => document.querySelector('.trek-modal-backdrop') as HTMLElement; + const clickBackdrop = () => { fireEvent.mouseDown(backdrop()); fireEvent.click(backdrop()); }; + + it('FE-COMP-DIALOGSHELL-2253-1: an untouched editor closes on the backdrop straight away', () => { + render(); + clickBackdrop(); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('FE-COMP-DIALOGSHELL-2253-2: after typing, the backdrop asks; keep editing stays, discard closes', async () => { + const user = userEvent.setup(); + render(); + await user.type(screen.getByLabelText('Name'), 'Cafe'); + clickBackdrop(); + expect(onClose).not.toHaveBeenCalled(); + expect(screen.getByRole('alertdialog')).toHaveTextContent('Discard your changes?'); + await user.click(screen.getByRole('button', { name: 'Keep editing' })); + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(); + expect(screen.getByLabelText('Name')).toHaveValue('Cafe'); + + await user.keyboard('{Escape}'); + expect(screen.getByRole('alertdialog')).toBeInTheDocument(); + // Escape on the question only takes the question away. + await user.keyboard('{Escape}'); + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(); + expect(onClose).not.toHaveBeenCalled(); + + clickBackdrop(); + await user.click(screen.getByRole('button', { name: 'Discard' })); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('FE-COMP-DIALOGSHELL-2253-3: typing back to where it started closes without asking', async () => { + const user = userEvent.setup(); + render(); + await user.type(screen.getByLabelText('Name'), 'a'); + await user.clear(screen.getByLabelText('Name')); + clickBackdrop(); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); + describe('DialogHeader', () => { it('FE-COMP-DIALOGSHELL-004: a plain title is the heading the dialog is named by', () => { render(} tint={NEUTRAL_TINT} labelId="t1" onClose={onClose} title="Export" />); diff --git a/client/src/components/shared/DialogShell.tsx b/client/src/components/shared/DialogShell.tsx index 4f1ec48bc..b4bdf5c32 100644 --- a/client/src/components/shared/DialogShell.tsx +++ b/client/src/components/shared/DialogShell.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, type ButtonHTMLAttributes, type ClipboardEvent, type CSSProperties, type FormEvent, type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from 'react' +import { useCallback, useEffect, useRef, useState, type ButtonHTMLAttributes, type ClipboardEvent, type CSSProperties, type FormEvent, type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from 'react' import { createPortal } from 'react-dom' import { Trash2, X } from 'lucide-react' import { useTranslation } from '../../i18n' @@ -39,6 +39,20 @@ export interface DialogShellProps { bodyClassName?: string /** A paste anywhere in the panel, head band included (a note takes pasted images from its title too). */ onPaste?: (e: ClipboardEvent) => void + /** + * The editor's state, for the unsaved-changes question (#2253). Snapshotted at + * the first key or pointer press inside the panel, so whatever the dialog + * loaded on open counts as the starting point. A click on the backdrop or + * Escape then asks before throwing away a state that differs from it. Must be + * JSON-serialisable; pass counts for files and arrays for sets. + */ + discardGuard?: unknown +} + +const DISCARD_BUTTON = 'inline-flex items-center gap-1.5 rounded-[10px] bg-danger px-4 py-2 font-medium text-white hover:opacity-90' // theme-lint-disable: white on the danger fill, as ConfirmDialog draws it + +const snapshot = (value: unknown): string | null => { + try { return JSON.stringify(value) ?? null } catch { return null } } /** @@ -49,18 +63,40 @@ export function DialogShell({ open = true, ...frame }: DialogShellProps) { return open ? : null } -function DialogFrame({ onClose, labelledBy, width = 'detail', align = 'center', blocked = false, header, footer, onSubmit, children, bodyClassName, onPaste }: Omit) { +function DialogFrame({ onClose, labelledBy, width = 'detail', align = 'center', blocked = false, header, footer, onSubmit, children, bodyClassName, onPaste, discardGuard }: Omit) { + const { t } = useTranslation() const panelRef = useRef(null) const pressedOn = useRef(null) // Read while rendering, before a field inside can take the focus with autoFocus. const [focusedBefore] = useState(() => document.activeElement) + // The unsaved-changes question (#2253): the state at the first touch, and + // whether the question is on screen. + const baseline = useRef(null) + const guarded = discardGuard !== undefined + const guardRef = useRef(discardGuard) + useEffect(() => { guardRef.current = discardGuard }) + const [asking, setAsking] = useState(false) + const markStart = () => { if (guarded && baseline.current === null) baseline.current = snapshot(discardGuard) } + // The backdrop and Escape: the two ways out a hand can take by accident. + const requestClose = useCallback(() => { + if (guardRef.current !== undefined && baseline.current !== null && snapshot(guardRef.current) !== baseline.current) setAsking(true) + else onClose() + }, [onClose]) + const held = blocked || asking useEffect(() => { - if (blocked) return - const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && !e.defaultPrevented) onClose() } + if (held) return + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && !e.defaultPrevented) requestClose() } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) - }, [blocked, onClose]) + }, [held, requestClose]) + + useEffect(() => { + if (!asking) return + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); setAsking(false) } } + document.addEventListener('keydown', onKey) + return () => document.removeEventListener('keydown', onKey) + }, [asking]) // The page stays put behind the dialog, the focus moves into it (the first text // field on a desktop, #1302), and whatever opened it gets the focus back. @@ -90,7 +126,7 @@ function DialogFrame({ onClose, labelledBy, width = 'detail', align = 'center', // dragged out of a field must not throw the dialog away. onMouseDown={e => { pressedOn.current = e.target }} onClick={e => { - if (!blocked && e.target === e.currentTarget && pressedOn.current === e.currentTarget) onClose() + if (!held && e.target === e.currentTarget && pressedOn.current === e.currentTarget) requestClose() pressedOn.current = null }} > @@ -102,12 +138,28 @@ function DialogFrame({ onClose, labelledBy, width = 'detail', align = 'center', aria-labelledby={labelledBy} tabIndex={-1} onPaste={onPaste} + onKeyDownCapture={markStart} + onPointerDownCapture={markStart} onKeyDown={e => { if (panelRef.current) trapTab(e, panelRef.current) }} - className={`trek-modal-enter flex max-h-full w-full ${PANEL_WIDTH[width]} flex-col overflow-hidden rounded-[22px] bg-surface-card shadow-2xl outline-none`} + className={`trek-modal-enter relative flex max-h-full w-full ${PANEL_WIDTH[width]} flex-col overflow-hidden rounded-[22px] bg-surface-card shadow-2xl outline-none`} > {header} {body} {footer} + {asking && ( +
+
+
{t('common.unsavedTitle')}
+

{t('common.unsavedMessage')}

+
+ setAsking(false)}>{t('common.keepEditing')} + +
+
+
+ )} , document.body, diff --git a/shared/src/i18n/ar/common.ts b/shared/src/i18n/ar/common.ts index e9c8460fd..a0437246b 100644 --- a/shared/src/i18n/ar/common.ts +++ b/shared/src/i18n/ar/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'إعادة تسمية', 'common.discardChanges': 'تجاهل التغييرات', 'common.discard': 'تجاهل', + 'common.unsavedTitle': 'تجاهل تغييراتك؟', + 'common.unsavedMessage': 'ما أدخلته هنا لم يُحفظ بعد.', + 'common.keepEditing': 'متابعة التعديل', 'common.name': 'الاسم', 'common.email': 'البريد الإلكتروني', 'common.password': 'كلمة المرور', diff --git a/shared/src/i18n/az/common.ts b/shared/src/i18n/az/common.ts index 6d1757160..8c4b1f9ec 100644 --- a/shared/src/i18n/az/common.ts +++ b/shared/src/i18n/az/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Adını dəyiş', 'common.discardChanges': 'Dəyişiklikləri ləğv et', 'common.discard': 'İmtina et', + 'common.unsavedTitle': 'Dəyişikliklər ləğv edilsin?', + 'common.unsavedMessage': 'Burada daxil etdikləriniz hələ yadda saxlanmayıb.', + 'common.keepEditing': 'Redaktəyə davam et', 'common.name': 'Ad', 'common.email': 'E-poçt', 'common.password': 'Parol', diff --git a/shared/src/i18n/br/common.ts b/shared/src/i18n/br/common.ts index 06a63a212..ff2dc52e1 100644 --- a/shared/src/i18n/br/common.ts +++ b/shared/src/i18n/br/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Renomear', 'common.discardChanges': 'Descartar alterações', 'common.discard': 'Descartar', + 'common.unsavedTitle': 'Descartar suas alterações?', + 'common.unsavedMessage': 'O que você inseriu aqui ainda não foi salvo.', + 'common.keepEditing': 'Continuar editando', 'common.name': 'Nome', 'common.email': 'E-mail', 'common.password': 'Senha', diff --git a/shared/src/i18n/ca/common.ts b/shared/src/i18n/ca/common.ts index a0d8e23d1..94f35a12a 100644 --- a/shared/src/i18n/ca/common.ts +++ b/shared/src/i18n/ca/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Canviar nom', 'common.discardChanges': 'Descartar canvis', 'common.discard': 'Descartar', + 'common.unsavedTitle': 'Vols descartar els canvis?', + 'common.unsavedMessage': "El que has introduït aquí encara no s'ha desat.", + 'common.keepEditing': 'Continua editant', 'common.name': 'Nom', 'common.email': 'Correu', 'common.password': 'Contrasenya', diff --git a/shared/src/i18n/cs/common.ts b/shared/src/i18n/cs/common.ts index 2dc1aca1d..9f692890a 100644 --- a/shared/src/i18n/cs/common.ts +++ b/shared/src/i18n/cs/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Přejmenovat', 'common.discardChanges': 'Zahodit změny', 'common.discard': 'Zahodit', + 'common.unsavedTitle': 'Zahodit změny?', + 'common.unsavedMessage': 'To, co jste zde zadali, ještě není uložené.', + 'common.keepEditing': 'Pokračovat v úpravách', 'common.name': 'Jméno', 'common.email': 'E-mail', 'common.password': 'Heslo', diff --git a/shared/src/i18n/de/common.ts b/shared/src/i18n/de/common.ts index 9fa9aa639..f9d35366d 100644 --- a/shared/src/i18n/de/common.ts +++ b/shared/src/i18n/de/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Umbenennen', 'common.discardChanges': 'Änderungen verwerfen', 'common.discard': 'Verwerfen', + 'common.unsavedTitle': 'Änderungen verwerfen?', + 'common.unsavedMessage': 'Was du hier eingegeben hast, ist noch nicht gespeichert.', + 'common.keepEditing': 'Weiter bearbeiten', 'common.name': 'Name', 'common.email': 'E-Mail', 'common.password': 'Passwort', diff --git a/shared/src/i18n/en/common.ts b/shared/src/i18n/en/common.ts index ab6f2e272..23d1c6aad 100644 --- a/shared/src/i18n/en/common.ts +++ b/shared/src/i18n/en/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Rename', 'common.discardChanges': 'Discard Changes', 'common.discard': 'Discard', + 'common.unsavedTitle': 'Discard your changes?', + 'common.unsavedMessage': 'What you entered here has not been saved yet.', + 'common.keepEditing': 'Keep editing', 'common.name': 'Name', 'common.email': 'Email', 'common.password': 'Password', diff --git a/shared/src/i18n/es/common.ts b/shared/src/i18n/es/common.ts index 0e78ee833..2ba40ca78 100644 --- a/shared/src/i18n/es/common.ts +++ b/shared/src/i18n/es/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Renombrar', 'common.discardChanges': 'Descartar cambios', 'common.discard': 'Descartar', + 'common.unsavedTitle': '¿Descartar los cambios?', + 'common.unsavedMessage': 'Lo que has introducido aquí aún no se ha guardado.', + 'common.keepEditing': 'Seguir editando', 'common.name': 'Nombre', 'common.email': 'Correo', 'common.password': 'Contraseña', diff --git a/shared/src/i18n/et/common.ts b/shared/src/i18n/et/common.ts index 08259dea6..3d1f34651 100644 --- a/shared/src/i18n/et/common.ts +++ b/shared/src/i18n/et/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Nimeta ümber', 'common.discardChanges': 'Loobu muudatustest', 'common.discard': 'Loobu', + 'common.unsavedTitle': 'Kas loobuda muudatustest?', + 'common.unsavedMessage': 'Siia sisestatu pole veel salvestatud.', + 'common.keepEditing': 'Jätka muutmist', 'common.name': 'Nimi', 'common.email': 'E-post', 'common.password': 'Parool', diff --git a/shared/src/i18n/fr/common.ts b/shared/src/i18n/fr/common.ts index 240a8ef48..0e97abc76 100644 --- a/shared/src/i18n/fr/common.ts +++ b/shared/src/i18n/fr/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Renommer', 'common.discardChanges': 'Ignorer les modifications', 'common.discard': 'Ignorer', + 'common.unsavedTitle': 'Abandonner vos modifications ?', + 'common.unsavedMessage': "Ce que vous avez saisi ici n'est pas encore enregistré.", + 'common.keepEditing': 'Continuer la modification', 'common.name': 'Nom', 'common.email': 'E-mail', 'common.password': 'Mot de passe', diff --git a/shared/src/i18n/gr/common.ts b/shared/src/i18n/gr/common.ts index 1bed8d578..cf4782db5 100644 --- a/shared/src/i18n/gr/common.ts +++ b/shared/src/i18n/gr/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Μετονομασία', 'common.discardChanges': 'Απόρριψη αλλαγών', 'common.discard': 'Απόρριψη', + 'common.unsavedTitle': 'Απόρριψη των αλλαγών;', + 'common.unsavedMessage': 'Όσα καταχωρίσατε εδώ δεν έχουν αποθηκευτεί ακόμη.', + 'common.keepEditing': 'Συνέχεια επεξεργασίας', 'common.name': 'Όνομα', 'common.email': 'Email', 'common.password': 'Κωδικός', diff --git a/shared/src/i18n/hu/common.ts b/shared/src/i18n/hu/common.ts index 5deb64cf9..43a6e09d8 100644 --- a/shared/src/i18n/hu/common.ts +++ b/shared/src/i18n/hu/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Átnevezés', 'common.discardChanges': 'Változtatások elvetése', 'common.discard': 'Elveti', + 'common.unsavedTitle': 'Elveted a módosításokat?', + 'common.unsavedMessage': 'Amit itt megadtál, még nincs elmentve.', + 'common.keepEditing': 'Szerkesztés folytatása', 'common.name': 'Név', 'common.email': 'E-mail', 'common.password': 'Jelszó', diff --git a/shared/src/i18n/id/common.ts b/shared/src/i18n/id/common.ts index c6af809e7..eb0642d40 100644 --- a/shared/src/i18n/id/common.ts +++ b/shared/src/i18n/id/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Ganti nama', 'common.discardChanges': 'Buang perubahan', 'common.discard': 'Buang', + 'common.unsavedTitle': 'Buang perubahan?', + 'common.unsavedMessage': 'Yang kamu masukkan di sini belum disimpan.', + 'common.keepEditing': 'Lanjut mengedit', 'common.name': 'Nama', 'common.email': 'Email', 'common.password': 'Kata sandi', diff --git a/shared/src/i18n/it/common.ts b/shared/src/i18n/it/common.ts index a8a718139..db0588e89 100644 --- a/shared/src/i18n/it/common.ts +++ b/shared/src/i18n/it/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Rinomina', 'common.discardChanges': 'Scarta modifiche', 'common.discard': 'Scarta', + 'common.unsavedTitle': 'Scartare le modifiche?', + 'common.unsavedMessage': 'Quello che hai inserito qui non è ancora stato salvato.', + 'common.keepEditing': 'Continua a modificare', 'common.name': 'Nome', 'common.email': 'Email', 'common.password': 'Password', diff --git a/shared/src/i18n/ja/common.ts b/shared/src/i18n/ja/common.ts index 670604f2b..dbd28f10c 100644 --- a/shared/src/i18n/ja/common.ts +++ b/shared/src/i18n/ja/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': '名前を変更', 'common.discardChanges': '変更をキャンセル', 'common.discard': 'キャンセル', + 'common.unsavedTitle': '変更を破棄しますか?', + 'common.unsavedMessage': 'ここで入力した内容はまだ保存されていません。', + 'common.keepEditing': '編集を続ける', 'common.name': '名前', 'common.email': 'メールアドレス', 'common.password': 'パスワード', diff --git a/shared/src/i18n/ko/common.ts b/shared/src/i18n/ko/common.ts index 06b157008..b99a6b02f 100644 --- a/shared/src/i18n/ko/common.ts +++ b/shared/src/i18n/ko/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': '이름 변경', 'common.discardChanges': '변경 사항 취소', 'common.discard': '취소', + 'common.unsavedTitle': '변경 사항을 버릴까요?', + 'common.unsavedMessage': '여기에 입력한 내용은 아직 저장되지 않았습니다.', + 'common.keepEditing': '계속 편집', 'common.name': '이름', 'common.email': '이메일', 'common.password': '비밀번호', diff --git a/shared/src/i18n/nl/common.ts b/shared/src/i18n/nl/common.ts index 484ab4c85..b09128a8a 100644 --- a/shared/src/i18n/nl/common.ts +++ b/shared/src/i18n/nl/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Hernoemen', 'common.discardChanges': 'Wijzigingen verwerpen', 'common.discard': 'Verwerpen', + 'common.unsavedTitle': 'Wijzigingen weggooien?', + 'common.unsavedMessage': 'Wat je hier hebt ingevuld, is nog niet opgeslagen.', + 'common.keepEditing': 'Verder bewerken', 'common.name': 'Naam', 'common.email': 'E-mail', 'common.password': 'Wachtwoord', diff --git a/shared/src/i18n/pl/common.ts b/shared/src/i18n/pl/common.ts index de4321452..f50dc479a 100644 --- a/shared/src/i18n/pl/common.ts +++ b/shared/src/i18n/pl/common.ts @@ -31,6 +31,9 @@ const common: TranslationStrings = { 'common.rename': 'Zmień nazwę', 'common.discardChanges': 'Odrzuć zmiany', 'common.discard': 'Odrzuć', + 'common.unsavedTitle': 'Odrzucić zmiany?', + 'common.unsavedMessage': 'To, co tu wpisano, nie zostało jeszcze zapisane.', + 'common.keepEditing': 'Kontynuuj edycję', 'common.name': 'Nazwa', 'common.email': 'E-mail', 'common.password': 'Hasło', diff --git a/shared/src/i18n/ru/common.ts b/shared/src/i18n/ru/common.ts index cd3c981cb..ff8beaf7b 100644 --- a/shared/src/i18n/ru/common.ts +++ b/shared/src/i18n/ru/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Переименовать', 'common.discardChanges': 'Отменить изменения', 'common.discard': 'Отменить', + 'common.unsavedTitle': 'Отменить изменения?', + 'common.unsavedMessage': 'Введённое здесь ещё не сохранено.', + 'common.keepEditing': 'Продолжить', 'common.name': 'Имя', 'common.email': 'Эл. почта', 'common.password': 'Пароль', diff --git a/shared/src/i18n/sk/common.ts b/shared/src/i18n/sk/common.ts index 0bcf0d62c..2d93b96a1 100644 --- a/shared/src/i18n/sk/common.ts +++ b/shared/src/i18n/sk/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Premenovať', 'common.discardChanges': 'Zahodiť zmeny', 'common.discard': 'Zahodiť', + 'common.unsavedTitle': 'Zahodiť zmeny?', + 'common.unsavedMessage': 'To, čo ste tu zadali, ešte nie je uložené.', + 'common.keepEditing': 'Pokračovať v úpravách', 'common.name': 'Meno', 'common.email': 'E-mail', 'common.password': 'Heslo', diff --git a/shared/src/i18n/sv/common.ts b/shared/src/i18n/sv/common.ts index 2da689bbb..57efdcff2 100644 --- a/shared/src/i18n/sv/common.ts +++ b/shared/src/i18n/sv/common.ts @@ -47,6 +47,9 @@ const common: TranslationStrings = { 'common.rename': 'Döp om', 'common.discardChanges': 'Ångra ändringar', 'common.discard': 'Ångra', + 'common.unsavedTitle': 'Slänga ändringarna?', + 'common.unsavedMessage': 'Det du har fyllt i här är inte sparat än.', + 'common.keepEditing': 'Fortsätt redigera', 'common.name': 'Namn', 'common.email': 'E-post', 'common.password': 'Lösenord', diff --git a/shared/src/i18n/th/common.ts b/shared/src/i18n/th/common.ts index 31a148e69..2ecf876ba 100644 --- a/shared/src/i18n/th/common.ts +++ b/shared/src/i18n/th/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': "เปลี่ยนชื่อ", 'common.discardChanges': "ยกเลิกการเปลี่ยนแปลง", 'common.discard': "ทิ้ง", + 'common.unsavedTitle': 'ทิ้งการเปลี่ยนแปลงไหม', + 'common.unsavedMessage': 'สิ่งที่คุณกรอกไว้ที่นี่ยังไม่ได้บันทึก', + 'common.keepEditing': 'แก้ไขต่อ', 'common.name': "ชื่อ", 'common.email': "อีเมล", 'common.password': "รหัสผ่าน", diff --git a/shared/src/i18n/tr/common.ts b/shared/src/i18n/tr/common.ts index f6f1c5d7b..aaee60a54 100644 --- a/shared/src/i18n/tr/common.ts +++ b/shared/src/i18n/tr/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Yeniden adlandır', 'common.discardChanges': 'Değişiklikleri iptal et', 'common.discard': 'Vazgeç', + 'common.unsavedTitle': 'Değişiklikler atılsın mı?', + 'common.unsavedMessage': 'Buraya girdikleriniz henüz kaydedilmedi.', + 'common.keepEditing': 'Düzenlemeye devam et', 'common.name': 'Ad', 'common.email': 'E-posta', 'common.password': 'Şifre', diff --git a/shared/src/i18n/uk/common.ts b/shared/src/i18n/uk/common.ts index 4db8a6081..3474b0c92 100644 --- a/shared/src/i18n/uk/common.ts +++ b/shared/src/i18n/uk/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': 'Перейменувати', 'common.discardChanges': 'Скасувати зміни', 'common.discard': 'Скасувати', + 'common.unsavedTitle': 'Скасувати зміни?', + 'common.unsavedMessage': 'Введене тут ще не збережено.', + 'common.keepEditing': 'Продовжити редагування', 'common.name': "Ім'я", 'common.email': 'Ел. пошта', 'common.password': 'Пароль', diff --git a/shared/src/i18n/vi/common.ts b/shared/src/i18n/vi/common.ts index 6faa90102..78523743d 100644 --- a/shared/src/i18n/vi/common.ts +++ b/shared/src/i18n/vi/common.ts @@ -47,6 +47,9 @@ const common: TranslationStrings = { 'common.rename': 'Đổi tên', 'common.discardChanges': 'Hủy thay đổi', 'common.discard': 'Bỏ', + 'common.unsavedTitle': 'Bỏ các thay đổi?', + 'common.unsavedMessage': 'Những gì bạn nhập ở đây chưa được lưu.', + 'common.keepEditing': 'Tiếp tục chỉnh sửa', 'common.name': 'Tên', 'common.email': 'Email', 'common.password': 'Mật khẩu', diff --git a/shared/src/i18n/zh-TW/common.ts b/shared/src/i18n/zh-TW/common.ts index 396a48cc1..0d7cda513 100644 --- a/shared/src/i18n/zh-TW/common.ts +++ b/shared/src/i18n/zh-TW/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': '重新命名', 'common.discardChanges': '捨棄變更', 'common.discard': '捨棄', + 'common.unsavedTitle': '放棄變更?', + 'common.unsavedMessage': '你在這裡輸入的內容尚未儲存。', + 'common.keepEditing': '繼續編輯', 'common.name': '名稱', 'common.email': '郵箱', 'common.password': '密碼', diff --git a/shared/src/i18n/zh/common.ts b/shared/src/i18n/zh/common.ts index 4c5fb1f96..3aeaf542e 100644 --- a/shared/src/i18n/zh/common.ts +++ b/shared/src/i18n/zh/common.ts @@ -35,6 +35,9 @@ const common: TranslationStrings = { 'common.rename': '重命名', 'common.discardChanges': '放弃更改', 'common.discard': '放弃', + 'common.unsavedTitle': '放弃更改?', + 'common.unsavedMessage': '你在这里输入的内容尚未保存。', + 'common.keepEditing': '继续编辑', 'common.name': '名称', 'common.email': '邮箱', 'common.password': '密码',