From c4091eaf991b7be79e2569be23c913e67e6b46d6 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 24 Oct 2026 09:19:17 +0200 Subject: [PATCH] feat(studio): a full page says so instead of swallowing the click --- .../components/Studio/StudioElementsPanel.tsx | 6 +- .../src/components/Studio/StudioNotices.tsx | 24 +++++ client/src/components/Studio/StudioShell.tsx | 2 + .../components/Studio/StudioTextStyles.tsx | 6 +- client/src/store/studioLayers.ts | 7 +- client/src/store/studioStore.ts | 20 ++++- .../unit/studio/studioStore.pageFull.test.tsx | 89 +++++++++++++++++++ shared/src/i18n/ar/journey.ts | 2 + shared/src/i18n/az/journey.ts | 2 + shared/src/i18n/br/journey.ts | 2 + shared/src/i18n/ca/journey.ts | 2 + shared/src/i18n/cs/journey.ts | 2 + shared/src/i18n/de/journey.ts | 2 + shared/src/i18n/en/journey.ts | 2 + shared/src/i18n/es/journey.ts | 2 + shared/src/i18n/et/journey.ts | 2 + shared/src/i18n/fr/journey.ts | 2 + shared/src/i18n/gr/journey.ts | 2 + shared/src/i18n/hu/journey.ts | 2 + shared/src/i18n/id/journey.ts | 2 + shared/src/i18n/it/journey.ts | 2 + shared/src/i18n/ja/journey.ts | 2 + shared/src/i18n/ko/journey.ts | 2 + shared/src/i18n/nl/journey.ts | 2 + shared/src/i18n/pl/journey.ts | 2 + shared/src/i18n/ru/journey.ts | 2 + shared/src/i18n/sk/journey.ts | 2 + shared/src/i18n/sv/journey.ts | 2 + shared/src/i18n/th/journey.ts | 2 + shared/src/i18n/tr/journey.ts | 2 + shared/src/i18n/uk/journey.ts | 2 + shared/src/i18n/vi/journey.ts | 2 + shared/src/i18n/zh-TW/journey.ts | 1 + shared/src/i18n/zh/journey.ts | 1 + 34 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 client/src/components/Studio/StudioNotices.tsx create mode 100644 client/tests/unit/studio/studioStore.pageFull.test.tsx diff --git a/client/src/components/Studio/StudioElementsPanel.tsx b/client/src/components/Studio/StudioElementsPanel.tsx index 110b043eb..05d79710f 100644 --- a/client/src/components/Studio/StudioElementsPanel.tsx +++ b/client/src/components/Studio/StudioElementsPanel.tsx @@ -195,7 +195,11 @@ function useElementAdders(page: BookPageSetup) { const els = gridElements(grid, defaultGridBox(page, single)) // The page's own cap, as a duplicate keeps it: frames that would make the book unsaveable are not added. const on = useStudioStore.getState().doc?.spreads[active] - if (!on || on.elements.length + els.length > MAX_SPREAD_ELEMENTS) return + if (!on) return + if (on.elements.length + els.length > MAX_SPREAD_ELEMENTS) { + useStudioStore.getState().refuseFull() + return + } commit(d => ({ ...d, spreads: d.spreads.map((sp, i) => (i !== active ? sp : { ...sp, elements: [...sp.elements, ...els] })), diff --git a/client/src/components/Studio/StudioNotices.tsx b/client/src/components/Studio/StudioNotices.tsx new file mode 100644 index 000000000..95addee07 --- /dev/null +++ b/client/src/components/Studio/StudioNotices.tsx @@ -0,0 +1,24 @@ +import { MAX_SPREAD_ELEMENTS } from '@trek/shared'; +import { useEffect, useRef } from 'react'; +import { useTranslation } from '../../i18n'; +import { useStudioStore } from '../../store/studioStore'; +import { useToast } from '../shared/Toast'; + +/** + * What the Studio says about a click it could not carry out. Today one thing: + * the page already holds as many elements as a page can, so a sticker, a frame, + * a paste or a duplicate was not added. Every path that refuses counts it in the + * store, and this says it once per count rather than each path in its own words. + */ +export function StudioNotices() { + const { t } = useTranslation(); + const toast = useToast(); + const refused = useStudioStore((s) => s.refusedFull); + const seen = useRef(refused); + useEffect(() => { + if (refused === seen.current) return; + seen.current = refused; + toast.warning(t('journey.studio.pageFull', { max: MAX_SPREAD_ELEMENTS })); + }, [refused, toast, t]); + return null; +} diff --git a/client/src/components/Studio/StudioShell.tsx b/client/src/components/Studio/StudioShell.tsx index 3caca64e5..eba329279 100644 --- a/client/src/components/Studio/StudioShell.tsx +++ b/client/src/components/Studio/StudioShell.tsx @@ -16,6 +16,7 @@ import { StudioExport } from './StudioExport' import { StudioAssistantColumn, StudioPageStage } from './StudioAssistantPanel' import { useStudioAssistant } from './useStudioAssistant' import { StudioHeader } from './StudioHeader' +import { StudioNotices } from './StudioNotices' import '../../styles/dashboard.css' import '../../styles/studio.css' import './bookFontFaces' @@ -84,6 +85,7 @@ export default function StudioShell() { aria-label={s.t('journey.studio.title')} > setExporting(true)} assistant={assistant} /> + {/* The journey gallery's picker, inside Studio's own layer (#2271). */} {s.providerPick && s.journey && ( { const els = placeLockup(lockup, { page, spread, words, gallery: source.photos, newId: bookElementId }); // The page's own cap, as a duplicate keeps it: a set that would make the book unsaveable is not placed. - if (!els.length || !spread || spread.elements.length + els.length > MAX_SPREAD_ELEMENTS) return; + if (!els.length || !spread) return; + if (spread.elements.length + els.length > MAX_SPREAD_ELEMENTS) { + useStudioStore.getState().refuseFull(); + return; + } commit((d) => ({ ...d, spreads: d.spreads.map((sp, i) => (i === active ? addBookLockup(sp, els, bookElementId('g')) : sp)), diff --git a/client/src/store/studioLayers.ts b/client/src/store/studioLayers.ts index 2f6c3c4ee..4852665b4 100644 --- a/client/src/store/studioLayers.ts +++ b/client/src/store/studioLayers.ts @@ -54,6 +54,7 @@ interface Host { styleClip: BookCopiedStyle | null; commit: (fn: (doc: BookDocument) => BookDocument) => void; apply: (fn: (doc: BookDocument) => BookDocument) => void; + refuseFull: () => void; } type Set = (partial: Partial | ((s: Host) => Partial)) => void; @@ -370,7 +371,11 @@ function panelActions( if (!doc) return; const chosen = spread.elements.filter((e) => ids.includes(e.id)); // The page's own cap: copies that would make the book unsaveable are not made. - if (!chosen.length || spread.elements.length + chosen.length > MAX_SPREAD_ELEMENTS) return; + if (!chosen.length) return; + if (spread.elements.length + chosen.length > MAX_SPREAD_ELEMENTS) { + get().refuseFull(); + return; + } const copies = chosen.map( (e) => ({ diff --git a/client/src/store/studioStore.ts b/client/src/store/studioStore.ts index b0dc56254..1efb289db 100644 --- a/client/src/store/studioStore.ts +++ b/client/src/store/studioStore.ts @@ -35,6 +35,14 @@ interface StudioState extends StudioLayerActions { gestureBase: BookDocument | null /** Elements copied for pasting onto any page of the book (#2316). Kept across page switches. */ clipboard: StudioClipboard | null + /** + * Counts the times something was not added because the page already holds + * MAX_SPREAD_ELEMENTS. The Studio says so once per count (StudioNotices), so + * a full page no longer swallows a click without a word. + */ + refusedFull: number + /** Notes that an addition was refused because the page is full. */ + refuseFull: () => void /** * The page a chat card points at, outlined in the page rail (journey-studio.md * 4.2). Not part of the book or its history; opening a book clears it. @@ -147,6 +155,8 @@ export const useStudioStore = create((set, get) => ({ future: [], gestureBase: null, clipboard: null, + refusedFull: 0, + refuseFull: () => set(st => ({ refusedFull: st.refusedFull + 1 })), hintSpread: null, readOnly: false, @@ -228,7 +238,11 @@ export const useStudioStore = create((set, get) => ({ addElement: (spreadIndex, el) => { // The page's own cap, as paste and duplicate keep it: what would make the book unsaveable is not added. const target = get().doc?.spreads[spreadIndex] - if (!target || target.elements.length >= MAX_SPREAD_ELEMENTS) return false + if (!target) return false + if (target.elements.length >= MAX_SPREAD_ELEMENTS) { + get().refuseFull() + return false + } get().commit(doc => replaceSpread(doc, spreadIndex, sp => ({ ...sp, elements: [...sp.elements, el] }))) /* @@ -268,6 +282,10 @@ export const useStudioStore = create((set, get) => ({ const room = Math.max(0, MAX_SPREAD_ELEMENTS - target.elements.length) const source = doc.spreads.find(sp => sp.id === clipboard.fromSpreadId) // A copied group arrives as a group of its own, never as part of one already on the page. + if (room === 0) { + get().refuseFull() + return + } const copies = regroupCopies(pastedElements(clipboard, target, doc.page).slice(0, room), source, bookElementId) if (!copies.elements.length) return get().commit(d => replaceSpread(d, spreadIndex, sp => withCopies(sp, copies))) diff --git a/client/tests/unit/studio/studioStore.pageFull.test.tsx b/client/tests/unit/studio/studioStore.pageFull.test.tsx new file mode 100644 index 000000000..cb4c9f113 --- /dev/null +++ b/client/tests/unit/studio/studioStore.pageFull.test.tsx @@ -0,0 +1,89 @@ +import { act, render } from '@testing-library/react'; +import { MAX_SPREAD_ELEMENTS, type BookDocument, type BookElement, type BookSpread } from '@trek/shared'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { StudioNotices } from '../../../src/components/Studio/StudioNotices'; +import { useStudioStore } from '../../../src/store/studioStore'; + +/** + * FE-STUDIO-FULL-001 to FE-STUDIO-FULL-004: a page that already holds + * MAX_SPREAD_ELEMENTS takes nothing more, and the Studio says so instead of + * swallowing the click. + */ + +const el = (id: string): BookElement => + ({ + id, + kind: 'shape', + frame: { x: 0, y: 0, w: 10, h: 10 }, + rotation: 0, + opacity: 1, + locked: false, + shape: 'rect', + fill: '#000000', + gradient: 'none', + stroke: null, + strokeWidth: 0, + strokeStyle: 'solid', + radius: 0, + }) as BookElement; + +const spread = (id: string, elements: BookElement[]): BookSpread => ({ + id, + role: 'inner', + background: null, + elements, + parked: [], + entryId: null, +}); + +const book = (...spreads: BookSpread[]): BookDocument => + ({ + version: 1, + title: 'T', + page: { preset: 'square-210', pageWidth: 210, pageHeight: 210, bleed: 3, safe: 5 }, + spreads, + }) as BookDocument; + +const full = () => Array.from({ length: MAX_SPREAD_ELEMENTS }, (_, i) => el(`f${i}`)); +const store = () => useStudioStore.getState(); + +beforeEach(() => { + store().load(book(spread('full', full()), spread('room', [el('a')]))); + useStudioStore.setState({ refusedFull: 0, clipboard: null }); +}); + +describe('a full page', () => { + it('FE-STUDIO-FULL-001: refuses one more element and counts the refusal', () => { + expect(store().addElement(0, el('extra'))).toBe(false); + expect(store().doc!.spreads[0].elements).toHaveLength(MAX_SPREAD_ELEMENTS); + expect(store().refusedFull).toBe(1); + expect(store().addElement(1, el('fits'))).toBe(true); + expect(store().refusedFull).toBe(1); + }); + + it('FE-STUDIO-FULL-002: a paste onto it is refused and counted', () => { + useStudioStore.setState({ clipboard: { fromSpreadId: 'room', elements: [el('a')] } }); + store().paste(0); + expect(store().doc!.spreads[0].elements).toHaveLength(MAX_SPREAD_ELEMENTS); + expect(store().refusedFull).toBe(1); + }); + + it('FE-STUDIO-FULL-003: a duplicate on it is refused and counted', () => { + store().duplicate(0, ['f0']); + expect(store().doc!.spreads[0].elements).toHaveLength(MAX_SPREAD_ELEMENTS); + expect(store().refusedFull).toBe(1); + }); + + it('FE-STUDIO-FULL-004: the Studio says it once per refusal', () => { + const addToast = vi.fn(); + window.__addToast = addToast; + render(); + expect(addToast).not.toHaveBeenCalled(); + act(() => store().refuseFull()); + expect(addToast).toHaveBeenCalledTimes(1); + expect(addToast.mock.calls[0][1]).toBe('warning'); + act(() => store().refuseFull()); + expect(addToast).toHaveBeenCalledTimes(2); + }); +}); diff --git a/shared/src/i18n/ar/journey.ts b/shared/src/i18n/ar/journey.ts index 31f01d8b8..c18cbaa5c 100644 --- a/shared/src/i18n/ar/journey.ts +++ b/shared/src/i18n/ar/journey.ts @@ -493,6 +493,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'نسخ للصق في أي صفحة (Ctrl+C)', 'journey.studio.paste': 'لصق (Ctrl+V)', 'journey.studio.pasteEmpty': 'انسخ شيئًا أولًا، ثم الصقه في أي صفحة', + 'journey.studio.pageFull': + 'هذه الصفحة ممتلئة: تحتوي على {max} عنصرًا، وهو الحد الأقصى للصفحة. احذف شيئًا أولًا أو استخدم صفحة أخرى.', 'journey.studio.myLayouts': 'تخطيطاتي', 'journey.studio.myLayoutsEmpty': 'احتفظ بصفحة رتّبتها، ورتّب صفحات أخرى بالطريقة نفسها. تبقى صورها ونصوصها كما هي.', 'journey.studio.saveLayout': 'حفظ هذه الصفحة كتخطيط', diff --git a/shared/src/i18n/az/journey.ts b/shared/src/i18n/az/journey.ts index af4fe4e4f..6fba73fbd 100644 --- a/shared/src/i18n/az/journey.ts +++ b/shared/src/i18n/az/journey.ts @@ -510,6 +510,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'İstənilən səhifəyə yapışdırmaq üçün kopyala (Ctrl+C)', 'journey.studio.paste': 'Yapışdır (Ctrl+V)', 'journey.studio.pasteEmpty': 'Əvvəlcə nəyisə kopyalayın, sonra onu istənilən səhifəyə yapışdırın', + 'journey.studio.pageFull': + 'Bu səhifə doludur: onda artıq {max} element var, bir səhifəyə bundan çoxu sığmır. Əvvəlcə nəyisə silin və ya başqa səhifədən istifadə edin.', 'journey.studio.myLayouts': 'Mənim tərtibatlarım', 'journey.studio.myLayoutsEmpty': 'Tərtib etdiyiniz səhifəni saxlayın və digər səhifələri eyni şəkildə düzün. Onların fotoları və mətnləri qalır.', diff --git a/shared/src/i18n/br/journey.ts b/shared/src/i18n/br/journey.ts index aaf635f28..71bc76fd8 100644 --- a/shared/src/i18n/br/journey.ts +++ b/shared/src/i18n/br/journey.ts @@ -466,6 +466,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Copiar, para colar em qualquer página (Ctrl+C)', 'journey.studio.paste': 'Colar (Ctrl+V)', 'journey.studio.pasteEmpty': 'Copie algo primeiro e depois cole em qualquer página', + 'journey.studio.pageFull': + 'Esta página está cheia: ela já tem {max} elementos, o máximo de uma página. Remova algo primeiro ou use outra página.', 'journey.studio.myLayouts': 'Minhas disposições', 'journey.studio.myLayoutsEmpty': 'Guarde uma página que você organizou e disponha outras páginas do mesmo jeito. As fotos e os textos delas continuam.', diff --git a/shared/src/i18n/ca/journey.ts b/shared/src/i18n/ca/journey.ts index d187f58f6..16cd6d871 100644 --- a/shared/src/i18n/ca/journey.ts +++ b/shared/src/i18n/ca/journey.ts @@ -465,6 +465,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Copia, per enganxar a qualsevol pàgina (Ctrl+C)', 'journey.studio.paste': 'Enganxa (Ctrl+V)', 'journey.studio.pasteEmpty': 'Primer copia alguna cosa i després enganxa-la a qualsevol pàgina', + 'journey.studio.pageFull': + 'Aquesta pàgina és plena: ja té {max} elements, el màxim que hi cap. Treu-ne algun primer o fes servir una altra pàgina.', 'journey.studio.myLayouts': 'Les meves disposicions', 'journey.studio.myLayoutsEmpty': 'Guarda una pàgina que hagis organitzat i disposa altres pàgines de la mateixa manera. Les seves fotos i textos es mantenen.', diff --git a/shared/src/i18n/cs/journey.ts b/shared/src/i18n/cs/journey.ts index ac80d500b..c49535e14 100644 --- a/shared/src/i18n/cs/journey.ts +++ b/shared/src/i18n/cs/journey.ts @@ -470,6 +470,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopírovat pro vložení na libovolnou stranu (Ctrl+C)', 'journey.studio.paste': 'Vložit (Ctrl+V)', 'journey.studio.pasteEmpty': 'Nejprve něco zkopírujte a pak to vložte na libovolnou stranu', + 'journey.studio.pageFull': + 'Tato stránka je plná: má už {max} prvků, víc se na stránku nevejde. Nejdřív něco odeberte, nebo použijte jinou stránku.', 'journey.studio.myLayouts': 'Moje rozvržení', 'journey.studio.myLayoutsEmpty': 'Uložte si stranu, kterou jste uspořádali, a rozvrhněte stejně i další strany. Jejich fotky a texty zůstanou.', diff --git a/shared/src/i18n/de/journey.ts b/shared/src/i18n/de/journey.ts index 7a5686834..5f1cee421 100644 --- a/shared/src/i18n/de/journey.ts +++ b/shared/src/i18n/de/journey.ts @@ -466,6 +466,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopieren, um es auf einer beliebigen Seite einzufügen (Ctrl+C)', 'journey.studio.paste': 'Einfügen (Ctrl+V)', 'journey.studio.pasteEmpty': 'Kopiere zuerst etwas und füge es dann auf einer beliebigen Seite ein', + 'journey.studio.pageFull': + 'Diese Seite ist voll: Sie hat schon {max} Elemente, mehr geht auf einer Seite nicht. Entferne zuerst etwas oder nimm eine andere Seite.', 'journey.studio.myLayouts': 'Meine Anordnungen', 'journey.studio.myLayoutsEmpty': 'Behalte eine Seite, die du angeordnet hast, und ordne andere Seiten genauso an. Ihre Fotos und Texte bleiben erhalten.', diff --git a/shared/src/i18n/en/journey.ts b/shared/src/i18n/en/journey.ts index c4b37e5fd..bcfb82800 100644 --- a/shared/src/i18n/en/journey.ts +++ b/shared/src/i18n/en/journey.ts @@ -463,6 +463,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Copy, to paste on any page (Ctrl+C)', 'journey.studio.paste': 'Paste (Ctrl+V)', 'journey.studio.pasteEmpty': 'Copy something first, then paste it on any page', + 'journey.studio.pageFull': + 'This page is full: it holds {max} elements, the most a page can. Remove something first, or use another page.', 'journey.studio.myLayouts': 'My layouts', 'journey.studio.myLayoutsEmpty': 'Keep a page you arranged and lay other pages out the same way. Their photos and words stay.', diff --git a/shared/src/i18n/es/journey.ts b/shared/src/i18n/es/journey.ts index a85bec929..eea5dc511 100644 --- a/shared/src/i18n/es/journey.ts +++ b/shared/src/i18n/es/journey.ts @@ -466,6 +466,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Copiar, para pegar en cualquier página (Ctrl+C)', 'journey.studio.paste': 'Pegar (Ctrl+V)', 'journey.studio.pasteEmpty': 'Copia algo primero y luego pégalo en cualquier página', + 'journey.studio.pageFull': + 'Esta página está llena: ya tiene {max} elementos, el máximo que cabe. Quita algo primero o usa otra página.', 'journey.studio.myLayouts': 'Mis disposiciones', 'journey.studio.myLayoutsEmpty': 'Guarda una página que hayas organizado y dispón otras páginas de la misma forma. Sus fotos y textos se mantienen.', diff --git a/shared/src/i18n/et/journey.ts b/shared/src/i18n/et/journey.ts index 20d8172af..c349b39b3 100644 --- a/shared/src/i18n/et/journey.ts +++ b/shared/src/i18n/et/journey.ts @@ -464,6 +464,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopeeri, et kleepida mis tahes lehele (Ctrl+C)', 'journey.studio.paste': 'Kleebi (Ctrl+V)', 'journey.studio.pasteEmpty': 'Kopeeri esmalt midagi, siis kleebi see mis tahes lehele', + 'journey.studio.pageFull': + 'See leht on täis: sellel on juba {max} elementi, rohkem ühele lehele ei mahu. Eemalda esmalt midagi või kasuta teist lehte.', 'journey.studio.myLayouts': 'Minu paigutused', 'journey.studio.myLayoutsEmpty': 'Salvesta enda kujundatud leht ja paiguta teised lehed samamoodi. Nende fotod ja tekstid jäävad alles.', diff --git a/shared/src/i18n/fr/journey.ts b/shared/src/i18n/fr/journey.ts index 1b2540fc4..094a3805c 100644 --- a/shared/src/i18n/fr/journey.ts +++ b/shared/src/i18n/fr/journey.ts @@ -464,6 +464,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Copier, pour coller sur n’importe quelle page (Ctrl+C)', 'journey.studio.paste': 'Coller (Ctrl+V)', 'journey.studio.pasteEmpty': 'Copiez d’abord quelque chose, puis collez-le sur n’importe quelle page', + 'journey.studio.pageFull': + "Cette page est pleine : elle contient déjà {max} éléments, le maximum pour une page. Retirez d'abord un élément ou utilisez une autre page.", 'journey.studio.myLayouts': 'Mes dispositions', 'journey.studio.myLayoutsEmpty': 'Conservez une page que vous avez agencée et disposez d’autres pages de la même façon. Leurs photos et textes restent.', diff --git a/shared/src/i18n/gr/journey.ts b/shared/src/i18n/gr/journey.ts index a6e479050..ecc210f93 100644 --- a/shared/src/i18n/gr/journey.ts +++ b/shared/src/i18n/gr/journey.ts @@ -469,6 +469,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Αντιγραφή, για επικόλληση σε οποιαδήποτε σελίδα (Ctrl+C)', 'journey.studio.paste': 'Επικόλληση (Ctrl+V)', 'journey.studio.pasteEmpty': 'Αντιγράψτε πρώτα κάτι και μετά επικολλήστε το σε οποιαδήποτε σελίδα', + 'journey.studio.pageFull': + 'Αυτή η σελίδα είναι γεμάτη: έχει ήδη {max} στοιχεία, το μέγιστο για μια σελίδα. Αφαιρέστε πρώτα κάτι ή χρησιμοποιήστε άλλη σελίδα.', 'journey.studio.myLayouts': 'Οι διατάξεις μου', 'journey.studio.myLayoutsEmpty': 'Κρατήστε μια σελίδα που έχετε διαμορφώσει και στήστε άλλες σελίδες με τον ίδιο τρόπο. Οι φωτογραφίες και τα κείμενά τους παραμένουν.', diff --git a/shared/src/i18n/hu/journey.ts b/shared/src/i18n/hu/journey.ts index 7369ae783..982e7c167 100644 --- a/shared/src/i18n/hu/journey.ts +++ b/shared/src/i18n/hu/journey.ts @@ -463,6 +463,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Másolás, bármelyik oldalra beilleszthető (Ctrl+C)', 'journey.studio.paste': 'Beillesztés (Ctrl+V)', 'journey.studio.pasteEmpty': 'Előbb másoljon ki valamit, aztán illessze be bármelyik oldalra', + 'journey.studio.pageFull': + 'Ez az oldal megtelt: már {max} elem van rajta, ennél több nem fér egy oldalra. Előbb távolíts el valamit, vagy használj másik oldalt.', 'journey.studio.myLayouts': 'Saját elrendezéseim', 'journey.studio.myLayoutsEmpty': 'Mentsen el egy elrendezett oldalt, és rendezzen el más oldalakat is ugyanígy. Azok fotói és szövegei megmaradnak.', diff --git a/shared/src/i18n/id/journey.ts b/shared/src/i18n/id/journey.ts index a51e1599f..b259736d1 100644 --- a/shared/src/i18n/id/journey.ts +++ b/shared/src/i18n/id/journey.ts @@ -455,6 +455,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Salin, untuk ditempel di halaman mana pun (Ctrl+C)', 'journey.studio.paste': 'Tempel (Ctrl+V)', 'journey.studio.pasteEmpty': 'Salin sesuatu dulu, lalu tempel di halaman mana pun', + 'journey.studio.pageFull': + 'Halaman ini penuh: sudah berisi {max} elemen, batas maksimal satu halaman. Hapus sesuatu dulu atau gunakan halaman lain.', 'journey.studio.myLayouts': 'Tata letakku', 'journey.studio.myLayoutsEmpty': 'Simpan halaman yang sudah kamu susun dan tata halaman lain dengan cara yang sama. Foto dan teksnya tetap.', diff --git a/shared/src/i18n/it/journey.ts b/shared/src/i18n/it/journey.ts index 43cfb0d5f..4fb8eacef 100644 --- a/shared/src/i18n/it/journey.ts +++ b/shared/src/i18n/it/journey.ts @@ -465,6 +465,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Copia, per incollare su qualsiasi pagina (Ctrl+C)', 'journey.studio.paste': 'Incolla (Ctrl+V)', 'journey.studio.pasteEmpty': 'Copia prima qualcosa, poi incollalo su qualsiasi pagina', + 'journey.studio.pageFull': + "Questa pagina è piena: contiene già {max} elementi, il massimo per una pagina. Rimuovi prima qualcosa o usa un'altra pagina.", 'journey.studio.myLayouts': 'Le mie disposizioni', 'journey.studio.myLayoutsEmpty': 'Conserva una pagina che hai sistemato e disponi le altre pagine allo stesso modo. Le loro foto e i testi restano.', diff --git a/shared/src/i18n/ja/journey.ts b/shared/src/i18n/ja/journey.ts index 19c9eda80..536c637bc 100644 --- a/shared/src/i18n/ja/journey.ts +++ b/shared/src/i18n/ja/journey.ts @@ -453,6 +453,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'コピーして任意のページに貼り付け (Ctrl+C)', 'journey.studio.paste': '貼り付け (Ctrl+V)', 'journey.studio.pasteEmpty': 'まず何かをコピーしてから、任意のページに貼り付けてください', + 'journey.studio.pageFull': + 'このページはいっぱいです。すでに{max}個の要素があり、1ページの上限です。先に何かを削除するか、別のページを使ってください。', 'journey.studio.myLayouts': 'マイレイアウト', 'journey.studio.myLayoutsEmpty': '配置したページを保存して、他のページも同じ配置にできます。写真と文章はそのまま残ります。', diff --git a/shared/src/i18n/ko/journey.ts b/shared/src/i18n/ko/journey.ts index a826fa5a9..1948fd0b4 100644 --- a/shared/src/i18n/ko/journey.ts +++ b/shared/src/i18n/ko/journey.ts @@ -450,6 +450,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': '복사해서 아무 페이지에나 붙여넣기 (Ctrl+C)', 'journey.studio.paste': '붙여넣기 (Ctrl+V)', 'journey.studio.pasteEmpty': '먼저 무언가를 복사한 뒤 아무 페이지에나 붙여넣으세요', + 'journey.studio.pageFull': + '이 페이지는 가득 찼습니다. 이미 요소가 {max}개 있어 한 페이지의 최대치입니다. 먼저 무언가를 지우거나 다른 페이지를 사용하세요.', 'journey.studio.myLayouts': '내 레이아웃', 'journey.studio.myLayoutsEmpty': '배치한 페이지를 저장해 두고 다른 페이지도 같은 방식으로 배치하세요. 사진과 글은 그대로 유지됩니다.', diff --git a/shared/src/i18n/nl/journey.ts b/shared/src/i18n/nl/journey.ts index b3fb20695..6800926fa 100644 --- a/shared/src/i18n/nl/journey.ts +++ b/shared/src/i18n/nl/journey.ts @@ -466,6 +466,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopiëren, om op elke pagina te plakken (Ctrl+C)', 'journey.studio.paste': 'Plakken (Ctrl+V)', 'journey.studio.pasteEmpty': 'Kopieer eerst iets en plak het dan op een willekeurige pagina', + 'journey.studio.pageFull': + 'Deze pagina is vol: er staan al {max} elementen op, het maximum voor een pagina. Verwijder eerst iets of gebruik een andere pagina.', 'journey.studio.myLayouts': 'Mijn indelingen', 'journey.studio.myLayoutsEmpty': 'Bewaar een pagina die je hebt ingedeeld en deel andere pagina’s op dezelfde manier in. Hun foto’s en teksten blijven staan.', diff --git a/shared/src/i18n/pl/journey.ts b/shared/src/i18n/pl/journey.ts index 63cc978b9..7f2551190 100644 --- a/shared/src/i18n/pl/journey.ts +++ b/shared/src/i18n/pl/journey.ts @@ -482,6 +482,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopiuj, aby wkleić na dowolnej stronie (Ctrl+C)', 'journey.studio.paste': 'Wklej (Ctrl+V)', 'journey.studio.pasteEmpty': 'Najpierw coś skopiuj, a potem wklej to na dowolnej stronie', + 'journey.studio.pageFull': + 'Ta strona jest pełna: ma już {max} elementów, tyle, ile mieści się na stronie. Najpierw coś usuń albo użyj innej strony.', 'journey.studio.myLayouts': 'Moje układy', 'journey.studio.myLayoutsEmpty': 'Zachowaj ułożoną stronę i rozmieść inne strony w ten sam sposób. Ich zdjęcia i teksty pozostaną.', diff --git a/shared/src/i18n/ru/journey.ts b/shared/src/i18n/ru/journey.ts index 7e26700db..3776b9940 100644 --- a/shared/src/i18n/ru/journey.ts +++ b/shared/src/i18n/ru/journey.ts @@ -481,6 +481,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Копировать, чтобы вставить на любую страницу (Ctrl+C)', 'journey.studio.paste': 'Вставить (Ctrl+V)', 'journey.studio.pasteEmpty': 'Сначала скопируйте что-нибудь, затем вставьте на любую страницу', + 'journey.studio.pageFull': + 'Эта страница заполнена: на ней уже {max} элементов, больше на страницу не помещается. Сначала удалите что-нибудь или используйте другую страницу.', 'journey.studio.myLayouts': 'Мои макеты', 'journey.studio.myLayoutsEmpty': 'Сохраните оформленную страницу и расположите другие страницы так же. Их фото и тексты останутся.', diff --git a/shared/src/i18n/sk/journey.ts b/shared/src/i18n/sk/journey.ts index 932c15ddb..a17a763bd 100644 --- a/shared/src/i18n/sk/journey.ts +++ b/shared/src/i18n/sk/journey.ts @@ -474,6 +474,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopírovať na vloženie na ľubovoľnú stranu (Ctrl+C)', 'journey.studio.paste': 'Vložiť (Ctrl+V)', 'journey.studio.pasteEmpty': 'Najprv niečo skopírujte a potom to vložte na ľubovoľnú stranu', + 'journey.studio.pageFull': + 'Táto stránka je plná: má už {max} prvkov, viac sa na stránku nezmestí. Najprv niečo odstráňte alebo použite inú stránku.', 'journey.studio.myLayouts': 'Moje rozloženia', 'journey.studio.myLayoutsEmpty': 'Uložte si stranu, ktorú ste usporiadali, a rozložte rovnako aj ďalšie strany. Ich fotky a texty zostanú.', diff --git a/shared/src/i18n/sv/journey.ts b/shared/src/i18n/sv/journey.ts index 223a1822f..c08c4149a 100644 --- a/shared/src/i18n/sv/journey.ts +++ b/shared/src/i18n/sv/journey.ts @@ -463,6 +463,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopiera för att klistra in på valfri sida (Ctrl+C)', 'journey.studio.paste': 'Klistra in (Ctrl+V)', 'journey.studio.pasteEmpty': 'Kopiera något först och klistra sedan in det på valfri sida', + 'journey.studio.pageFull': + 'Den här sidan är full: den har redan {max} element, det mesta en sida rymmer. Ta bort något först eller använd en annan sida.', 'journey.studio.myLayouts': 'Mina layouter', 'journey.studio.myLayoutsEmpty': 'Spara en sida du har ordnat och lägg upp andra sidor på samma sätt. Deras foton och texter finns kvar.', diff --git a/shared/src/i18n/th/journey.ts b/shared/src/i18n/th/journey.ts index 10ad340c5..5a13c4d21 100644 --- a/shared/src/i18n/th/journey.ts +++ b/shared/src/i18n/th/journey.ts @@ -449,6 +449,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'คัดลอกเพื่อวางในหน้าใดก็ได้ (Ctrl+C)', 'journey.studio.paste': 'วาง (Ctrl+V)', 'journey.studio.pasteEmpty': 'คัดลอกบางอย่างก่อน แล้ววางในหน้าใดก็ได้', + 'journey.studio.pageFull': + 'หน้านี้เต็มแล้ว มีองค์ประกอบ {max} ชิ้น ซึ่งเป็นจำนวนสูงสุดต่อหน้า โปรดลบบางอย่างก่อนหรือใช้หน้าอื่น', 'journey.studio.myLayouts': 'เค้าโครงของฉัน', 'journey.studio.myLayoutsEmpty': 'เก็บหน้าที่คุณจัดไว้ แล้วจัดหน้าอื่นในแบบเดียวกัน รูปภาพและข้อความของหน้าเหล่านั้นยังคงอยู่', diff --git a/shared/src/i18n/tr/journey.ts b/shared/src/i18n/tr/journey.ts index 701cb973b..a84ed39bf 100644 --- a/shared/src/i18n/tr/journey.ts +++ b/shared/src/i18n/tr/journey.ts @@ -464,6 +464,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Kopyala, herhangi bir sayfaya yapıştırmak için (Ctrl+C)', 'journey.studio.paste': 'Yapıştır (Ctrl+V)', 'journey.studio.pasteEmpty': 'Önce bir şey kopyala, sonra herhangi bir sayfaya yapıştır', + 'journey.studio.pageFull': + 'Bu sayfa dolu: zaten {max} öğe var, bir sayfaya sığabilecek en fazla sayı bu. Önce bir şey kaldırın ya da başka bir sayfa kullanın.', 'journey.studio.myLayouts': 'Yerleşimlerim', 'journey.studio.myLayoutsEmpty': 'Düzenlediğin bir sayfayı sakla ve diğer sayfaları da aynı şekilde yerleştir. Fotoğrafları ve metinleri olduğu gibi kalır.', diff --git a/shared/src/i18n/uk/journey.ts b/shared/src/i18n/uk/journey.ts index dd2e92c83..2afec76a3 100644 --- a/shared/src/i18n/uk/journey.ts +++ b/shared/src/i18n/uk/journey.ts @@ -485,6 +485,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Копіювати, щоб вставити на будь-яку сторінку (Ctrl+C)', 'journey.studio.paste': 'Вставити (Ctrl+V)', 'journey.studio.pasteEmpty': 'Спершу щось скопіюйте, потім вставте на будь-яку сторінку', + 'journey.studio.pageFull': + 'Ця сторінка заповнена: на ній уже {max} елементів, більше на сторінку не вміщується. Спершу видаліть щось або скористайтеся іншою сторінкою.', 'journey.studio.myLayouts': 'Мої макети', 'journey.studio.myLayoutsEmpty': 'Збережіть сторінку, яку ви впорядкували, і розкладайте інші сторінки так само. Їхні фото й тексти залишаться.', diff --git a/shared/src/i18n/vi/journey.ts b/shared/src/i18n/vi/journey.ts index bb71095de..3e24f9cc3 100644 --- a/shared/src/i18n/vi/journey.ts +++ b/shared/src/i18n/vi/journey.ts @@ -455,6 +455,8 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': 'Sao chép để dán vào trang bất kỳ (Ctrl+C)', 'journey.studio.paste': 'Dán (Ctrl+V)', 'journey.studio.pasteEmpty': 'Hãy sao chép thứ gì đó trước, rồi dán vào trang bất kỳ', + 'journey.studio.pageFull': + 'Trang này đã đầy: đã có {max} thành phần, mức tối đa cho một trang. Hãy xóa bớt trước hoặc dùng trang khác.', 'journey.studio.myLayouts': 'Bố cục của tôi', 'journey.studio.myLayoutsEmpty': 'Lưu một trang bạn đã sắp xếp và dàn các trang khác theo cùng cách. Ảnh và chữ của chúng được giữ nguyên.', diff --git a/shared/src/i18n/zh-TW/journey.ts b/shared/src/i18n/zh-TW/journey.ts index a3eec5569..c4ab24fce 100644 --- a/shared/src/i18n/zh-TW/journey.ts +++ b/shared/src/i18n/zh-TW/journey.ts @@ -447,6 +447,7 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': '複製,可貼到任何頁面 (Ctrl+C)', 'journey.studio.paste': '貼上 (Ctrl+V)', 'journey.studio.pasteEmpty': '請先複製內容,再貼到任何頁面', + 'journey.studio.pageFull': '此頁面已滿:已有 {max} 個元素,達到單頁上限。請先刪除一些內容,或使用其他頁面。', 'journey.studio.myLayouts': '我的版面', 'journey.studio.myLayoutsEmpty': '保存你排好的頁面,讓其他頁面套用相同的版面。它們的照片與文字會保留。', 'journey.studio.saveLayout': '將此頁面儲存為版面', diff --git a/shared/src/i18n/zh/journey.ts b/shared/src/i18n/zh/journey.ts index 7b37ecaf3..bb67921bd 100644 --- a/shared/src/i18n/zh/journey.ts +++ b/shared/src/i18n/zh/journey.ts @@ -447,6 +447,7 @@ const journey: TranslationStrings = { 'journey.studio.copyToPage': '复制,可粘贴到任意页面 (Ctrl+C)', 'journey.studio.paste': '粘贴 (Ctrl+V)', 'journey.studio.pasteEmpty': '请先复制内容,然后粘贴到任意页面', + 'journey.studio.pageFull': '此页面已满:已有 {max} 个元素,达到单页上限。请先删除一些内容,或使用其他页面。', 'journey.studio.myLayouts': '我的版式', 'journey.studio.myLayoutsEmpty': '保存你排好的页面,让其他页面采用相同的排版。它们的照片和文字保持不变。', 'journey.studio.saveLayout': '将此页面保存为版式',