From 23b28a1f3f855c1d7c923eb54838bc59fab29c2f Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 18 Oct 2026 13:06:32 +0200 Subject: [PATCH] feat(packing): drag a category by its head to move it among the others Categories showed in the order they were created, which a list kept per day cannot undo once the days swap. A grip in each category's head band moves the whole category onto another's place on desktop. A category has no position of its own, it shows where its first item is, so the move reorders the items as one block through the item reorder the list already has: no new column, and the order reaches everyone the way a dragged item does. The other view's categories keep their order. --- .../Packing/PackingListPanelCategoryGroup.tsx | 61 ++++++++++++++++++- .../Packing/PackingListPanelList.tsx | 20 +++++- .../Packing/packingListModel.test.ts | 27 +++++++- .../components/Packing/packingListModel.ts | 28 +++++++++ .../components/Packing/usePackingListPanel.ts | 11 +++- shared/src/i18n/ar/packing.ts | 1 + shared/src/i18n/az/packing.ts | 1 + shared/src/i18n/br/packing.ts | 1 + shared/src/i18n/ca/packing.ts | 1 + shared/src/i18n/cs/packing.ts | 1 + shared/src/i18n/de/packing.ts | 1 + shared/src/i18n/en/packing.ts | 1 + shared/src/i18n/es/packing.ts | 1 + shared/src/i18n/et/packing.ts | 1 + shared/src/i18n/fr/packing.ts | 1 + shared/src/i18n/gr/packing.ts | 1 + shared/src/i18n/hu/packing.ts | 1 + shared/src/i18n/id/packing.ts | 1 + shared/src/i18n/it/packing.ts | 1 + shared/src/i18n/ja/packing.ts | 1 + shared/src/i18n/ko/packing.ts | 1 + shared/src/i18n/nl/packing.ts | 1 + shared/src/i18n/pl/packing.ts | 1 + shared/src/i18n/ru/packing.ts | 1 + shared/src/i18n/sk/packing.ts | 1 + shared/src/i18n/sv/packing.ts | 1 + shared/src/i18n/th/packing.ts | 1 + shared/src/i18n/tr/packing.ts | 1 + shared/src/i18n/uk/packing.ts | 1 + shared/src/i18n/vi/packing.ts | 1 + shared/src/i18n/zh-TW/packing.ts | 1 + shared/src/i18n/zh/packing.ts | 1 + 32 files changed, 167 insertions(+), 7 deletions(-) diff --git a/client/src/components/Packing/PackingListPanelCategoryGroup.tsx b/client/src/components/Packing/PackingListPanelCategoryGroup.tsx index 78a4761b1..c911c8c1b 100644 --- a/client/src/components/Packing/PackingListPanelCategoryGroup.tsx +++ b/client/src/components/Packing/PackingListPanelCategoryGroup.tsx @@ -4,7 +4,7 @@ import { useToast } from '../shared/Toast' import { useTranslation } from '../../i18n' import { Trash2, Plus, ChevronDown, ChevronRight, - X, Pencil, Check, MoreHorizontal, CheckCheck, RotateCcw, UserPlus, + X, Pencil, Check, MoreHorizontal, CheckCheck, RotateCcw, UserPlus, GripVertical, } from 'lucide-react' import type { PackingItem, PackingBag } from '../../types' import { katColor } from './packingListPanel.helpers' @@ -38,6 +38,18 @@ interface KategorieGruppeProps { // Off while the list is shown A-Z: a drop there would move an item in an order // the reader cannot see. reorderable?: boolean + // Moving the whole category among the others, wired by the list, which sees every + // card. `active` is true while any category is being dragged, so a dragged ITEM + // passing over a card is left to the item rows. + categoryDrag?: { + active: boolean + isDragging: boolean + isOver: boolean + onStart: () => void + onOver: () => void + onEnd: () => void + onDrop: () => void + } // Three-tier sharing (#858) — threaded down to each item's share control. currentUserId?: number onSetSharing?: (id: number, visibility: 'common' | 'personal' | 'shared', recipientIds: number[]) => void @@ -46,7 +58,7 @@ interface KategorieGruppeProps { onLeave?: (id: number, userId: number) => void } -export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRename, onDeleteAll, onDeleteItem, onAddItem, assignees, tripMembers, onSetAssignees, bagTrackingEnabled, bags, onCreateBag, canEdit = true, allItems, onReorder, reorderable = true, currentUserId, onSetSharing, onClone, onJoin, onLeave }: KategorieGruppeProps) { +export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRename, onDeleteAll, onDeleteItem, onAddItem, assignees, tripMembers, onSetAssignees, bagTrackingEnabled, bags, onCreateBag, canEdit = true, allItems, onReorder, reorderable = true, categoryDrag, currentUserId, onSetSharing, onClone, onJoin, onLeave }: KategorieGruppeProps) { const [offen, setOffen] = useState(true) const [dragId, setDragId] = useState(null) const [overId, setOverId] = useState(null) @@ -120,8 +132,9 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen return ( // Shaped after the phone's category card: a tinted head band carrying the // name as written, the count, and the fold arrow on the far right. -
+
+ {editingName && canEdit ? ( @@ -344,6 +357,48 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen ))}
)} +
+ ) +} + +type CategoryDrag = NonNullable + +/** + * The card, and while a category is dragged its drop target. Captured, because + * the item rows stop their own drops from bubbling. + */ +function CategoryDropFrame({ drag, children }: { drag?: CategoryDrag; children: React.ReactNode }) { + const target = drag?.active ? drag : null + return ( +
{ e.preventDefault(); e.dataTransfer.dropEffect = 'move'; target.onOver() }) : undefined} + onDropCapture={target ? (e => { e.preventDefault(); e.stopPropagation(); target.onDrop() }) : undefined} + style={{ + marginBottom: 6, background: 'var(--bg-card)', borderRadius: 16, border: '1px solid var(--border-secondary)', overflow: 'visible', + opacity: drag?.isDragging ? 0.4 : 1, + boxShadow: drag?.isOver ? '0 0 0 2px var(--accent)' : 'none', + transition: 'opacity 0.15s, box-shadow 0.15s', + }} + > + {children}
) } + +/** The handle a category is dragged by, in the head band before its colour dot. */ +function CategoryGrip({ drag, kategorie, label }: { drag?: CategoryDrag; kategorie: string; label: string }) { + if (!drag) return null + return ( + { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', kategorie); drag.onStart() }} + onDragEnd={drag.onEnd} + style={{ cursor: 'grab', display: 'flex', alignItems: 'center', color: 'var(--text-faint)', flexShrink: 0, marginInlineStart: -6 }} + > + + + ) +} diff --git a/client/src/components/Packing/PackingListPanelList.tsx b/client/src/components/Packing/PackingListPanelList.tsx index 946da4e37..ac9c950f9 100644 --- a/client/src/components/Packing/PackingListPanelList.tsx +++ b/client/src/components/Packing/PackingListPanelList.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import type { PackingState } from './usePackingListPanel' import { KategorieGruppe } from './PackingListPanelCategoryGroup' import EmptyState from '../shared/EmptyState' @@ -7,8 +8,13 @@ export function PackingList(S: PackingState) { items, gruppiert, t, tripId, allCategories, handleRenameCategory, handleDeleteCategory, handleDeleteItem, handleAddItemToCategory, categoryAssignees, tripMembers, handleSetAssignees, bagTrackingEnabled, bags, handleCreateBagByName, canEdit, reorderPackingItems, - currentUserId, handleSetSharing, handleCloneItem, handleJoinItem, handleLeaveItem, sort, + currentUserId, handleSetSharing, handleCloneItem, handleJoinItem, handleLeaveItem, sort, moveCategory, } = S + // Which category is being dragged and which card it is over. Held here, above + // the cards, because the drag starts in one card and ends in another. + const [draggedCategory, setDraggedCategory] = useState(null) + const [overCategory, setOverCategory] = useState(null) + const endCategoryDrag = () => { setDraggedCategory(null); setOverCategory(null) } return (
{items.length === 0 ? ( @@ -40,6 +46,18 @@ export function PackingList(S: PackingState) { allItems={items} onReorder={(orderedIds) => reorderPackingItems(tripId, orderedIds)} reorderable={sort === 'manual'} + categoryDrag={canEdit ? { + active: draggedCategory !== null, + isDragging: draggedCategory === kat, + isOver: overCategory === kat && draggedCategory !== null && draggedCategory !== kat, + onStart: () => { setDraggedCategory(kat); setOverCategory(null) }, + onOver: () => setOverCategory(kat), + onEnd: endCategoryDrag, + onDrop: () => { + if (draggedCategory !== null) moveCategory(draggedCategory, kat) + endCategoryDrag() + }, + } : undefined} currentUserId={currentUserId} onSetSharing={handleSetSharing} onClone={handleCloneItem} diff --git a/client/src/components/Packing/packingListModel.test.ts b/client/src/components/Packing/packingListModel.test.ts index 2388109ca..2d397d6b5 100644 --- a/client/src/components/Packing/packingListModel.test.ts +++ b/client/src/components/Packing/packingListModel.test.ts @@ -6,6 +6,7 @@ import { groupPackingItems, isLastCustomItemInCategory, isPackingPlaceholder, + itemOrderWithCategoryMoved, packingCategoryOrder, packingItemWeight, packingProgress, @@ -14,7 +15,7 @@ import { } from './packingListModel'; import { PACKING_PLACEHOLDER_NAME } from './packingListPanel.constants'; -// FE-PACK-MODEL-001 to FE-PACK-MODEL-008 +// FE-PACK-MODEL-001 to FE-PACK-MODEL-009 const shared = buildPackingItem({ id: 1, name: 'Tent', category: 'Camp', checked: 1 }); const mine = buildPackingItem({ id: 2, name: 'Socks', category: 'Clothes', is_private: 1 }); @@ -76,4 +77,28 @@ describe('packingListModel', () => { expect(isPackingPlaceholder({ name: PACKING_PLACEHOLDER_NAME })).toBe(true); expect(isPackingPlaceholder({ name: 'Tent' })).toBe(false); }); + it('FE-PACK-MODEL-009: moving a category moves its items as a block, the other view stays as it was', () => { + const day1 = buildPackingItem({ id: 11, name: 'Shirt', category: 'Day 1' }); + const day2a = buildPackingItem({ id: 12, name: 'Shorts', category: 'Day 2' }); + const own = buildPackingItem({ id: 13, name: 'Pills', category: 'Day 2', is_private: 1 }); + const day3 = buildPackingItem({ id: 14, name: 'Jacket', category: 'Day 3' }); + const day2b = buildPackingItem({ id: 15, name: 'Hat', category: 'Day 2' }); + const items = [day1, day2a, own, day3, day2b]; + const common = packingViewItems(items, 'common'); + + const order = itemOrderWithCategoryMoved(items, common, 'Day 3', 'Day 1', 'Other'); + expect(order).toEqual([14, 11, 12, 15, 13]); + const moved = (order ?? []).flatMap((id) => items.filter((i) => i.id === id)); + expect(packingCategoryOrder(packingViewItems(moved, 'common'), 'Other')).toEqual(['Day 3', 'Day 1', 'Day 2']); + expect(packingCategoryOrder(packingViewItems(moved, 'personal'), 'Other')).toEqual(['Day 2']); + + expect(itemOrderWithCategoryMoved(items, common, 'Day 1', 'Day 3', 'Other')).toEqual([12, 15, 14, 11, 13]); + }); + + it('FE-PACK-MODEL-009b: nothing moves for the same category or one the view does not show', () => { + const items = [shared, stove, loose]; + expect(itemOrderWithCategoryMoved(items, items, 'Camp', 'Camp', 'Other')).toBeNull(); + expect(itemOrderWithCategoryMoved(items, items, 'Clothes', 'Camp', 'Other')).toBeNull(); + expect(itemOrderWithCategoryMoved(items, items, 'Other', 'Camp', 'Other')).toEqual([4, 1, 3]); + }); }); diff --git a/client/src/components/Packing/packingListModel.ts b/client/src/components/Packing/packingListModel.ts index 39b195958..774d56b6c 100644 --- a/client/src/components/Packing/packingListModel.ts +++ b/client/src/components/Packing/packingListModel.ts @@ -25,6 +25,34 @@ export function packingCategoryOrder(viewItems: PackingItem[], defaultCategory: return seen; } +/** + * The whole list's item order with category `from` moved to where `to` stands. + * + * A category has no position of its own: it shows where its first item is. So a + * category is moved by moving its items, as one block, through the item reorder + * the list already has; nothing new is stored, and the order reaches every device + * the way a dragged item does. The view's items come first, grouped in the new + * order with each group in its own order; the other view's items follow in theirs, + * which leaves that view's categories as they were. Null when nothing would move. + */ +export function itemOrderWithCategoryMoved( + items: PackingItem[], + viewItems: PackingItem[], + from: string, + to: string, + defaultCategory: string +): number[] | null { + const order = packingCategoryOrder(viewItems, defaultCategory); + const fromIndex = order.indexOf(from); + const toIndex = order.indexOf(to); + if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) return null; + order.splice(fromIndex, 1); + order.splice(toIndex, 0, from); + const inView = new Set(viewItems.map((i) => i.id)); + const grouped = order.flatMap((cat) => viewItems.filter((i) => (i.category || defaultCategory) === cat)); + return [...grouped, ...items.filter((i) => !inView.has(i.id))].map((i) => i.id); +} + export function packingStatusFiltered(items: PackingItem[], status: PackingStatusFilter): PackingItem[] { if (status === 'open') return items.filter((i) => !i.checked); if (status === 'done') return items.filter((i) => !!i.checked); diff --git a/client/src/components/Packing/usePackingListPanel.ts b/client/src/components/Packing/usePackingListPanel.ts index 579784529..0b1a3f4f3 100644 --- a/client/src/components/Packing/usePackingListPanel.ts +++ b/client/src/components/Packing/usePackingListPanel.ts @@ -8,7 +8,7 @@ import { tripsApi } from '../../api/client' import { useAddonStore } from '../../store/addonStore' import type { PackingItem } from '../../types' import { parseImportLines, sortItemsByName } from './packingListPanel.helpers' -import { groupPackingItems, packingCategoryOrder, packingProgress, packingViewItems } from './packingListModel' +import { groupPackingItems, itemOrderWithCategoryMoved, packingCategoryOrder, packingProgress, packingViewItems } from './packingListModel' import { usePackingBags } from './usePackingBags' import { usePackingCategoryAssignees } from './usePackingCategoryAssignees' import { usePackingImport } from './usePackingImport' @@ -106,6 +106,13 @@ export function usePackingList({ tripId, items, openImportSignal = 0, addCategor const { checked: abgehakt, pct: fortschritt } = packingProgress(viewItems) + // A category dropped onto another takes its place; the slice reorders + // optimistically and rolls back with a toast of its own. + const moveCategory = (from: string, to: string) => { + const orderedIds = itemOrderWithCategoryMoved(items, viewItems, from, to, t('packing.defaultCategory')) + if (orderedIds) void reorderPackingItems(tripId, orderedIds) + } + const { addingCategory, setAddingCategory, newCategoryName: newCatName, setNewCategoryName: setNewCatName, addItemToCategory: handleAddItemToCategory, deleteItem: handleDeleteItem, addNewCategory: handleAddNewCategory, @@ -213,7 +220,7 @@ export function usePackingList({ tripId, items, openImportSignal = 0, addCategor handleSetSharing, handleCloneItem, handleJoinItem, handleLeaveItem, tripId, items, inlineHeader, t, canEdit, isAdmin, font, reorderPackingItems, filter, setFilter, sort, setSort, addingCategory, setAddingCategory, newCatName, setNewCatName, - tripMembers, categoryAssignees, handleSetAssignees, allCategories, gruppiert, abgehakt, fortschritt, + tripMembers, categoryAssignees, handleSetAssignees, allCategories, gruppiert, abgehakt, fortschritt, moveCategory, handleAddItemToCategory, handleAddNewCategory, handleRenameCategory, handleDeleteCategory, handleDeleteItem, handleClearChecked, bagTrackingEnabled, bags, unassignedWeightGrams, serverWeightsFresh, newBagName, setNewBagName, showAddBag, setShowAddBag, showBagModal, setShowBagModal, handleCreateBag, handleCreateBagByName, handleDeleteBag, handleUpdateBag, handleSetBagMembers, diff --git a/shared/src/i18n/ar/packing.ts b/shared/src/i18n/ar/packing.ts index 42c04009f..78c484962 100644 --- a/shared/src/i18n/ar/packing.ts +++ b/shared/src/i18n/ar/packing.ts @@ -111,6 +111,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat.few': 'هل أنت متأكد أنك تريد حذف القائمة "{name}" التي تحتوي على {count} عناصر؟', 'packing.confirm.deleteCat.many': 'هل أنت متأكد أنك تريد حذف القائمة "{name}" التي تحتوي على {count} عنصرًا؟', 'packing.defaultCategory': 'أخرى', + 'packing.dragCategory': 'اسحب لإعادة ترتيب الفئة', 'packing.toast.saveError': 'فشل الحفظ', 'packing.toast.deleteError': 'فشل الحذف', 'packing.toast.renameError': 'فشلت إعادة التسمية', diff --git a/shared/src/i18n/az/packing.ts b/shared/src/i18n/az/packing.ts index aaa2e8ebb..e6315d6bf 100644 --- a/shared/src/i18n/az/packing.ts +++ b/shared/src/i18n/az/packing.ts @@ -117,6 +117,7 @@ const packing: TranslationStrings = { '“{name}” siyahısını daxilindəki {count} elementlə birlikdə silmək istədiyinizə əminsiniz?', 'packing.defaultCategory': 'Digər', + 'packing.dragCategory': 'Kateqoriyanı yerini dəyişmək üçün sürüşdürün', 'packing.toast.saveError': 'Yadda saxlamaq mümkün olmadı', 'packing.toast.deleteError': 'Silmək mümkün olmadı', 'packing.toast.renameError': 'Adını dəyişmək mümkün olmadı', diff --git a/shared/src/i18n/br/packing.ts b/shared/src/i18n/br/packing.ts index d773cb681..cd3f387d7 100644 --- a/shared/src/i18n/br/packing.ts +++ b/shared/src/i18n/br/packing.ts @@ -82,6 +82,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Tem certeza de que deseja excluir a lista "{name}" com {count} itens?', 'packing.confirm.deleteCat.one': 'Tem certeza de que deseja excluir a lista "{name}" com {count} item?', 'packing.defaultCategory': 'Outros', + 'packing.dragCategory': 'Arraste para reordenar a categoria', 'packing.toast.saveError': 'Falha ao salvar', 'packing.toast.deleteError': 'Falha ao excluir', 'packing.toast.renameError': 'Falha ao renomear', diff --git a/shared/src/i18n/ca/packing.ts b/shared/src/i18n/ca/packing.ts index a907e5231..4ad02c686 100644 --- a/shared/src/i18n/ca/packing.ts +++ b/shared/src/i18n/ca/packing.ts @@ -70,6 +70,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Segur que vols eliminar la categoria "{name}" amb {count} elements?', 'packing.confirm.deleteCat.one': 'Segur que vols eliminar la categoria "{name}" amb {count} element?', 'packing.defaultCategory': 'Altres', + 'packing.dragCategory': 'Arrossega per reordenar la categoria', 'packing.toast.saveError': "No s'ha pogut desar", 'packing.toast.deleteError': "No s'ha pogut eliminar", 'packing.toast.renameError': "No s'ha pogut canviar el nom", diff --git a/shared/src/i18n/cs/packing.ts b/shared/src/i18n/cs/packing.ts index 10af5f1f5..21f2b9f49 100644 --- a/shared/src/i18n/cs/packing.ts +++ b/shared/src/i18n/cs/packing.ts @@ -90,6 +90,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat.one': 'Opravdu chcete smazat seznam „{name}“ s {count} položkou?', 'packing.confirm.deleteCat.few': 'Opravdu chcete smazat seznam „{name}“ s {count} položkami?', 'packing.defaultCategory': 'Ostatní', + 'packing.dragCategory': 'Přetažením změníte pořadí kategorie', 'packing.toast.saveError': 'Uložení se nezdařilo', 'packing.toast.deleteError': 'Smazání se nezdařilo', 'packing.toast.renameError': 'Přejmenování se nezdařilo', diff --git a/shared/src/i18n/de/packing.ts b/shared/src/i18n/de/packing.ts index 4688626b9..e8d737e94 100644 --- a/shared/src/i18n/de/packing.ts +++ b/shared/src/i18n/de/packing.ts @@ -102,6 +102,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Möchtest du die Liste „{name}“ mit {count} Einträgen wirklich löschen?', 'packing.confirm.deleteCat.one': 'Möchtest du die Liste „{name}“ mit {count} Eintrag wirklich löschen?', 'packing.defaultCategory': 'Sonstiges', + 'packing.dragCategory': 'Ziehen, um die Kategorie umzusortieren', 'packing.toast.saveError': 'Fehler beim Speichern', 'packing.toast.deleteError': 'Fehler beim Löschen', 'packing.toast.renameError': 'Fehler beim Umbenennen', diff --git a/shared/src/i18n/en/packing.ts b/shared/src/i18n/en/packing.ts index b61470682..1548c272b 100644 --- a/shared/src/i18n/en/packing.ts +++ b/shared/src/i18n/en/packing.ts @@ -101,6 +101,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Are you sure you want to delete the list "{name}" with {count} items?', 'packing.confirm.deleteCat.one': 'Are you sure you want to delete the list "{name}" with {count} item?', 'packing.defaultCategory': 'Other', + 'packing.dragCategory': 'Drag to reorder the category', 'packing.toast.saveError': 'Failed to save', 'packing.toast.deleteError': 'Failed to delete', 'packing.toast.renameError': 'Failed to rename', diff --git a/shared/src/i18n/es/packing.ts b/shared/src/i18n/es/packing.ts index a75ebca7b..79169e321 100644 --- a/shared/src/i18n/es/packing.ts +++ b/shared/src/i18n/es/packing.ts @@ -83,6 +83,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': '¿Seguro que quieres eliminar la lista "{name}" con {count} elementos?', 'packing.confirm.deleteCat.one': '¿Seguro que quieres eliminar la lista "{name}" con {count} elemento?', 'packing.defaultCategory': 'Otros', + 'packing.dragCategory': 'Arrastra para reordenar la categoría', 'packing.toast.saveError': 'No se pudo guardar', 'packing.toast.deleteError': 'No se pudo eliminar', 'packing.toast.renameError': 'No se pudo renombrar', diff --git a/shared/src/i18n/et/packing.ts b/shared/src/i18n/et/packing.ts index 53a28cfca..fbcdc1182 100644 --- a/shared/src/i18n/et/packing.ts +++ b/shared/src/i18n/et/packing.ts @@ -101,6 +101,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Kas soovid kindlasti kustutada nimekirja "{name}" koos {count} esemega?', 'packing.confirm.deleteCat.one': 'Kas soovid kindlasti kustutada nimekirja "{name}" koos {count} esemega?', 'packing.defaultCategory': 'Muu', + 'packing.dragCategory': 'Lohista kategooria järjekorra muutmiseks', 'packing.toast.saveError': 'Salvestamine ebaõnnestus', 'packing.toast.deleteError': 'Kustutamine ebaõnnestus', 'packing.toast.renameError': 'Ümbernimetamine ebaõnnestus', diff --git a/shared/src/i18n/fr/packing.ts b/shared/src/i18n/fr/packing.ts index cbee1132f..ca4c166ac 100644 --- a/shared/src/i18n/fr/packing.ts +++ b/shared/src/i18n/fr/packing.ts @@ -83,6 +83,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Voulez-vous vraiment supprimer la liste « {name} » contenant {count} éléments ?', 'packing.confirm.deleteCat.one': 'Voulez-vous vraiment supprimer la liste « {name} » contenant {count} élément ?', 'packing.defaultCategory': 'Autre', + 'packing.dragCategory': 'Faites glisser pour réordonner la catégorie', 'packing.toast.saveError': "Échec de l'enregistrement", 'packing.toast.deleteError': 'Échec de la suppression', 'packing.toast.renameError': 'Échec du renommage', diff --git a/shared/src/i18n/gr/packing.ts b/shared/src/i18n/gr/packing.ts index 9fa1aba9c..3c4bc138e 100644 --- a/shared/src/i18n/gr/packing.ts +++ b/shared/src/i18n/gr/packing.ts @@ -84,6 +84,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Είστε βέβαιοι ότι θέλετε να διαγράψετε τη λίστα "{name}" με {count} στοιχεία;', 'packing.confirm.deleteCat.one': 'Είστε βέβαιοι ότι θέλετε να διαγράψετε τη λίστα "{name}" με {count} στοιχείο;', 'packing.defaultCategory': 'Άλλα', + 'packing.dragCategory': 'Σύρετε για να αλλάξετε τη σειρά της κατηγορίας', 'packing.toast.saveError': 'Αποτυχία αποθήκευσης', 'packing.toast.deleteError': 'Αποτυχία διαγραφής', 'packing.toast.renameError': 'Αποτυχία μετονομασίας', diff --git a/shared/src/i18n/hu/packing.ts b/shared/src/i18n/hu/packing.ts index 465d019eb..2fecec2ac 100644 --- a/shared/src/i18n/hu/packing.ts +++ b/shared/src/i18n/hu/packing.ts @@ -85,6 +85,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat.one': 'Biztosan törölni szeretnéd a(z) „{name}” listát a benne lévő {count} elemmel együtt?', 'packing.defaultCategory': 'Egyéb', + 'packing.dragCategory': 'Húzd a kategória átrendezéséhez', 'packing.toast.saveError': 'Nem sikerült menteni', 'packing.toast.deleteError': 'Nem sikerült törölni', 'packing.toast.renameError': 'Nem sikerült átnevezni', diff --git a/shared/src/i18n/id/packing.ts b/shared/src/i18n/id/packing.ts index f554c1225..440b64473 100644 --- a/shared/src/i18n/id/packing.ts +++ b/shared/src/i18n/id/packing.ts @@ -76,6 +76,7 @@ const packing: TranslationStrings = { 'packing.confirm.clearChecked': 'Yakin ingin menghapus {count} item yang dicentang?', 'packing.confirm.deleteCat': 'Yakin ingin menghapus daftar "{name}" dengan {count} item?', 'packing.defaultCategory': 'Lainnya', + 'packing.dragCategory': 'Seret untuk mengurutkan ulang kategori', 'packing.toast.saveError': 'Gagal menyimpan', 'packing.toast.deleteError': 'Gagal menghapus', 'packing.toast.renameError': 'Gagal mengganti nama', diff --git a/shared/src/i18n/it/packing.ts b/shared/src/i18n/it/packing.ts index 809d9f35e..a42a55a6c 100644 --- a/shared/src/i18n/it/packing.ts +++ b/shared/src/i18n/it/packing.ts @@ -83,6 +83,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Vuoi davvero eliminare la lista "{name}" con {count} elementi?', 'packing.confirm.deleteCat.one': 'Vuoi davvero eliminare la lista "{name}" con {count} elemento?', 'packing.defaultCategory': 'Altro', + 'packing.dragCategory': 'Trascina per riordinare la categoria', 'packing.toast.saveError': 'Impossibile salvare', 'packing.toast.deleteError': 'Impossibile eliminare', 'packing.toast.renameError': 'Impossibile rinominare', diff --git a/shared/src/i18n/ja/packing.ts b/shared/src/i18n/ja/packing.ts index 4a0becad8..888d6db76 100644 --- a/shared/src/i18n/ja/packing.ts +++ b/shared/src/i18n/ja/packing.ts @@ -75,6 +75,7 @@ const packing: TranslationStrings = { 'packing.confirm.clearChecked': 'チェック済み{count}件を削除しますか?', 'packing.confirm.deleteCat': 'リスト「{name}」({count}件)を削除してもよろしいですか?', 'packing.defaultCategory': 'その他', + 'packing.dragCategory': 'ドラッグしてカテゴリを並べ替え', 'packing.toast.saveError': '保存に失敗しました', 'packing.toast.deleteError': '削除に失敗しました', 'packing.toast.renameError': '名前の変更に失敗しました', diff --git a/shared/src/i18n/ko/packing.ts b/shared/src/i18n/ko/packing.ts index 6adb5d95b..c38f6f209 100644 --- a/shared/src/i18n/ko/packing.ts +++ b/shared/src/i18n/ko/packing.ts @@ -75,6 +75,7 @@ const packing: TranslationStrings = { 'packing.confirm.clearChecked': '체크된 {count}개 항목을 제거할까요?', 'packing.confirm.deleteCat': '"{name}" 목록({count}개 항목)을 삭제하시겠습니까?', 'packing.defaultCategory': '기타', + 'packing.dragCategory': '드래그하여 카테고리 순서 변경', 'packing.toast.saveError': '저장 실패', 'packing.toast.deleteError': '삭제 실패', 'packing.toast.renameError': '이름 변경 실패', diff --git a/shared/src/i18n/nl/packing.ts b/shared/src/i18n/nl/packing.ts index b7595b6c1..11c583b94 100644 --- a/shared/src/i18n/nl/packing.ts +++ b/shared/src/i18n/nl/packing.ts @@ -83,6 +83,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Weet je zeker dat je de lijst "{name}" met {count} items wilt verwijderen?', 'packing.confirm.deleteCat.one': 'Weet je zeker dat je de lijst "{name}" met {count} item wilt verwijderen?', 'packing.defaultCategory': 'Overig', + 'packing.dragCategory': 'Sleep om de categorie te verplaatsen', 'packing.toast.saveError': 'Opslaan mislukt', 'packing.toast.deleteError': 'Verwijderen mislukt', 'packing.toast.renameError': 'Hernoemen mislukt', diff --git a/shared/src/i18n/pl/packing.ts b/shared/src/i18n/pl/packing.ts index d4ded8cd5..689cd1311 100644 --- a/shared/src/i18n/pl/packing.ts +++ b/shared/src/i18n/pl/packing.ts @@ -97,6 +97,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat.few': 'Czy na pewno chcesz usunąć listę „{name}” z {count} przedmiotami?', 'packing.confirm.deleteCat.many': 'Czy na pewno chcesz usunąć listę „{name}” z {count} przedmiotami?', 'packing.defaultCategory': 'Inne', + 'packing.dragCategory': 'Przeciągnij, aby zmienić kolejność kategorii', 'packing.toast.saveError': 'Nie udało się zapisać', 'packing.toast.deleteError': 'Nie udało się usunąć', 'packing.toast.renameError': 'Nie udało się zmienić nazwy', diff --git a/shared/src/i18n/ru/packing.ts b/shared/src/i18n/ru/packing.ts index d39521cd9..c905ebab7 100644 --- a/shared/src/i18n/ru/packing.ts +++ b/shared/src/i18n/ru/packing.ts @@ -97,6 +97,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat.few': 'Вы уверены, что хотите удалить список «{name}» с {count} предметами?', 'packing.confirm.deleteCat.many': 'Вы уверены, что хотите удалить список «{name}» с {count} предметами?', 'packing.defaultCategory': 'Другое', + 'packing.dragCategory': 'Перетащите, чтобы изменить порядок категории', 'packing.toast.saveError': 'Ошибка сохранения', 'packing.toast.deleteError': 'Ошибка удаления', 'packing.toast.renameError': 'Ошибка переименования', diff --git a/shared/src/i18n/sk/packing.ts b/shared/src/i18n/sk/packing.ts index 65fe574fe..d32d29abd 100644 --- a/shared/src/i18n/sk/packing.ts +++ b/shared/src/i18n/sk/packing.ts @@ -109,6 +109,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat.one': 'Naozaj chcete odstrániť zoznam „{name}“ s {count} položkou?', 'packing.confirm.deleteCat.few': 'Naozaj chcete odstrániť zoznam „{name}“ s {count} položkami?', 'packing.defaultCategory': 'Ostatné', + 'packing.dragCategory': 'Potiahnutím zmeníte poradie kategórie', 'packing.toast.saveError': 'Uloženie zlyhalo', 'packing.toast.deleteError': 'Odstránenie zlyhalo', 'packing.toast.renameError': 'Premenovanie zlyhalo', diff --git a/shared/src/i18n/sv/packing.ts b/shared/src/i18n/sv/packing.ts index 08f3402a6..c415d0494 100644 --- a/shared/src/i18n/sv/packing.ts +++ b/shared/src/i18n/sv/packing.ts @@ -83,6 +83,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': 'Är du säker på att du vill ta bort listan "{name}" med {count} föremål?', 'packing.confirm.deleteCat.one': 'Är du säker på att du vill ta bort listan "{name}" med {count} föremål?', 'packing.defaultCategory': 'Övrigt', + 'packing.dragCategory': 'Dra för att ändra kategorins ordning', 'packing.toast.saveError': 'Kunde inte spara', 'packing.toast.deleteError': 'Kunde inte ta bort', 'packing.toast.renameError': 'Kunde inte döpa om', diff --git a/shared/src/i18n/th/packing.ts b/shared/src/i18n/th/packing.ts index 74f2509d0..2e8516bca 100644 --- a/shared/src/i18n/th/packing.ts +++ b/shared/src/i18n/th/packing.ts @@ -93,6 +93,7 @@ const packing: TranslationStrings = { 'packing.confirm.clearChecked': 'คุณแน่ใจหรือไม่ว่าต้องการลบรายการที่ทำเครื่องหมายแล้ว {count} รายการ', 'packing.confirm.deleteCat': 'คุณแน่ใจหรือไม่ว่าต้องการลบรายการ "{name}" ที่มี {count} รายการ', 'packing.defaultCategory': 'อื่นๆ', + 'packing.dragCategory': 'ลากเพื่อจัดลำดับหมวดหมู่ใหม่', 'packing.toast.saveError': 'บันทึกไม่สำเร็จ', 'packing.toast.deleteError': 'ลบไม่สำเร็จ', 'packing.toast.renameError': 'เปลี่ยนชื่อไม่สำเร็จ', diff --git a/shared/src/i18n/tr/packing.ts b/shared/src/i18n/tr/packing.ts index d0266cb68..0b61e54d2 100644 --- a/shared/src/i18n/tr/packing.ts +++ b/shared/src/i18n/tr/packing.ts @@ -83,6 +83,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat': '"{name}" listesini {count} öğesiyle birlikte silmek istediğinizden emin misiniz?', 'packing.confirm.deleteCat.one': '"{name}" listesini {count} öğesiyle birlikte silmek istediğinizden emin misiniz?', 'packing.defaultCategory': 'Diğer', + 'packing.dragCategory': 'Kategoriyi yeniden sıralamak için sürükleyin', 'packing.toast.saveError': 'Kaydedilemedi', 'packing.toast.deleteError': 'Silinemedi', 'packing.toast.renameError': 'Yeniden adlandırılamadı', diff --git a/shared/src/i18n/uk/packing.ts b/shared/src/i18n/uk/packing.ts index 6b0ee2346..4c50c7847 100644 --- a/shared/src/i18n/uk/packing.ts +++ b/shared/src/i18n/uk/packing.ts @@ -97,6 +97,7 @@ const packing: TranslationStrings = { 'packing.confirm.deleteCat.few': 'Ви впевнені, що хочете видалити список «{name}» з {count} елементами?', 'packing.confirm.deleteCat.many': 'Ви впевнені, що хочете видалити список «{name}» з {count} елементами?', 'packing.defaultCategory': 'Інше', + 'packing.dragCategory': 'Перетягніть, щоб змінити порядок категорії', 'packing.toast.saveError': 'Помилка збереження', 'packing.toast.deleteError': 'Помилка видалення', 'packing.toast.renameError': 'Помилка перейменування', diff --git a/shared/src/i18n/vi/packing.ts b/shared/src/i18n/vi/packing.ts index 8b521b026..0d41fb413 100644 --- a/shared/src/i18n/vi/packing.ts +++ b/shared/src/i18n/vi/packing.ts @@ -76,6 +76,7 @@ const packing: TranslationStrings = { 'packing.confirm.clearChecked': 'Bạn có chắc chắn muốn xóa {count} mục đã chọn không?', 'packing.confirm.deleteCat': 'Bạn có chắc muốn xóa danh sách "{name}" với {count} mục không?', 'packing.defaultCategory': 'Khác', + 'packing.dragCategory': 'Kéo để sắp xếp lại danh mục', 'packing.toast.saveError': 'Không lưu được', 'packing.toast.deleteError': 'Không thể xóa', 'packing.toast.renameError': 'Không thể đổi tên', diff --git a/shared/src/i18n/zh-TW/packing.ts b/shared/src/i18n/zh-TW/packing.ts index b24bf7e6a..9821d03bd 100644 --- a/shared/src/i18n/zh-TW/packing.ts +++ b/shared/src/i18n/zh-TW/packing.ts @@ -75,6 +75,7 @@ const packing: TranslationStrings = { 'packing.confirm.clearChecked': '確定移除 {count} 個已勾選的物品?', 'packing.confirm.deleteCat': '確定要刪除清單「{name}」及其 {count} 個物品嗎?', 'packing.defaultCategory': '其他', + 'packing.dragCategory': '拖曳以重新排列類別', 'packing.toast.saveError': '儲存失敗', 'packing.toast.deleteError': '刪除失敗', 'packing.toast.renameError': '重新命名失敗', diff --git a/shared/src/i18n/zh/packing.ts b/shared/src/i18n/zh/packing.ts index ee88f1280..c8c43c7ae 100644 --- a/shared/src/i18n/zh/packing.ts +++ b/shared/src/i18n/zh/packing.ts @@ -75,6 +75,7 @@ const packing: TranslationStrings = { 'packing.confirm.clearChecked': '确定移除 {count} 个已勾选的物品?', 'packing.confirm.deleteCat': '确定要删除清单“{name}”及其 {count} 个物品吗?', 'packing.defaultCategory': '其他', + 'packing.dragCategory': '拖动以重新排列类别', 'packing.toast.saveError': '保存失败', 'packing.toast.deleteError': '删除失败', 'packing.toast.renameError': '重命名失败',