fix(packing): show who brings a shared item in a card with their pictures, and the others as a +N circle

The row's badges said who brings an item, who it is shared with and who
takes care of it in the browser's own tooltip. They now use TREK's, and
the bringers one lists everyone with their picture on a line of their
own. The others show as a +N circle tucked under the first picture.
Tooltip takes an optional content for tips that hold more than words.
This commit is contained in:
Maurice
2026-11-05 13:02:29 +01:00
parent b9c53d692b
commit c7d5fc12c6
33 changed files with 251 additions and 30 deletions
@@ -0,0 +1,57 @@
// FE-PACK-BRINGERCARD-001 to FE-PACK-BRINGERCARD-004
import { describe, expect, it } from 'vitest';
import { render, screen } from '../../../tests/helpers/render';
import { BringersCard, MoreBringers, bringersOf } from './PackingBringersCard';
const members = [
{ id: 1, username: 'anna', avatar_url: '/uploads/avatars/anna.jpg' },
{ id: 2, username: 'ben', avatar: '/uploads/avatars/ben.jpg' },
{ id: 3, username: 'carla' },
];
describe('bringersOf', () => {
it('FE-PACK-BRINGERCARD-001: lists the bringer first, then the others, each with the picture the trip knows', () => {
const item = {
owner_id: 1,
owner_username: 'anna',
contributors: [
{ user_id: 2, username: 'ben' },
{ user_id: 3, username: 'carla' },
],
};
expect(bringersOf(item, members)).toEqual([
{ name: 'anna', avatar: '/uploads/avatars/anna.jpg' },
{ name: 'ben', avatar: '/uploads/avatars/ben.jpg' },
{ name: 'carla', avatar: null },
]);
});
it('FE-PACK-BRINGERCARD-002: lists a person once and copes with a bringer the trip no longer has', () => {
const item = { owner_id: 9, owner_username: 'dora', contributors: [{ user_id: 9, username: 'dora' }] };
expect(bringersOf(item, members)).toEqual([{ name: 'dora', avatar: null }]);
expect(bringersOf({}, members)).toEqual([]);
});
});
describe('BringersCard', () => {
it('FE-PACK-BRINGERCARD-003: names everyone on a line of their own under the heading', () => {
render(
<BringersCard
bringers={bringersOf(
{ owner_id: 1, owner_username: 'anna', contributors: [{ user_id: 3, username: 'carla' }] },
members
)}
/>
);
const rows = screen.getAllByRole('listitem');
expect(rows).toHaveLength(2);
expect(rows[0]).toHaveTextContent('anna');
expect(rows[1]).toHaveTextContent('carla');
expect(screen.getByText('packing.bringersTitle')).toBeInTheDocument();
});
it('FE-PACK-BRINGERCARD-004: the "+N" circle counts the others', () => {
const { container } = render(<MoreBringers count={3} />);
expect(container).toHaveTextContent('+3');
});
});
@@ -0,0 +1,122 @@
import type { CSSProperties } from 'react';
import { useTranslation } from '../../i18n';
import type { TripMember } from './usePackingListPanel';
/** One person bringing a common item: the name, and the picture when the trip knows one. */
export interface Bringer {
name: string;
avatar: string | null;
}
/** The item's bringer first, then whoever also brings it, each once, with their picture from the trip's members. */
export function bringersOf(
item: {
owner_id?: number | null;
owner_username?: string | null;
contributors?: { user_id: number; username: string }[] | null;
},
members: readonly TripMember[]
): Bringer[] {
const avatarOf = (id: number | null | undefined) => {
const member = id == null ? undefined : members.find((m) => m.id === id);
return member?.avatar_url ?? member?.avatar ?? null;
};
const all = [
...(item.owner_username ? [{ id: item.owner_id ?? null, name: item.owner_username }] : []),
...(item.contributors ?? []).map((c) => ({ id: c.user_id, name: c.username })),
];
const bringers: Bringer[] = [];
for (const person of all) {
if (bringers.some((b) => b.name === person.name)) continue;
bringers.push({ name: person.name, avatar: avatarOf(person.id) });
}
return bringers;
}
const CIRCLE: CSSProperties = {
width: 20,
height: 20,
borderRadius: '50%',
flexShrink: 0,
display: 'grid',
placeItems: 'center',
fontSize: 'calc(9px * var(--fs-scale-caption, 1))',
fontWeight: 700,
};
/** A bringer's picture, or the first letter of the name on a colour taken from it. */
export function BringerAvatar({ bringer }: { bringer: Bringer }) {
if (bringer.avatar) {
return <img src={bringer.avatar} alt="" style={{ ...CIRCLE, objectFit: 'cover', display: 'block' }} />;
}
return (
<span
aria-hidden
style={{
...CIRCLE,
background: `hsl(${((bringer.name.codePointAt(0) ?? 0) * 37) % 360}, 55%, 55%)`, // theme-lint-disable: the colour comes from the bringer's name
color: 'white', // theme-lint-disable: the letter sits on the name's own colour
textTransform: 'uppercase',
}}
>
{bringer.name[0]}
</span>
);
}
/** The "+N" of the others bringing it, as a circle tucked under the first bringer's picture. */
export function MoreBringers({ count }: { count: number }) {
return (
<span
aria-hidden
style={{
...CIRCLE,
marginInlineStart: -6,
color: 'var(--text-muted)',
background: 'var(--bg-tertiary)',
boxShadow: '0 0 0 2px var(--bg-card)',
fontVariantNumeric: 'tabular-nums',
}}
>
+{count}
</span>
);
}
/** What the row's tooltip shows: everyone bringing the item, one per line with their picture. */
export function BringersCard({ bringers }: { bringers: readonly Bringer[] }) {
const { t } = useTranslation();
return (
<div style={{ minWidth: 150 }}>
<div
className="text-content-faint"
style={{
fontSize: 'calc(9.5px * var(--fs-scale-caption, 1))',
fontWeight: 700,
letterSpacing: '.06em',
textTransform: 'uppercase',
paddingBottom: 6,
}}
>
{t('packing.bringersTitle')}
</div>
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
{bringers.map((b, i) => (
<li
key={b.name}
className={i > 0 ? 'border-t border-edge-faint' : undefined}
style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' }}
>
<BringerAvatar bringer={b} />
<span
className="text-content"
style={{ fontSize: 'calc(12px * var(--fs-scale-body, 1))', fontWeight: 600 }}
>
{b.name}
</span>
</li>
))}
</ul>
</div>
);
}
@@ -19,6 +19,8 @@ import { POPOVER, POPOVER_CAPTION, POPOVER_DIVIDER } from './packingPopoverStyle
import type { TripMember } from './usePackingListPanel'
import { NumericInput } from '../shared/NumericInput'
import { packingBringerNames } from './packingBringers'
import { Tooltip } from '../shared/Tooltip'
import { BringerAvatar, BringersCard, MoreBringers, bringersOf } from './PackingBringersCard'
interface ArtikelZeileProps {
item: PackingItem
@@ -78,7 +80,6 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange: _onCa
const recipients = item.recipients || []
const sharedByMe = !!item.is_private && item.owner_id === currentUserId && recipients.length > 0
const broughtBy = !item.is_private && item.owner_username ? item.owner_username : null
const contributors = item.contributors || []
const canShare = canEdit && !isPlaceholder && !!onSetSharing
const handleToggle = () => togglePackingItem(tripId, item.id, !item.checked)
@@ -114,8 +115,7 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange: _onCa
// A multi-piece item counts its pieces into the bag (#2296).
const packed = packedOf(item)
const partlyPacked = !item.checked && packed > 0
const owner = tripMembers.find(m => m.id === item.owner_id)
const ownerAvatar = owner?.avatar_url ?? owner?.avatar ?? null
const bringers = bringersOf(item, tripMembers)
// At rest a row reads as what it holds (owner, a quantity above one, a
// weight, the bag) and everything else stays in place but dimmed: nothing
@@ -215,31 +215,29 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange: _onCa
{/* Sharing badges (#858 three-tier), as compact chips like the phone's
(#1525): the full sentence is the tooltip and the accessible name. */}
{!isPlaceholder && sharedToMe && (
<span className="packing-row-badge" title={t('packing.takenCareOf', { name: item.owner_username || '' })} aria-label={t('packing.takenCareOf', { name: item.owner_username || '' })}
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, width: 22, height: 22, borderRadius: 99, color: 'var(--accent)', background: 'color-mix(in srgb, var(--accent) 12%, transparent)' }}>
<HandHelping size={12} />
</span>
<Tooltip label={t('packing.takenCareOf', { name: item.owner_username || '' })}>
<span className="packing-row-badge" aria-label={t('packing.takenCareOf', { name: item.owner_username || '' })}
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, width: 22, height: 22, borderRadius: 99, color: 'var(--accent)', background: 'color-mix(in srgb, var(--accent) 12%, transparent)' }}>
<HandHelping size={12} />
</span>
</Tooltip>
)}
{!isPlaceholder && sharedByMe && (
<span className="packing-row-badge" title={`${t('packing.sharedWithCount', { count: recipients.length })}: ${recipients.map(r => r.username).join(', ')}`} aria-label={t('packing.sharedWithCount', { count: recipients.length })}
style={{ display: 'inline-flex', alignItems: 'center', gap: 3, flexShrink: 0, height: 22, padding: '0 7px', borderRadius: 99, fontSize: 'calc(10px * var(--fs-scale-caption, 1))', fontWeight: 700, color: 'var(--text-muted)', background: 'var(--bg-tertiary)' }}>
<UserRound size={11} /> {recipients.length}
</span>
<Tooltip label={`${t('packing.sharedWithCount', { count: recipients.length })}: ${recipients.map(r => r.username).join(', ')}`}>
<span className="packing-row-badge" aria-label={t('packing.sharedWithCount', { count: recipients.length })}
style={{ display: 'inline-flex', alignItems: 'center', gap: 3, flexShrink: 0, height: 22, padding: '0 7px', borderRadius: 99, fontSize: 'calc(10px * var(--fs-scale-caption, 1))', fontWeight: 700, color: 'var(--text-muted)', background: 'var(--bg-tertiary)' }}>
<UserRound size={11} /> {recipients.length}
</span>
</Tooltip>
)}
{!isPlaceholder && broughtBy && (
<span className="packing-row-badge" title={t('packing.broughtBy', { name: packingBringerNames(item) })} aria-label={t('packing.broughtBy', { name: packingBringerNames(item) })}
style={{ display: 'inline-flex', alignItems: 'center', gap: 3, flexShrink: 0 }}>
{ownerAvatar
? <img src={ownerAvatar} alt="" style={{ width: 20, height: 20, borderRadius: '50%', objectFit: 'cover', display: 'block' }} />
: (
<span aria-hidden style={{
width: 20, height: 20, borderRadius: '50%', display: 'grid', placeItems: 'center',
background: `hsl(${(broughtBy.codePointAt(0) ?? 0) * 37 % 360}, 55%, 55%)`, // theme-lint-disable: the colour comes from the bringer's name
color: 'white', fontSize: 'calc(9px * var(--fs-scale-caption, 1))', fontWeight: 700, textTransform: 'uppercase',
}}>{broughtBy[0]}</span>
)}
{contributors.length > 0 && <span style={{ fontSize: 'calc(10px * var(--fs-scale-caption, 1))', fontWeight: 700, color: 'var(--text-faint)' }}>+{contributors.length}</span>}
</span>
<Tooltip label={t('packing.broughtBy', { name: packingBringerNames(item) })} content={<BringersCard bringers={bringers} />}>
<span className="packing-row-badge" aria-label={t('packing.broughtBy', { name: packingBringerNames(item) })}
style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}>
<BringerAvatar bringer={bringers[0]} />
{bringers.length > 1 && <MoreBringers count={bringers.length - 1} />}
</span>
</Tooltip>
)}
<div className="packing-row-inline-actions" style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
+12 -1
View File
@@ -1,4 +1,4 @@
// FE-W4TIP-001 to FE-W4TIP-017
// FE-W4TIP-001 to FE-W4TIP-018
import { createRef, useRef } from 'react'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, fireEvent, act } from '../../../tests/helpers/render'
@@ -213,3 +213,14 @@ describe('Tooltip — with a shortcut', () => {
expect(screen.getByRole('tooltip').querySelector('kbd')).toBeNull()
})
})
describe('Tooltip — with content', () => {
it('FE-W4TIP-018: shows the content in place of the label, which still opens it', () => {
render(<Tooltip label="brought by Anna, Ben" content={<ul><li>Anna</li><li>Ben</li></ul>}><button>x</button></Tooltip>)
hover(screen.getByRole('button'))
const tip = screen.getByRole('tooltip')
expect(tip.querySelectorAll('li')).toHaveLength(2)
expect(tip).not.toHaveTextContent('brought by')
})
})
+10 -5
View File
@@ -11,10 +11,15 @@ interface TooltipProps {
disabled?: boolean
/** The keys that do the same, shown beside the label: ["⌘", "K"]. */
shortcut?: readonly string[]
/**
* Shown in place of the label when the tip holds more than words, such as a list of
* people with their pictures. `label` still names it and decides whether it opens.
*/
content?: React.ReactNode
children: React.ReactElement
}
export function Tooltip({ label, placement = 'bottom', delay = 250, disabled, shortcut, children }: TooltipProps) {
export function Tooltip({ label, placement = 'bottom', delay = 250, disabled, shortcut, content, children }: TooltipProps) {
const [open, setOpen] = useState(false)
const [coords, setCoords] = useState<{ top: number; left: number } | null>(null)
const triggerRef = useRef<HTMLElement | null>(null)
@@ -93,8 +98,8 @@ export function Tooltip({ label, placement = 'bottom', delay = 250, disabled, sh
zIndex: 100000,
fontSize: 'calc(11px * var(--fs-scale-caption, 1))',
fontWeight: 500,
padding: '5px 10px',
borderRadius: 8,
padding: content ? '8px 10px' : '5px 10px',
borderRadius: content ? 10 : 8,
// A label of several lines (the paddle check bench) breaks at its lines and wraps within the window.
whiteSpace: label.includes('\n') ? 'pre-line' : 'nowrap',
maxWidth: label.includes('\n') ? 'min(22rem, calc(100vw - 12px))' : undefined,
@@ -103,12 +108,12 @@ export function Tooltip({ label, placement = 'bottom', delay = 250, disabled, sh
transformOrigin: placement === 'top' ? 'bottom center' : placement === 'bottom' ? 'top center' : placement === 'left' ? 'center right' : 'center left',
}}
>
{shortcut && shortcut.length > 0 ? (
{content ?? (shortcut && shortcut.length > 0 ? (
<span className="inline-flex items-center gap-2">
{label}
<Kbd keys={shortcut} />
</span>
) : label}
) : label)}
</div>,
document.body,
)}
+1
View File
@@ -135,6 +135,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount.few': 'مشترك مع {count} أشخاص',
'packing.sharedWithCount.many': 'مشترك مع {count} شخصًا',
'packing.broughtBy': 'يحضره {name}',
'packing.bringersTitle': 'يحضره',
'packing.alsoBring': 'يمكنني إحضاره أيضًا',
'packing.alsoBringingStop': 'لن أحضره',
'packing.cloneToMine': 'نسخ إلى قائمتي',
+1
View File
@@ -105,6 +105,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': '{count} nəfərlə paylaşılıb',
'packing.sharedWithCount.one': '{count} nəfərlə paylaşılıb',
'packing.broughtBy': '{name} gətirir',
'packing.bringersTitle': 'Gətirənlər',
'packing.alsoBring': 'Mən də bunu gətirə bilərəm',
'packing.alsoBringingStop': 'Mən bunu gətirməyəcəyəm',
'packing.cloneToMine': 'Mənim siyahıma köçür',
+1
View File
@@ -102,6 +102,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'compartilhado com {count}',
'packing.sharedWithCount.one': 'compartilhado com {count}',
'packing.broughtBy': 'trazido por {name}',
'packing.bringersTitle': 'Quem leva',
'packing.alsoBring': 'Eu também posso levar',
'packing.alsoBringingStop': 'Não vou levar',
'packing.cloneToMine': 'Copiar para minha lista',
+1
View File
@@ -91,6 +91,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'compartit amb {count}',
'packing.sharedWithCount.one': 'compartit amb {count}',
'packing.broughtBy': 'portat per {name}',
'packing.bringersTitle': 'Ho porta',
'packing.alsoBring': 'Jo també puc portar això',
'packing.alsoBringingStop': 'Ja no ho porto',
'packing.cloneToMine': 'Copiar a la meva llista',
+1
View File
@@ -111,6 +111,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount.one': 'sdíleno s {count} osobou',
'packing.sharedWithCount.few': 'sdíleno s {count} osobami',
'packing.broughtBy': 'přináší {name}',
'packing.bringersTitle': 'Přinese',
'packing.alsoBring': 'Můžu to vzít taky',
'packing.alsoBringingStop': 'Neberu to',
'packing.cloneToMine': 'Kopírovat do mého seznamu',
+1
View File
@@ -94,6 +94,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'geteilt mit {count}',
'packing.sharedWithCount.one': 'geteilt mit {count}',
'packing.broughtBy': 'bringt {name}',
'packing.bringersTitle': 'Bringt mit',
'packing.alsoBring': 'Bring ich auch mit',
'packing.alsoBringingStop': 'Bring ich doch nicht mit',
'packing.cloneToMine': 'In meine Liste kopieren',
+1
View File
@@ -93,6 +93,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'shared with {count}',
'packing.sharedWithCount.one': 'shared with {count}',
'packing.broughtBy': 'brought by {name}',
'packing.bringersTitle': 'Brought by',
'packing.alsoBring': 'I can bring that too',
'packing.alsoBringingStop': "I'm not bringing it",
'packing.cloneToMine': 'Copy to my list',
+1
View File
@@ -103,6 +103,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'compartido con {count}',
'packing.sharedWithCount.one': 'compartido con {count}',
'packing.broughtBy': 'lo trae {name}',
'packing.bringersTitle': 'Lo lleva',
'packing.alsoBring': 'Yo también puedo llevarlo',
'packing.alsoBringingStop': 'No lo llevo',
'packing.cloneToMine': 'Copiar a mi lista',
+1
View File
@@ -93,6 +93,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'jagatud {count} inimesega',
'packing.sharedWithCount.one': 'jagatud {count} inimesega',
'packing.broughtBy': 'toob {name}',
'packing.bringersTitle': 'Toob kaasa',
'packing.alsoBring': 'Võin selle ka kaasa võtta',
'packing.alsoBringingStop': 'Ma ei võta seda kaasa',
'packing.cloneToMine': 'Kopeeri minu nimekirja',
+1
View File
@@ -103,6 +103,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'partagé avec {count}',
'packing.sharedWithCount.one': 'partagé avec {count}',
'packing.broughtBy': 'apporté par {name}',
'packing.bringersTitle': 'Apporté par',
'packing.alsoBring': "Je peux l'apporter aussi",
'packing.alsoBringingStop': "Je ne l'apporte pas",
'packing.cloneToMine': 'Copier dans ma liste',
+1
View File
@@ -104,6 +104,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'κοινό με {count} άτομα',
'packing.sharedWithCount.one': 'κοινό με {count} άτομο',
'packing.broughtBy': 'φέρνει ο/η {name}',
'packing.bringersTitle': 'Το φέρνει',
'packing.alsoBring': 'Μπορώ να το φέρω κι εγώ',
'packing.alsoBringingStop': 'Δεν το φέρνω',
'packing.cloneToMine': 'Αντιγραφή στη λίστα μου',
+1
View File
@@ -99,6 +99,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount.one': 'משותף עם אדם אחד',
'packing.sharedWithCount.two': 'משותף עם שני אנשים',
'packing.broughtBy': '{name} מביא/ה',
'packing.bringersTitle': 'מביאים',
'packing.alsoBring': 'גם אני יכול/ה להביא',
'packing.alsoBringingStop': 'אני לא מביא/ה',
'packing.cloneToMine': 'העתקה לרשימה שלי',
+1
View File
@@ -105,6 +105,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'megosztva {count} fővel',
'packing.sharedWithCount.one': 'megosztva {count} fővel',
'packing.broughtBy': 'hozza: {name}',
'packing.bringersTitle': 'Hozza',
'packing.alsoBring': 'Én is tudom hozni',
'packing.alsoBringingStop': 'Mégsem hozom',
'packing.cloneToMine': 'Másolás a listámra',
+1
View File
@@ -95,6 +95,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': 'oleh {name}',
'packing.sharedWithCount': 'dibagikan dengan {count}',
'packing.broughtBy': 'dibawa oleh {name}',
'packing.bringersTitle': 'Dibawa oleh',
'packing.alsoBring': 'Saya juga bisa membawanya',
'packing.alsoBringingStop': 'Saya tidak membawanya',
'packing.cloneToMine': 'Salin ke daftar saya',
+1
View File
@@ -103,6 +103,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'condiviso con {count}',
'packing.sharedWithCount.one': 'condiviso con {count}',
'packing.broughtBy': 'lo porta {name}',
'packing.bringersTitle': 'Lo porta',
'packing.alsoBring': "Posso portarlo anch'io",
'packing.alsoBringingStop': 'Non lo porto',
'packing.cloneToMine': 'Copia nella mia lista',
+1
View File
@@ -94,6 +94,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': '{name} さん担当',
'packing.sharedWithCount': '{count} 人と共有',
'packing.broughtBy': '{name} さんが持参',
'packing.bringersTitle': '持っていく人',
'packing.alsoBring': '私も持っていけます',
'packing.alsoBringingStop': 'やっぱり持っていきません',
'packing.cloneToMine': 'マイリストにコピー',
+1
View File
@@ -94,6 +94,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': '{name} 담당',
'packing.sharedWithCount': '{count}명과 공유',
'packing.broughtBy': '{name}님이 가져옴',
'packing.bringersTitle': '가져오는 사람',
'packing.alsoBring': '저도 가져갈 수 있어요',
'packing.alsoBringingStop': '안 가져갈게요',
'packing.cloneToMine': '내 목록으로 복사',
+1
View File
@@ -103,6 +103,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'gedeeld met {count}',
'packing.sharedWithCount.one': 'gedeeld met {count}',
'packing.broughtBy': 'meegenomen door {name}',
'packing.bringersTitle': 'Neemt mee',
'packing.alsoBring': 'Ik kan het ook meenemen',
'packing.alsoBringingStop': 'Ik neem het niet mee',
'packing.cloneToMine': 'Kopiëren naar mijn lijst',
+1
View File
@@ -119,6 +119,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount.few': 'udostępniono {count} osobom',
'packing.sharedWithCount.many': 'udostępniono {count} osobom',
'packing.broughtBy': 'przynosi {name}',
'packing.bringersTitle': 'Przyniesie',
'packing.alsoBring': 'Ja też mogę to wziąć',
'packing.alsoBringingStop': 'Jednak nie biorę',
'packing.cloneToMine': 'Kopiuj do mojej listy',
+1
View File
@@ -119,6 +119,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount.few': 'доступно {count} участникам',
'packing.sharedWithCount.many': 'доступно {count} участникам',
'packing.broughtBy': 'приносит {name}',
'packing.bringersTitle': 'Возьмёт',
'packing.alsoBring': 'Я тоже могу принести',
'packing.alsoBringingStop': 'Я не принесу',
'packing.cloneToMine': 'Копировать в мой список',
+1
View File
@@ -99,6 +99,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount.one': 'zdieľané s {count} osobou',
'packing.sharedWithCount.few': 'zdieľané s {count} osobami',
'packing.broughtBy': 'prináša {name}',
'packing.bringersTitle': 'Prinesie',
'packing.alsoBring': 'Môžem to vziať tiež',
'packing.alsoBringingStop': 'Neberiem to',
'packing.cloneToMine': 'Kopírovať do môjho zoznamu',
+1
View File
@@ -103,6 +103,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': 'delat med {count}',
'packing.sharedWithCount.one': 'delat med {count}',
'packing.broughtBy': 'tas med av {name}',
'packing.bringersTitle': 'Tar med',
'packing.alsoBring': 'Jag kan ta med det också',
'packing.alsoBringingStop': 'Jag tar inte med det',
'packing.cloneToMine': 'Kopiera till min lista',
+1
View File
@@ -87,6 +87,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': 'โดย {name}',
'packing.sharedWithCount': 'แชร์กับ {count}',
'packing.broughtBy': '{name} จะนำมา',
'packing.bringersTitle': 'ผู้นำมา',
'packing.alsoBring': 'ฉันก็สามารถนำสิ่งนั้นมาได้เช่นกัน',
'packing.alsoBringingStop': 'ฉันไม่เอามันมา',
'packing.cloneToMine': 'คัดลอกไปยังรายการของฉัน',
+1
View File
@@ -103,6 +103,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount': '{count} kişiyle paylaşıldı',
'packing.sharedWithCount.one': '{count} kişiyle paylaşıldı',
'packing.broughtBy': '{name} getiriyor',
'packing.bringersTitle': 'Getiren',
'packing.alsoBring': 'Ben de getirebilirim',
'packing.alsoBringingStop': 'Getirmiyorum',
'packing.cloneToMine': 'Listeme kopyala',
+1
View File
@@ -119,6 +119,7 @@ const packing: TranslationStrings = {
'packing.sharedWithCount.few': 'надано {count} учасникам',
'packing.sharedWithCount.many': 'надано {count} учасникам',
'packing.broughtBy': 'приносить {name}',
'packing.bringersTitle': 'Візьме',
'packing.alsoBring': 'Я теж можу принести',
'packing.alsoBringingStop': 'Я не принесу',
'packing.cloneToMine': 'Копіювати до мого списку',
+1
View File
@@ -95,6 +95,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': 'bởi {name}',
'packing.sharedWithCount': 'chia sẻ với {count}',
'packing.broughtBy': '{name} mang',
'packing.bringersTitle': 'Người mang',
'packing.alsoBring': 'Tôi cũng có thể mang',
'packing.alsoBringingStop': 'Tôi không mang',
'packing.cloneToMine': 'Sao chép vào danh sách của tôi',
+1
View File
@@ -94,6 +94,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': '由 {name} 負責',
'packing.sharedWithCount': '已共用給 {count} 人',
'packing.broughtBy': '由 {name} 帶',
'packing.bringersTitle': '攜帶者',
'packing.alsoBring': '我也可以帶',
'packing.alsoBringingStop': '我不帶了',
'packing.cloneToMine': '複製到我的清單',
+1
View File
@@ -94,6 +94,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': '由 {name} 负责',
'packing.sharedWithCount': '已共享给 {count} 人',
'packing.broughtBy': '由 {name} 带',
'packing.bringersTitle': '携带者',
'packing.alsoBring': '我也可以带',
'packing.alsoBringingStop': '我不带了',
'packing.cloneToMine': '复制到我的清单',