feat(packing): print, PDF and Markdown/CSV export of packing lists
The packing list bar gets an export menu next to the import button, both now icon only. The menu prints the open view as a TREK styled page (trip, dates, packed count, one card per category, bags), which the browser can also save as a PDF, and downloads it as a Markdown checklist or as a CSV in the exact format the import reads, bags included, so a list works as a personal template without admin rights. The import now also reads Markdown checklists: headings become categories, "- [x]" marks an item packed, a "3x" prefix sets the quantity and a "(200 g)" suffix the weight. The phone action sheet offers the same exports through the shared usePackingExport hook. The help center gets an export guide with new pictures, the import guide and the wiki page describe the Markdown format, in all 27 languages. Addresses discussions #1420 and #875.
@@ -75,6 +75,10 @@ const shareOverlay = (page: Page): Locator => page.locator('div[role="presentati
|
||||
/** The bulk-import dialog: its own portal, with none of the shared backdrop classes. */
|
||||
const importCard = (page: Page): Locator =>
|
||||
page.getByText('Import Packing List', { exact: true }).locator('xpath=..')
|
||||
/** The Export button's menu: print, Markdown and CSV. */
|
||||
const exportMenu = (page: Page): Locator => page.getByRole('menu')
|
||||
/** The printable page inside the preview, a srcdoc frame of its own. */
|
||||
const printFrame = (page: Page) => page.frameLocator('iframe[title^="Packing List"]')
|
||||
/** The bags column. Total weight is a span two divs inside it, under the rule. */
|
||||
const bagSidebar = (page: Page): Locator =>
|
||||
page.getByText('Total weight', { exact: true }).locator('xpath=ancestor::div[3]')
|
||||
@@ -367,7 +371,7 @@ const SCRIPTS: Record<string, GuideScript> = {
|
||||
},
|
||||
// Clicking it would open the operating system's file chooser, which is
|
||||
// not part of the picture; the button is shown, not pressed.
|
||||
only(p => importCard(p).getByRole('button', { name: 'Load CSV/TXT' })),
|
||||
only(p => importCard(p).getByRole('button', { name: 'Load CSV/TXT/MD' })),
|
||||
{
|
||||
target: p => importCard(p).getByRole('button', { name: /^Import \d+$/ }),
|
||||
act: async p => {
|
||||
@@ -380,6 +384,39 @@ const SCRIPTS: Record<string, GuideScript> = {
|
||||
],
|
||||
cleanup: p => deleteItems(p, row => IMPORTED.includes(row.name)),
|
||||
},
|
||||
'export-packing-list': {
|
||||
guide: guide('export-packing-list'),
|
||||
start: openLists,
|
||||
steps: [
|
||||
{
|
||||
target: p => p.getByRole('button', { name: 'Export', exact: true }),
|
||||
act: async p => {
|
||||
await p.getByRole('button', { name: 'Export', exact: true }).click()
|
||||
await expect(exportMenu(p)).toBeVisible()
|
||||
await settle(p)
|
||||
},
|
||||
},
|
||||
// The two file entries download at once; the step shows them, it does not press them.
|
||||
only(exportMenu),
|
||||
{
|
||||
target: p => exportMenu(p).getByRole('menuitem', { name: 'Print or save as PDF' }),
|
||||
act: async p => {
|
||||
await exportMenu(p).getByRole('menuitem', { name: 'Print or save as PDF' }).click()
|
||||
await expect(printFrame(p).locator('h1')).toBeVisible({ timeout: 15_000 })
|
||||
// The page carries the app's own Poppins; the result picture waits for it.
|
||||
await printFrame(p).locator('body').evaluate(async () => { await document.fonts.ready })
|
||||
await settle(p)
|
||||
},
|
||||
},
|
||||
// Pressing it opens the operating system's print dialog, which is not part
|
||||
// of the picture; the button is shown, not pressed. The result picture is the
|
||||
// preview itself.
|
||||
only(p => p.getByRole('button', { name: 'Print or save as PDF' })),
|
||||
],
|
||||
cleanup: async p => {
|
||||
await p.getByRole('button', { name: 'Close', exact: true }).click()
|
||||
},
|
||||
},
|
||||
'share-packing-item': {
|
||||
guide: guide('share-packing-item'),
|
||||
start: openLists,
|
||||
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
@@ -27,7 +27,7 @@ if (process.platform === 'win32' && SRC.startsWith('/')) SRC = SRC.slice(1);
|
||||
const EXEMPT = [
|
||||
/Mapbox/i, /placePopup/i, /marker/i, /popup/i, /TripPDF/, /JourneyBookPDF/,
|
||||
/MapViewGL/, /MapView\./, /JourneyMapGL/, /reservationsMapbox/, /useAtlas/,
|
||||
/ReservationOverlay/, /\.test\./, /\.spec\./,
|
||||
/ReservationOverlay/, /packingPrintDocument/, /\.test\./, /\.spec\./,
|
||||
];
|
||||
|
||||
const ARB_CLASS = /\b(?:bg|text|border|ring|fill|stroke|from|via|to|shadow|outline|decoration|divide|caret)-\[\s*(?:#|rgba?\(|hsla?\(|oklch\()/;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// FE-PACKEXPORTMENU-001 to FE-PACKEXPORTMENU-006
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { fireEvent, render, screen } from '../../../tests/helpers/render'
|
||||
import PackingExportMenu from './PackingExportMenu'
|
||||
import PackingPrintPreview from './PackingPrintPreview'
|
||||
import type { PackingExport } from './usePackingExport'
|
||||
|
||||
const packingExport: PackingExport = {
|
||||
hasItems: true,
|
||||
exportMarkdown: vi.fn(),
|
||||
exportCsv: vi.fn(async () => {}),
|
||||
openPrint: vi.fn(async () => {}),
|
||||
printHtml: null,
|
||||
printTitle: 'Packing List: Lisbon & Porto',
|
||||
closePrint: vi.fn(),
|
||||
}
|
||||
vi.mock('./usePackingExport', () => ({ usePackingExport: () => packingExport }))
|
||||
|
||||
const renderMenu = () => render(<PackingExportMenu tripId={7} view="common" className="btn" style={{}} />)
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
packingExport.hasItems = true
|
||||
packingExport.printHtml = null
|
||||
})
|
||||
|
||||
describe('PackingExportMenu', () => {
|
||||
it('FE-PACKEXPORTMENU-001: renders nothing while the view has no items', () => {
|
||||
packingExport.hasItems = false
|
||||
const { container } = renderMenu()
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTMENU-002: is an icon button named Export that opens a menu of three ways out', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderMenu()
|
||||
const button = screen.getByRole('button', { name: 'Export' })
|
||||
expect(button).toHaveAttribute('title', 'Export')
|
||||
expect(button).toHaveAttribute('aria-expanded', 'false')
|
||||
await user.click(button)
|
||||
expect(button).toHaveAttribute('aria-expanded', 'true')
|
||||
expect(screen.getAllByRole('menuitem').map(i => i.textContent)).toEqual([
|
||||
'Print or save as PDF', 'Markdown checklist (.md)', 'CSV for import (.csv)',
|
||||
])
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTMENU-003: runs the chosen export and closes the menu', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderMenu()
|
||||
for (const [label, action] of [
|
||||
['Print or save as PDF', packingExport.openPrint],
|
||||
['Markdown checklist (.md)', packingExport.exportMarkdown],
|
||||
['CSV for import (.csv)', packingExport.exportCsv],
|
||||
] as const) {
|
||||
await user.click(screen.getByRole('button', { name: 'Export' }))
|
||||
await user.click(screen.getByRole('menuitem', { name: label }))
|
||||
expect(action).toHaveBeenCalledTimes(1)
|
||||
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
|
||||
}
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTMENU-004: closes on Escape and on a click anywhere else', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderMenu()
|
||||
await user.click(screen.getByRole('button', { name: 'Export' }))
|
||||
fireEvent.keyDown(document, { key: 'Escape' })
|
||||
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
|
||||
await user.click(screen.getByRole('button', { name: 'Export' }))
|
||||
fireEvent.mouseDown(document.body)
|
||||
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTMENU-005: keeps the preview open even if the view has been emptied meanwhile', () => {
|
||||
packingExport.hasItems = false
|
||||
packingExport.printHtml = '<html><body><h1>Lisbon</h1></body></html>'
|
||||
renderMenu()
|
||||
expect(screen.getByTitle('Packing List: Lisbon & Porto')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('PackingPrintPreview', () => {
|
||||
it('FE-PACKEXPORTMENU-006: shows the page in a frame, prints that frame, and closes', async () => {
|
||||
const user = userEvent.setup()
|
||||
const onClose = vi.fn()
|
||||
const { rerender } = render(<PackingPrintPreview html={null} title="Packing List" onClose={onClose} />)
|
||||
expect(screen.queryByTitle('Packing List')).not.toBeInTheDocument()
|
||||
|
||||
rerender(<PackingPrintPreview html="<html><body>List</body></html>" title="Packing List" onClose={onClose} />)
|
||||
const frame = screen.getByTitle('Packing List') as HTMLIFrameElement
|
||||
expect(frame.getAttribute('srcdoc')).toBe('<html><body>List</body></html>')
|
||||
expect(frame.getAttribute('sandbox')).toBe('allow-same-origin allow-modals')
|
||||
const print = vi.fn()
|
||||
Object.defineProperty(frame, 'contentWindow', { value: { print }, configurable: true })
|
||||
await user.click(screen.getByRole('button', { name: 'Print or save as PDF' }))
|
||||
expect(print).toHaveBeenCalledTimes(1)
|
||||
await user.click(screen.getByRole('button', { name: 'Close' }))
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,89 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import { FileSpreadsheet, FileText, Printer, Upload } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import PackingPrintPreview from './PackingPrintPreview'
|
||||
import { usePackingExport, type PackingView } from './usePackingExport'
|
||||
|
||||
interface PackingExportMenuProps {
|
||||
tripId: number
|
||||
view: PackingView
|
||||
style: React.CSSProperties
|
||||
className?: string
|
||||
}
|
||||
|
||||
function MenuItem({ icon: Icon, label, onClick }: { icon: LucideIcon; label: string; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={onClick}
|
||||
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-caption font-medium text-content hover:bg-surface-tertiary"
|
||||
>
|
||||
<Icon size={13} className="text-content-faint" />
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The list's way out, beside Import in the Lists header (#875, #1420): print or save
|
||||
* as PDF, a Markdown checklist, or CSV in the import's own format. Covers the view on
|
||||
* screen and renders nothing while that view is empty.
|
||||
*/
|
||||
export default function PackingExportMenu({ tripId, view, style, className }: PackingExportMenuProps): React.ReactElement | null {
|
||||
const { t } = useTranslation()
|
||||
const packingExport = usePackingExport(tripId, view)
|
||||
const [open, setOpen] = useState(false)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
const onPointer = (e: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) setOpen(false)
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false) }
|
||||
document.addEventListener('mousedown', onPointer)
|
||||
document.addEventListener('keydown', onKey)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onPointer)
|
||||
document.removeEventListener('keydown', onKey)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
if (!packingExport.hasItems && !packingExport.printHtml) return null
|
||||
|
||||
const choose = (action: () => void | Promise<void>) => () => {
|
||||
setOpen(false)
|
||||
void action()
|
||||
}
|
||||
|
||||
return (
|
||||
// A flex box, so the button stretches to the row height like its neighbours in the header.
|
||||
<div ref={menuRef} className="relative flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(v => !v)}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
aria-label={t('packing.export')}
|
||||
title={t('packing.export')}
|
||||
className={className ?? 'hover:opacity-[0.88]'}
|
||||
style={style}
|
||||
>
|
||||
<Upload size={14} strokeWidth={2.5} />
|
||||
</button>
|
||||
{open && (
|
||||
<div
|
||||
role="menu"
|
||||
className="trek-menu-enter absolute right-0 top-full z-50 mt-1.5 min-w-[220px] origin-top-right rounded-[10px] border border-edge bg-surface-card p-1 shadow-dropdown"
|
||||
>
|
||||
<MenuItem icon={Printer} label={t('packing.exportPrint')} onClick={choose(packingExport.openPrint)} />
|
||||
<MenuItem icon={FileText} label={t('packing.exportMarkdown')} onClick={choose(packingExport.exportMarkdown)} />
|
||||
<MenuItem icon={FileSpreadsheet} label={t('packing.exportCsv')} onClick={choose(packingExport.exportCsv)} />
|
||||
</div>
|
||||
)}
|
||||
<PackingPrintPreview html={packingExport.printHtml} title={packingExport.printTitle} onClose={packingExport.closePrint} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -546,8 +546,8 @@ describe('PackingListPanel', () => {
|
||||
const textarea = screen.getByPlaceholderText(/Hygiene, Toothbrush/);
|
||||
expect(textarea).toBeInTheDocument();
|
||||
|
||||
// "Load CSV/TXT" button is present inside the modal
|
||||
expect(screen.getByText('Load CSV/TXT')).toBeInTheDocument();
|
||||
// "Load CSV/TXT/MD" button is present inside the modal
|
||||
expect(screen.getByText('Load CSV/TXT/MD')).toBeInTheDocument();
|
||||
|
||||
// Close by clicking backdrop (covers the onClick on the backdrop div)
|
||||
const modalTitle = screen.getByText('Import Packing List');
|
||||
@@ -1496,7 +1496,21 @@ describe('PackingListPanel', () => {
|
||||
await waitFor(() => expect(createBody).toMatchObject({ name: 'New Bag' }));
|
||||
});
|
||||
|
||||
it('FE-COMP-PACKING-069: Load CSV/TXT button clicks the hidden file input', async () => {
|
||||
it('FE-COMP-PACKING-082: the import dialog explains the Markdown form, takes .md files and counts a pasted checklist (#875)', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<PackingListPanel tripId={1} items={[]} />);
|
||||
await user.click(container.querySelector('svg.lucide-download')!.closest('button')!);
|
||||
await screen.findByText('Import Packing List');
|
||||
|
||||
expect(screen.getByText(/A Markdown list works too/)).toBeInTheDocument();
|
||||
expect(document.querySelector('input[type="file"]')).toHaveAttribute('accept', '.csv,.txt,.md,.markdown,text/markdown');
|
||||
fireEvent.change(screen.getByPlaceholderText(/Hygiene, Toothbrush/), {
|
||||
target: { value: '# Packing List\n## Clothing\n- [x] 3x Socks\n- [ ] Rain jacket (350 g)\nA note' },
|
||||
});
|
||||
expect(screen.getByRole('button', { name: 'Import 2' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-COMP-PACKING-069: Load CSV/TXT/MD button clicks the hidden file input', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<PackingListPanel tripId={1} items={[]} />);
|
||||
|
||||
@@ -1510,8 +1524,8 @@ describe('PackingListPanel', () => {
|
||||
expect(fileInput).toBeTruthy();
|
||||
const clickSpy = vi.spyOn(fileInput, 'click').mockImplementation(() => {});
|
||||
|
||||
// Click the "Load CSV/TXT" button
|
||||
await user.click(screen.getByText('Load CSV/TXT'));
|
||||
// Click the "Load CSV/TXT/MD" button
|
||||
await user.click(screen.getByText('Load CSV/TXT/MD'));
|
||||
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
clickSpy.mockRestore();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { FileDown } from 'lucide-react'
|
||||
import type { PackingState } from './usePackingListPanel'
|
||||
import { PACKING_IMPORT_ACCEPT } from './packingListPanel.constants'
|
||||
|
||||
export function BulkImportModal(S: PackingState) {
|
||||
const { setShowImportModal, t, importText, setImportText, csvInputRef, handleCsvFile, handleBulkImport, parseImportLines } = S
|
||||
@@ -16,7 +17,11 @@ export function BulkImportModal(S: PackingState) {
|
||||
display: 'flex', flexDirection: 'column', gap: 14,
|
||||
}}>
|
||||
<div style={{ fontSize: 'calc(15px * var(--fs-scale-subtitle, 1))', fontWeight: 600, color: 'var(--text-primary)' }}>{t('packing.importTitle')}</div>
|
||||
<div style={{ fontSize: 'calc(12px * var(--fs-scale-body, 1))', color: 'var(--text-faint)', lineHeight: 1.5 }}>{t('packing.importHint')}</div>
|
||||
<div style={{ fontSize: 'calc(12px * var(--fs-scale-body, 1))', color: 'var(--text-faint)', lineHeight: 1.5 }}>
|
||||
{t('packing.importHint')}
|
||||
<br />
|
||||
{t('packing.importHintMarkdown')}
|
||||
</div>
|
||||
<div style={{ display: 'flex', border: '1px solid var(--border-primary)', borderRadius: 10, overflow: 'hidden', background: 'var(--bg-input)' }}>
|
||||
<div style={{
|
||||
padding: '10px 0', fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontFamily: 'monospace', lineHeight: 1.5,
|
||||
@@ -42,7 +47,7 @@ export function BulkImportModal(S: PackingState) {
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<input ref={csvInputRef} type="file" accept=".csv,.txt" style={{ display: 'none' }} onChange={handleCsvFile} />
|
||||
<input ref={csvInputRef} type="file" accept={PACKING_IMPORT_ACCEPT} style={{ display: 'none' }} onChange={handleCsvFile} />
|
||||
<button type="button" onClick={() => csvInputRef.current?.click()} style={{
|
||||
display: 'flex', alignItems: 'center', gap: 5, padding: '5px 10px',
|
||||
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useRef } from 'react'
|
||||
import { Printer } from 'lucide-react'
|
||||
import Modal from '../shared/Modal'
|
||||
import { useTranslation } from '../../i18n'
|
||||
|
||||
interface PackingPrintPreviewProps {
|
||||
/** The page to print; the preview is closed while this is null. */
|
||||
html: string | null
|
||||
title: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* The printable packing list, shown before it goes to the printer (#1420).
|
||||
*
|
||||
* Printed from its own frame, so the page that reaches the printer is the list and
|
||||
* nothing of the planner around it, and "Save as PDF" in the browser's print dialog
|
||||
* is the PDF export. No script runs inside the frame; printing is started from here.
|
||||
*/
|
||||
export default function PackingPrintPreview({ html, title, onClose }: PackingPrintPreviewProps) {
|
||||
const { t } = useTranslation()
|
||||
const frameRef = useRef<HTMLIFrameElement>(null)
|
||||
return (
|
||||
<Modal
|
||||
isOpen={html != null}
|
||||
onClose={onClose}
|
||||
title={title}
|
||||
size="3xl"
|
||||
footer={(
|
||||
<div className="flex justify-end gap-2">
|
||||
<button type="button" onClick={onClose} className="rounded-lg border border-edge px-4 py-2 text-body text-content-muted hover:bg-surface-hover">
|
||||
{t('common.close')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => frameRef.current?.contentWindow?.print()}
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-accent px-4 py-2 text-body font-medium text-accent-text hover:bg-accent-hover"
|
||||
>
|
||||
<Printer size={15} strokeWidth={2.2} />
|
||||
{t('packing.exportPrint')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{html != null && (
|
||||
<iframe
|
||||
ref={frameRef}
|
||||
title={title}
|
||||
srcDoc={html}
|
||||
sandbox="allow-same-origin allow-modals"
|
||||
className="block h-[62vh] w-full rounded-lg border border-edge"
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// FE-PACKEXPORT-001 to FE-PACKEXPORT-011
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { PackingItem } from '../../types'
|
||||
import { groupByCategory, packingCsv, packingFileName, packingMarkdown, type PackingBagLookup } from './packingExport'
|
||||
import { packingPrintHtml, type PackingPrintText } from './packingPrintDocument'
|
||||
import { parseImportLines } from './packingListPanel.helpers'
|
||||
import { KAT_COLORS } from './packingListPanel.constants'
|
||||
|
||||
let nextId = 1
|
||||
const item = (over: Partial<PackingItem>): PackingItem => ({
|
||||
id: nextId++, trip_id: 1, name: 'Item', checked: 0, category: 'Other', sort_order: 0,
|
||||
weight_grams: null, bag_id: null, quantity: 1, is_private: 0, ...over,
|
||||
})
|
||||
|
||||
const LIST: PackingItem[] = [
|
||||
item({ name: 'Passport', category: 'Documents', checked: 1, weight_grams: 40, bag_id: 2 }),
|
||||
item({ name: 'T-Shirts', category: 'Clothing', quantity: 5, weight_grams: 180, bag_id: 1 }),
|
||||
item({ name: 'Boarding passes', category: 'Documents' }),
|
||||
item({ name: 'Phone charger (USB-C)', category: 'Electronics', weight_grams: 90, bag_id: 2 }),
|
||||
item({ name: 'Sunglasses', category: null }),
|
||||
]
|
||||
|
||||
const BAGS: Record<number, { name: string; color: string }> = {
|
||||
1: { name: 'Suitcase', color: '#f97316' },
|
||||
2: { name: 'Backpack', color: '#6366f1' },
|
||||
}
|
||||
const bag: PackingBagLookup = id => BAGS[id]
|
||||
const noBag: PackingBagLookup = () => undefined
|
||||
|
||||
const TEXT: PackingPrintText = {
|
||||
eyebrow: 'Packing List · Shared', title: 'Lisbon & Porto', dates: 'Oct 9 – 16, 2026',
|
||||
itemsLabel: 'Items', packedLabel: 'Packed', weightLabel: 'Total weight', bagsLabel: 'Bags',
|
||||
uncategorized: 'Other', lang: 'en', origin: 'https://trek.example', fontCss: '@font-face{font-family:Poppins}',
|
||||
}
|
||||
|
||||
describe('groupByCategory', () => {
|
||||
it('FE-PACKEXPORT-001: keeps list order, groups by category and files a blank one under the fallback', () => {
|
||||
expect(groupByCategory(LIST, 'Other').map(g => [g.category, g.items.map(i => i.name)])).toEqual([
|
||||
['Documents', ['Passport', 'Boarding passes']],
|
||||
['Clothing', ['T-Shirts']],
|
||||
['Electronics', ['Phone charger (USB-C)']],
|
||||
['Other', ['Sunglasses']],
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('packingFileName', () => {
|
||||
it('FE-PACKEXPORT-002: builds a short ascii slug from the trip title, and a plain name without one', () => {
|
||||
expect(packingFileName('Lisbon & Porto 2026!', '.md')).toBe('packing-list-lisbon-porto-2026.md')
|
||||
expect(packingFileName(undefined, '.csv')).toBe('packing-list.csv')
|
||||
expect(packingFileName('東京', '.csv')).toBe('packing-list.csv')
|
||||
})
|
||||
})
|
||||
|
||||
describe('packingMarkdown', () => {
|
||||
it('FE-PACKEXPORT-003: writes a checklist with a heading per category, quantities and weights per piece', () => {
|
||||
expect(packingMarkdown(LIST, 'Packing List: Lisbon & Porto (Shared)', 'Other')).toBe([
|
||||
'# Packing List: Lisbon & Porto (Shared)',
|
||||
'',
|
||||
'## Documents',
|
||||
'',
|
||||
'- [x] Passport (40 g)',
|
||||
'- [ ] Boarding passes',
|
||||
'',
|
||||
'## Clothing',
|
||||
'',
|
||||
'- [ ] 5 × T-Shirts (180 g)',
|
||||
'',
|
||||
'## Electronics',
|
||||
'',
|
||||
'- [ ] Phone charger (USB-C) (90 g)',
|
||||
'',
|
||||
'## Other',
|
||||
'',
|
||||
'- [ ] Sunglasses',
|
||||
'',
|
||||
].join('\n'))
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORT-004: comes back through the import with the same categories, quantities, weights and checkmarks', () => {
|
||||
const back = parseImportLines(packingMarkdown(LIST, 'Packing List', 'Other'))
|
||||
expect(back.map(r => [r.category, r.name, r.quantity ?? 1, r.weight_grams ?? null, r.checked])).toEqual([
|
||||
['Documents', 'Passport', 1, '40', true],
|
||||
['Documents', 'Boarding passes', 1, null, false],
|
||||
['Clothing', 'T-Shirts', 5, '180', false],
|
||||
['Electronics', 'Phone charger (USB-C)', 1, '90', false],
|
||||
['Other', 'Sunglasses', 1, null, false],
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('packingCsv', () => {
|
||||
it('FE-PACKEXPORT-005: writes the import format with bag names and no header row', () => {
|
||||
expect(packingCsv(LIST, bag)).toBe([
|
||||
'Documents,Passport,40,Backpack,checked',
|
||||
'Clothing,5 × T-Shirts,180,Suitcase,',
|
||||
'Documents,Boarding passes,,,',
|
||||
'Electronics,Phone charger (USB-C),90,Backpack,',
|
||||
',Sunglasses,,,',
|
||||
'',
|
||||
].join('\n'))
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORT-006: quotes a field with a separator or a quote, and the import reads it back unchanged', () => {
|
||||
const tricky = [item({ name: '12" tray, round', category: 'Kitchen; camping', bag_id: 9, quantity: 2 })]
|
||||
const csv = packingCsv(tricky, () => ({ name: 'Box "A"' }))
|
||||
expect(csv).toBe('"Kitchen; camping","2 × 12"" tray, round",,"Box ""A""",\n')
|
||||
expect(parseImportLines(csv)[0]).toMatchObject({ category: 'Kitchen; camping', name: '12" tray, round', quantity: 2, bag: 'Box "A"', checked: false })
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORT-007: leaves the bag empty when it is unknown', () => {
|
||||
expect(packingCsv([LIST[0]], noBag)).toBe('Documents,Passport,40,,checked\n')
|
||||
})
|
||||
})
|
||||
|
||||
describe('packingPrintHtml', () => {
|
||||
const html = packingPrintHtml(LIST, TEXT, bag)
|
||||
|
||||
it('FE-PACKEXPORT-008: carries the trip, its dates and the numbers of the list in the cover block', () => {
|
||||
expect(html).toContain('<h1>Lisbon & Porto</h1>')
|
||||
expect(html).toContain('Oct 9 – 16, 2026')
|
||||
expect(html).toContain('<b>5</b><span>Items</span>')
|
||||
expect(html).toContain('<b>1/5</b><span>Packed</span>')
|
||||
expect(html).toContain('<b>1.0 kg</b><span>Total weight</span>')
|
||||
expect(html).toContain('<b>2</b><span>Bags</span>')
|
||||
expect(html).toContain('style="width:20%"')
|
||||
expect(html).toContain('https://trek.example/logo-light.svg')
|
||||
expect(html).toContain('@font-face{font-family:Poppins}')
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORT-009: draws a card per category in the app’s colours, with quantity, weight and bag per item', () => {
|
||||
expect(html).toContain(`style="--c:${KAT_COLORS[0]}"`)
|
||||
expect(html).toContain('<em>1/2</em>')
|
||||
expect(html).toMatch(/<li class="done"><span class="box"><svg[^]*?<\/svg><\/span><span class="name">Passport<\/span>/)
|
||||
expect(html).toContain('<span class="qty">×5</span><span class="weight">900 g</span>')
|
||||
expect(html).toContain('<i style="background:#f97316"></i>Suitcase')
|
||||
expect(html).toContain('<b>Backpack</b><span>2 · 130 g</span>')
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORT-010: escapes every text and lets only a plain hex colour into a style', () => {
|
||||
const hostile = packingPrintHtml(
|
||||
[item({ name: '<img src=x onerror=alert(1)>', category: '<b>Cat</b>', bag_id: 1 })],
|
||||
{ ...TEXT, title: '<script>x</script>', dates: null },
|
||||
() => ({ name: '<i>Bag</i>', color: 'red;background:url(//evil)' }),
|
||||
)
|
||||
expect(hostile).not.toContain('<img src=x')
|
||||
expect(hostile).not.toContain('<script>x')
|
||||
expect(hostile).toContain('<img src=x onerror=alert(1)>')
|
||||
expect(hostile).not.toContain('evil')
|
||||
expect(hostile).toContain('background:#94a3b8')
|
||||
expect(hostile).not.toContain('class="dates"')
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORT-011: leaves out weight and bags where the list has none', () => {
|
||||
const bare = packingPrintHtml([item({ name: 'Hat' })], TEXT, noBag)
|
||||
expect(bare).not.toContain('Total weight')
|
||||
expect(bare).not.toContain('class="bags"')
|
||||
expect(bare).toContain('<b>0/1</b><span>Packed</span>')
|
||||
expect(bare).toContain('style="width:0%"')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { PackingItem } from '../../types'
|
||||
|
||||
/**
|
||||
* A packing list leaving TREK as a file (#875): as Markdown for a notes app, and as
|
||||
* CSV in the exact format the import reads. The printable page is its own module,
|
||||
* packingPrintDocument.ts, built from the same grouping.
|
||||
*
|
||||
* Both files follow the order the list shows, so a list that goes out and comes back
|
||||
* through the import lands in the same categories, with the same quantities, weights
|
||||
* and checkmarks. Markdown carries everything but the bag (bags belong to one trip);
|
||||
* CSV carries the bag too.
|
||||
*/
|
||||
|
||||
/** What an export needs to know about a bag: its name, and for the printout its colour. */
|
||||
export interface PackingBagInfo {
|
||||
name: string
|
||||
color?: string | null
|
||||
}
|
||||
export type PackingBagLookup = (bagId: number) => PackingBagInfo | undefined
|
||||
|
||||
export interface PackingCategoryGroup {
|
||||
category: string
|
||||
items: PackingItem[]
|
||||
}
|
||||
|
||||
/** The items under their category, categories in the order their first item appears. */
|
||||
export function groupByCategory(items: PackingItem[], uncategorized: string): PackingCategoryGroup[] {
|
||||
const groups = new Map<string, PackingItem[]>()
|
||||
for (const item of items) {
|
||||
const category = item.category?.trim() || uncategorized
|
||||
const group = groups.get(category)
|
||||
if (group) group.push(item)
|
||||
else groups.set(category, [item])
|
||||
}
|
||||
return [...groups].map(([category, grouped]) => ({ category, items: grouped }))
|
||||
}
|
||||
|
||||
/** "3 × Socks", read back by the import's quantity rule; one is left unsaid. */
|
||||
const withQuantity = (item: PackingItem): string =>
|
||||
(item.quantity ?? 1) > 1 ? `${item.quantity} × ${item.name}` : item.name
|
||||
|
||||
/** A file name from the trip title: lowercase, ascii-ish, short. */
|
||||
export function packingFileName(tripTitle: string | undefined, extension: string): string {
|
||||
const slug = (tripTitle || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40)
|
||||
return `packing-list${slug ? `-${slug}` : ''}${extension}`
|
||||
}
|
||||
|
||||
/**
|
||||
* A Markdown checklist: the title as the one top heading, a second-level heading per
|
||||
* category, and a task-list item per entry with its weight per piece in brackets.
|
||||
*/
|
||||
export function packingMarkdown(items: PackingItem[], title: string, uncategorized: string): string {
|
||||
const lines = [`# ${title}`]
|
||||
for (const group of groupByCategory(items, uncategorized)) {
|
||||
lines.push('', `## ${group.category}`, '')
|
||||
for (const item of group.items) {
|
||||
const weight = item.weight_grams ? ` (${item.weight_grams} g)` : ''
|
||||
lines.push(`- [${item.checked ? 'x' : ' '}] ${withQuantity(item)}${weight}`)
|
||||
}
|
||||
}
|
||||
return `${lines.join('\n')}\n`
|
||||
}
|
||||
|
||||
/** One CSV field, quoted when it holds a separator or a quote, a quote doubled inside. */
|
||||
const csvField = (value: string): string => (/[",;\t\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value)
|
||||
|
||||
/**
|
||||
* The import format, one item per row: Category, Name, Weight, Bag, checked. No header
|
||||
* row, because the import would read it as an item.
|
||||
*/
|
||||
export function packingCsv(items: PackingItem[], bag: PackingBagLookup): string {
|
||||
const rows = items.map(item => [
|
||||
item.category?.trim() || '',
|
||||
withQuantity(item),
|
||||
item.weight_grams ? String(item.weight_grams) : '',
|
||||
item.bag_id != null ? bag(item.bag_id)?.name ?? '' : '',
|
||||
item.checked ? 'checked' : '',
|
||||
].map(csvField).join(','))
|
||||
return `${rows.join('\n')}\n`
|
||||
}
|
||||
@@ -17,3 +17,6 @@ export const BAG_COLORS = ['#6366f1', '#ec4899', '#f97316', '#10b981', '#06b6d4'
|
||||
// A category's first item is seeded with this sentinel because the server
|
||||
// rejects empty names. Treat it as a placeholder in the UI.
|
||||
export const PACKING_PLACEHOLDER_NAME = '...'
|
||||
|
||||
/** What the import's file picker offers: CSV rows, plain text, or a Markdown list (#875). */
|
||||
export const PACKING_IMPORT_ACCEPT = '.csv,.txt,.md,.markdown,text/markdown'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { katColor, itemWeight, bagFillPct, bagTotalWeight, countsTowardsMyLoad, parseCsvLine, parseImportLines, unassignedTotalWeight } from './packingListPanel.helpers'
|
||||
import { katColor, itemWeight, bagFillPct, bagTotalWeight, countsTowardsMyLoad, isMarkdownList, parseCsvLine, parseImportLines, unassignedTotalWeight } from './packingListPanel.helpers'
|
||||
import { KAT_COLORS } from './packingListPanel.constants'
|
||||
|
||||
describe('packingListPanel.helpers', () => {
|
||||
@@ -127,6 +127,72 @@ describe('packingListPanel.helpers', () => {
|
||||
expect(rows).toHaveLength(1)
|
||||
expect(rows[0].name).toBe('Passport')
|
||||
})
|
||||
|
||||
it('reads a leading "3x" as the quantity in CSV rows too, and leaves "4x4 adapter" a name', () => {
|
||||
expect(parseImportLines('Clothing, 3x Socks, 40')[0]).toMatchObject({ name: 'Socks', quantity: 3, weight_grams: '40' })
|
||||
expect(parseImportLines('Clothing, 2 × T-Shirts')[0]).toMatchObject({ name: 'T-Shirts', quantity: 2 })
|
||||
expect(parseImportLines('Car, 4x4 adapter')[0]).toMatchObject({ name: '4x4 adapter' })
|
||||
expect(parseImportLines('Car, 4x4 adapter')[0].quantity).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps a doubled quote inside a quoted field as one quote (#875 CSV export)', () => {
|
||||
expect(parseCsvLine('Other,"12"" pizza tray, round",,,')).toEqual(['Other', '12" pizza tray, round', '', '', ''])
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseImportLines with Markdown (#875)', () => {
|
||||
it('recognises a Markdown list by a heading or a list item, and leaves CSV rows alone', () => {
|
||||
expect(isMarkdownList('## Clothing\nSocks')).toBe(true)
|
||||
expect(isMarkdownList('- [ ] Socks')).toBe(true)
|
||||
expect(isMarkdownList('1. Passport')).toBe(true)
|
||||
expect(isMarkdownList('Clothing, Socks\nDocuments, Passport')).toBe(false)
|
||||
})
|
||||
|
||||
it('takes the category from the heading above, checkmarks from the box, and ignores everything else', () => {
|
||||
const rows = parseImportLines([
|
||||
'# Packing List: Lisbon (Shared)',
|
||||
'',
|
||||
'A note that is not an item.',
|
||||
'## Clothing ##',
|
||||
'- [x] T-Shirts',
|
||||
'* [ ] Rain jacket',
|
||||
'---',
|
||||
'### Documents',
|
||||
'1. Passport',
|
||||
'+ Boarding pass',
|
||||
].join('\n'))
|
||||
expect(rows).toEqual([
|
||||
{ name: 'T-Shirts', category: 'Clothing', weight_grams: undefined, bag: undefined, checked: true },
|
||||
{ name: 'Rain jacket', category: 'Clothing', weight_grams: undefined, bag: undefined, checked: false },
|
||||
{ name: 'Passport', category: 'Documents', weight_grams: undefined, bag: undefined, checked: false },
|
||||
{ name: 'Boarding pass', category: 'Documents', weight_grams: undefined, bag: undefined, checked: false },
|
||||
])
|
||||
})
|
||||
|
||||
it('reads the export’s quantity and weight back, in g or kg, and keeps other brackets in the name', () => {
|
||||
const rows = parseImportLines('## Kit\n- [ ] 5 × T-Shirts (180 g)\n- [ ] Tent (1,2 kg)\n- [x] Phone charger (USB-C) (90 g)\n- [ ] Charger (USB-C)')
|
||||
expect(rows.map(r => [r.name, r.quantity, r.weight_grams, r.checked])).toEqual([
|
||||
['T-Shirts', 5, '180', false],
|
||||
['Tent', undefined, '1200', false],
|
||||
['Phone charger (USB-C)', undefined, '90', true],
|
||||
['Charger (USB-C)', undefined, undefined, false],
|
||||
])
|
||||
})
|
||||
|
||||
it('turns links, emphasis and code marks into plain text, and skips an empty checkbox', () => {
|
||||
const rows = parseImportLines('- [ ] **Sun**screen\n- [ ] [Adapter](https://example.com/adapter) `EU`\n- [ ]\n- [x]')
|
||||
expect(rows.map(r => r.name)).toEqual(['Sunscreen', 'Adapter EU'])
|
||||
})
|
||||
|
||||
it('puts items before the first heading in no category, so the import files them under Other', () => {
|
||||
expect(parseImportLines('- Sunglasses\n## Hats\n- Cap').map(r => r.category)).toEqual([undefined, 'Hats'])
|
||||
})
|
||||
|
||||
it('stays linear on hostile input', () => {
|
||||
const started = performance.now()
|
||||
parseImportLines(`#${' '.repeat(50_000)}x\n-${' '.repeat(50_000)}\n- [ ] ${'('.repeat(50_000)}`)
|
||||
expect(performance.now() - started).toBeLessThan(500)
|
||||
})
|
||||
})
|
||||
|
||||
describe('bagTotalWeight / unassignedTotalWeight (#2191)', () => {
|
||||
|
||||
@@ -76,13 +76,16 @@ export const unassignedTotalWeight = (
|
||||
export const bagFillPct = (bagWeight: number, limitGrams: number | null | undefined, heaviestBagWeight: number): number =>
|
||||
Math.min(100, Math.round((bagWeight / (limitGrams || Math.max(heaviestBagWeight, 1))) * 100))
|
||||
|
||||
// Parse CSV line respecting quoted values (e.g. "Shirt, blue" stays as one field)
|
||||
// Parse CSV line respecting quoted values (e.g. "Shirt, blue" stays as one field).
|
||||
// A doubled quote inside a quoted field is a literal one, which is how the CSV
|
||||
// export writes a name that contains a quote.
|
||||
export const parseCsvLine = (line: string): string[] => {
|
||||
const parts: string[] = []
|
||||
let current = ''
|
||||
let inQuotes = false
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const ch = line[i]
|
||||
if (ch === '"' && inQuotes && line[i + 1] === '"') { current += '"'; i++; continue }
|
||||
if (ch === '"') { inQuotes = !inQuotes; continue }
|
||||
if (!inQuotes && (ch === ',' || ch === ';' || ch === '\t')) { parts.push(current.trim()); current = ''; continue }
|
||||
current += ch
|
||||
@@ -97,21 +100,83 @@ export interface ParsedImportItem {
|
||||
weight_grams: string | undefined
|
||||
bag: string | undefined
|
||||
checked: boolean
|
||||
/** From a leading "3x" or "3 ×" on the name. Absent means one. */
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
// "3x Socks", "3 x Socks", "3 × Socks". The space after the x is required, so
|
||||
// "4x4 adapter" stays a name.
|
||||
const QUANTITY_PREFIX = /^(\d{1,3})\s*[x×]\s+(\S.*)$/i
|
||||
|
||||
/** A name with its quantity prefix split off, the same way for CSV rows and Markdown items. */
|
||||
export const splitQuantity = (raw: string): { name: string; quantity?: number } => {
|
||||
const match = QUANTITY_PREFIX.exec(raw.trim())
|
||||
const quantity = match ? Number(match[1]) : 0
|
||||
return match && quantity >= 1 ? { name: match[2].trim(), quantity } : { name: raw.trim() }
|
||||
}
|
||||
|
||||
const MD_HEADING = /^#{1,6}\s+(\S.*)$/
|
||||
const MD_ITEM = /^(?:[-*+]|\d+[.)])\s+(?:\[([ xX])\]\s+)?(\S.*)$/
|
||||
// The weight the Markdown export writes after a name: "(200 g)" or "(1.2 kg)".
|
||||
// Only a parenthesis that holds nothing but a weight counts, so "Charger (USB-C)"
|
||||
// keeps its name.
|
||||
const MD_WEIGHT = /\((\d+(?:[.,]\d+)?)\s?(g|kg)\)$/i
|
||||
|
||||
/** Links, emphasis and code marks as plain text: a list copied from a notes app is full of them. */
|
||||
const plainMarkdown = (text: string): string =>
|
||||
text.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1').replace(/\*\*|__|~~|`/g, '').trim()
|
||||
|
||||
/**
|
||||
* Whether pasted text is a Markdown list rather than CSV rows (#875): any heading or
|
||||
* list item decides it, the way Obsidian, Notion, GitHub and most notes apps export.
|
||||
*/
|
||||
export const isMarkdownList = (text: string): boolean =>
|
||||
text.split('\n').some(line => MD_HEADING.test(line.trim()) || MD_ITEM.test(line.trim()))
|
||||
|
||||
/**
|
||||
* A Markdown list as import rows. Every heading names the category of the items
|
||||
* under it, "- [x]" marks an item packed, and anything that is neither heading nor
|
||||
* list item (a note, a blank line, a rule) is left out.
|
||||
*/
|
||||
const parseMarkdownLines = (text: string): ParsedImportItem[] => {
|
||||
const out: ParsedImportItem[] = []
|
||||
let category: string | undefined
|
||||
for (const line of text.split('\n').map(l => l.trim())) {
|
||||
const heading = MD_HEADING.exec(line)
|
||||
if (heading) {
|
||||
// A closing run of hashes ("## Clothing ##") is decoration, not part of the name.
|
||||
category = plainMarkdown(heading[1].replace(/\s#+$/, '')) || undefined
|
||||
continue
|
||||
}
|
||||
const item = MD_ITEM.exec(line)
|
||||
// "- [ ]" with nothing after it is an empty checkbox, not an item called "[ ]".
|
||||
if (!item || /^\[[ xX]\]$/.test(item[2])) continue
|
||||
let label = plainMarkdown(item[2])
|
||||
let weight_grams: string | undefined
|
||||
const weight = MD_WEIGHT.exec(label)
|
||||
if (weight) {
|
||||
const grams = Number(weight[1].replace(',', '.')) * (weight[2].toLowerCase() === 'kg' ? 1000 : 1)
|
||||
weight_grams = String(Math.round(grams))
|
||||
label = label.slice(0, weight.index).trim()
|
||||
}
|
||||
out.push({ ...splitQuantity(label), category, weight_grams, bag: undefined, checked: item[1]?.toLowerCase() === 'x' })
|
||||
}
|
||||
return out.filter(i => i.name)
|
||||
}
|
||||
|
||||
export const parseImportLines = (text: string): ParsedImportItem[] => {
|
||||
if (isMarkdownList(text)) return parseMarkdownLines(text)
|
||||
return text.split('\n').map(line => line.trim()).filter(Boolean).map(line => {
|
||||
// Format: Category, Name, Weight (optional), Bag (optional), checked/unchecked (optional)
|
||||
const parts = parseCsvLine(line)
|
||||
if (parts.length >= 2) {
|
||||
const category = parts[0]
|
||||
const name = parts[1]
|
||||
const weight_grams = parts[2] || undefined
|
||||
const bag = parts[3] || undefined
|
||||
const checked = parts[4]?.toLowerCase() === 'checked' || parts[4] === '1'
|
||||
return { name, category, weight_grams, bag, checked }
|
||||
return { ...splitQuantity(parts[1]), category, weight_grams, bag, checked }
|
||||
}
|
||||
// Single value = just a name
|
||||
return { name: parts[0], category: undefined, weight_grams: undefined, bag: undefined, checked: false }
|
||||
return { ...splitQuantity(parts[0]), category: undefined, weight_grams: undefined, bag: undefined, checked: false }
|
||||
}).filter(i => i.name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import { escapeHtml } from '@trek/shared'
|
||||
import type { PackingItem } from '../../types'
|
||||
import { formatWeight, packingItemWeight } from '../../mobile/screens/trip/tabs/listsModel'
|
||||
import { katColor } from './packingListPanel.helpers'
|
||||
import { groupByCategory, type PackingBagInfo, type PackingBagLookup } from './packingExport'
|
||||
|
||||
/*
|
||||
* The packing list as a standalone page to print or save as PDF (#1420). Like the trip
|
||||
* PDF it is a document of its own in a srcdoc frame, where the app's CSS variables do
|
||||
* not reach, so its colours are written out here (the theme lint exempts this file).
|
||||
*/
|
||||
|
||||
export interface PackingPrintText {
|
||||
/** "Packing List · Shared": what the page is, above the title. */
|
||||
eyebrow: string
|
||||
/** The trip's title, or the list's name when the trip has none. */
|
||||
title: string
|
||||
/** The trip's dates, already formatted for the reader. */
|
||||
dates: string | null
|
||||
itemsLabel: string
|
||||
packedLabel: string
|
||||
weightLabel: string
|
||||
bagsLabel: string
|
||||
uncategorized: string
|
||||
lang: string
|
||||
/** Where the logos are served from; the page is a srcdoc and has no URL of its own. */
|
||||
origin: string
|
||||
/** The app's own @font-face rules, so the page prints in Poppins without asking a font CDN. */
|
||||
fontCss: string
|
||||
}
|
||||
|
||||
// A bag colour lands in a style attribute, so only a plain hex colour gets there.
|
||||
const HEX_COLOR = /^#(?:[0-9a-f]{3}){1,2}$/i
|
||||
const BAG_FALLBACK = '#94a3b8'
|
||||
const bagColor = (info: PackingBagInfo): string =>
|
||||
info.color && HEX_COLOR.test(info.color) ? info.color : BAG_FALLBACK
|
||||
|
||||
const CHECK_SVG =
|
||||
'<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M2.6 6.3 5 8.6 9.5 3.7" fill="none" stroke="#fff" '
|
||||
+ 'stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
||||
|
||||
// The look of the trip PDF: Poppins, a navy cover block, small spaced capitals for labels,
|
||||
// slate greys. Colour is kept for print on purpose, the categories are told apart by it.
|
||||
const PRINT_CSS = `
|
||||
@page { size: A4; margin: 12mm; }
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html { background: #fff; }
|
||||
body { margin: 0; color: #1e293b; font: 9.5pt/1.4 'Poppins', -apple-system, 'Segoe UI', Roboto, sans-serif; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
@media screen {
|
||||
html { background: #e2e8f0; }
|
||||
body { padding: 28px 16px; }
|
||||
.sheet { max-width: 794px; margin: 0 auto; padding: 12mm; background: #fff; border-radius: 6px; box-shadow: 0 12px 40px rgba(15, 23, 42, 0.16); }
|
||||
}
|
||||
.hero { margin-bottom: 18px; padding: 22px 26px 20px; border-radius: 14px; background: #0f172a; color: #fff; break-inside: avoid; }
|
||||
.hero-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
|
||||
.eyebrow { font-size: 7.5pt; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; color: rgba(255, 255, 255, 0.5); }
|
||||
.hero-top img { height: 18px; opacity: 0.6; }
|
||||
h1 { margin: 0; font-size: 24pt; font-weight: 700; line-height: 1.1; letter-spacing: -0.01em; }
|
||||
.dates { margin-top: 6px; font-size: 9pt; color: rgba(255, 255, 255, 0.55); }
|
||||
.stats { display: flex; gap: 30px; margin-top: 18px; padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, 0.1); }
|
||||
.stat b { display: block; font-size: 16pt; font-weight: 700; line-height: 1; }
|
||||
.stat span { display: block; margin-top: 5px; font-size: 6.5pt; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; color: rgba(255, 255, 255, 0.45); }
|
||||
.progress { height: 4px; margin-top: 16px; overflow: hidden; border-radius: 99px; background: rgba(255, 255, 255, 0.12); }
|
||||
.progress i { display: block; height: 100%; border-radius: 99px; background: #fff; }
|
||||
main { columns: 2; column-gap: 14px; }
|
||||
.cat { margin: 0 0 14px; padding: 10px 12px 6px; border: 1px solid #e2e8f0; border-radius: 12px; break-inside: avoid; }
|
||||
.cat h2 { display: flex; align-items: center; gap: 7px; margin: 0 0 4px; font-size: 7.5pt; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase; color: #0f172a; }
|
||||
.cat h2 i { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--c); }
|
||||
.cat h2 span { flex: 1; min-width: 0; }
|
||||
.cat h2 em { padding: 1px 7px; border-radius: 99px; background: #f1f5f9; color: #64748b; font-size: 7pt; font-style: normal; font-weight: 600; letter-spacing: 0; }
|
||||
ul { margin: 0; padding: 0; list-style: none; }
|
||||
li { display: flex; align-items: center; gap: 7px; padding: 5px 0; border-top: 1px solid #f1f5f9; break-inside: avoid; }
|
||||
li:first-child { border-top: 0; }
|
||||
.box { display: flex; flex: none; align-items: center; justify-content: center; width: 13px; height: 13px; border: 1.5px solid #cbd5e1; border-radius: 4px; }
|
||||
li.done .box { border-color: #0f172a; background: #0f172a; }
|
||||
.box svg { width: 9px; height: 9px; }
|
||||
.name { flex: 1; min-width: 0; font-weight: 500; }
|
||||
li.done .name { color: #94a3b8; text-decoration: line-through; }
|
||||
.qty { flex: none; padding: 1px 6px; border-radius: 99px; background: #f1f5f9; color: #475569; font-size: 7pt; font-weight: 600; }
|
||||
.weight { flex: none; color: #64748b; font-size: 7.5pt; font-variant-numeric: tabular-nums; }
|
||||
.bag { display: inline-flex; flex: none; align-items: center; gap: 4px; color: #64748b; font-size: 7pt; }
|
||||
.bag i, .bag-card i { flex: none; width: 6px; height: 6px; border-radius: 50%; }
|
||||
.bags { margin-top: 4px; break-inside: avoid; }
|
||||
.bags h3 { margin: 0 0 8px; font-size: 7.5pt; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase; color: #64748b; }
|
||||
.bag-list { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.bag-card { display: flex; align-items: center; gap: 7px; padding: 7px 12px; border: 1px solid #e2e8f0; border-radius: 10px; font-size: 8pt; }
|
||||
.bag-card span { color: #64748b; }
|
||||
footer { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 22px; opacity: 0.35; color: #64748b; font-size: 6.5pt; letter-spacing: 0.5px; }
|
||||
footer img { height: 9px; }
|
||||
@media screen and (max-width: 560px) {
|
||||
body { padding: 12px 8px; }
|
||||
.sheet { padding: 16px; }
|
||||
.hero { padding: 18px 18px 16px; }
|
||||
h1 { font-size: 18pt; }
|
||||
.stats { flex-wrap: wrap; gap: 14px 22px; }
|
||||
main { columns: 1; }
|
||||
}
|
||||
`
|
||||
|
||||
interface BagSummary {
|
||||
info: PackingBagInfo
|
||||
count: number
|
||||
grams: number
|
||||
}
|
||||
|
||||
/** The bags this view's items go into, in the order they first appear, with this view's share of them. */
|
||||
function bagSummaries(items: PackingItem[], bag: PackingBagLookup): BagSummary[] {
|
||||
const byId = new Map<number, BagSummary>()
|
||||
for (const item of items) {
|
||||
const info = item.bag_id != null ? bag(item.bag_id) : undefined
|
||||
if (!info || item.bag_id == null) continue
|
||||
const summary = byId.get(item.bag_id) ?? { info, count: 0, grams: 0 }
|
||||
summary.count += 1
|
||||
summary.grams += packingItemWeight(item)
|
||||
byId.set(item.bag_id, summary)
|
||||
}
|
||||
return [...byId.values()]
|
||||
}
|
||||
|
||||
function itemRow(item: PackingItem, bag: PackingBagLookup): string {
|
||||
const info = item.bag_id != null ? bag(item.bag_id) : undefined
|
||||
const quantity = (item.quantity ?? 1) > 1 ? `<span class="qty">×${item.quantity}</span>` : ''
|
||||
const weight = item.weight_grams ? `<span class="weight">${escapeHtml(formatWeight(packingItemWeight(item)))}</span>` : ''
|
||||
const bagPill = info ? `<span class="bag"><i style="background:${bagColor(info)}"></i>${escapeHtml(info.name)}</span>` : ''
|
||||
return `<li${item.checked ? ' class="done"' : ''}><span class="box">${item.checked ? CHECK_SVG : ''}</span>`
|
||||
+ `<span class="name">${escapeHtml(item.name)}</span>${quantity}${weight}${bagPill}</li>`
|
||||
}
|
||||
|
||||
/**
|
||||
* The list as a page to print or save as PDF, in the look of the trip PDF: a navy cover
|
||||
* block with the trip, its dates and the list's numbers, then one card per category in
|
||||
* two columns, each item with a box to tick on paper. A category card never breaks across
|
||||
* a column or a page, and the bags close the page with what each one holds.
|
||||
*/
|
||||
export function packingPrintHtml(items: PackingItem[], text: PackingPrintText, bag: PackingBagLookup): string {
|
||||
const groups = groupByCategory(items, text.uncategorized)
|
||||
const categories = groups.map(group => group.category)
|
||||
const packed = items.filter(item => item.checked).length
|
||||
const percent = items.length ? Math.round((packed / items.length) * 100) : 0
|
||||
const total = items.reduce((sum, item) => sum + packingItemWeight(item), 0)
|
||||
const bags = bagSummaries(items, bag)
|
||||
|
||||
const stat = (value: string, label: string) =>
|
||||
`<div class="stat"><b>${escapeHtml(value)}</b><span>${escapeHtml(label)}</span></div>`
|
||||
const stats = [
|
||||
stat(String(items.length), text.itemsLabel),
|
||||
stat(`${packed}/${items.length}`, text.packedLabel),
|
||||
total > 0 ? stat(formatWeight(total), text.weightLabel) : '',
|
||||
bags.length ? stat(String(bags.length), text.bagsLabel) : '',
|
||||
].join('')
|
||||
|
||||
const sections = groups.map(group => {
|
||||
const done = group.items.filter(item => item.checked).length
|
||||
return `<section class="cat" style="--c:${katColor(group.category, categories)}">`
|
||||
+ `<h2><i></i><span>${escapeHtml(group.category)}</span><em>${done}/${group.items.length}</em></h2>`
|
||||
+ `<ul>${group.items.map(item => itemRow(item, bag)).join('')}</ul></section>`
|
||||
}).join('')
|
||||
|
||||
const bagCards = bags.map(summary =>
|
||||
`<div class="bag-card"><i style="background:${bagColor(summary.info)}"></i><b>${escapeHtml(summary.info.name)}</b>`
|
||||
+ `<span>${summary.count} · ${escapeHtml(formatWeight(summary.grams))}</span></div>`).join('')
|
||||
const bagsHtml = bags.length
|
||||
? `<div class="bags"><h3>${escapeHtml(text.bagsLabel)}</h3><div class="bag-list">${bagCards}</div></div>`
|
||||
: ''
|
||||
|
||||
const origin = escapeHtml(text.origin)
|
||||
return `<!doctype html><html lang="${escapeHtml(text.lang)}"><head><meta charset="utf-8"><title>${escapeHtml(text.title)}</title>
|
||||
<style>${text.fontCss}${PRINT_CSS}</style></head><body><div class="sheet">
|
||||
<header class="hero">
|
||||
<div class="hero-top"><span class="eyebrow">${escapeHtml(text.eyebrow)}</span><img src="${origin}/logo-light.svg" alt="TREK"></div>
|
||||
<h1>${escapeHtml(text.title)}</h1>
|
||||
${text.dates ? `<div class="dates">${escapeHtml(text.dates)}</div>` : ''}
|
||||
<div class="stats">${stats}</div>
|
||||
<div class="progress"><i style="width:${percent}%"></i></div>
|
||||
</header>
|
||||
<main>${sections}</main>
|
||||
${bagsHtml}
|
||||
<footer><span>made with</span><img src="${origin}/logo-dark.svg" alt="TREK"></footer>
|
||||
</div></body></html>`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
// FE-PACKEXPORTHOOK-001 to FE-PACKEXPORTHOOK-009
|
||||
import { createElement, type ReactNode } from 'react'
|
||||
import { act, renderHook, waitFor } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { PackingItem } from '../../types'
|
||||
import { TranslationProvider } from '../../i18n/TranslationContext'
|
||||
import { useAddonStore } from '../../store/addonStore'
|
||||
import { useTripStore } from '../../store/tripStore'
|
||||
import { isEffectivelyOffline } from '../../sync/networkMode'
|
||||
import { downloadBlob } from '../../utils/fileDownload'
|
||||
import { packingApi } from '../../api/client'
|
||||
import { appFontFaces, tripDateRange, usePackingExport } from './usePackingExport'
|
||||
import { PACKING_PLACEHOLDER_NAME } from './packingListPanel.constants'
|
||||
|
||||
vi.mock('../../api/client', () => ({ packingApi: { listBags: vi.fn() } }))
|
||||
vi.mock('../../utils/fileDownload', () => ({ downloadBlob: vi.fn() }))
|
||||
vi.mock('../../sync/networkMode', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('../../sync/networkMode')>()),
|
||||
isEffectivelyOffline: vi.fn(() => false),
|
||||
}))
|
||||
|
||||
let nextId = 1
|
||||
const item = (over: Partial<PackingItem>): PackingItem => ({
|
||||
id: nextId++, trip_id: 7, name: 'Item', checked: 0, category: 'Clothing', sort_order: 0,
|
||||
weight_grams: null, bag_id: null, quantity: 1, is_private: 0, ...over,
|
||||
})
|
||||
|
||||
const ITEMS = [
|
||||
item({ name: 'Socks', quantity: 7, weight_grams: 40, bag_id: 1 }),
|
||||
item({ name: PACKING_PLACEHOLDER_NAME, category: 'Empty list' }),
|
||||
item({ name: 'Diary', category: 'Personal', is_private: 1 }),
|
||||
]
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) => createElement(TranslationProvider, null, children)
|
||||
|
||||
/** What the last download wrote, as text, with its file name. */
|
||||
async function lastDownload(): Promise<{ name: string; text: string; type: string }> {
|
||||
const calls = vi.mocked(downloadBlob).mock.calls
|
||||
const [blob, name] = calls[calls.length - 1] as [Blob, string]
|
||||
return { name, text: await blob.text(), type: blob.type }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
useTripStore.setState({ trip: { id: 7, title: 'Lisbon & Porto', start_date: '2026-10-09', end_date: '2026-10-16' } as never, packingItems: ITEMS })
|
||||
useAddonStore.setState({ bagTracking: true })
|
||||
vi.mocked(isEffectivelyOffline).mockReturnValue(false)
|
||||
vi.mocked(packingApi.listBags).mockResolvedValue({ bags: [{ id: 1, name: 'Suitcase', color: '#f97316' }] })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
useTripStore.setState({ trip: null as never, packingItems: [] })
|
||||
useAddonStore.setState({ bagTracking: false })
|
||||
})
|
||||
|
||||
describe('usePackingExport', () => {
|
||||
it('FE-PACKEXPORTHOOK-001: exports the Shared view without private items or category placeholders', async () => {
|
||||
const { result } = renderHook(() => usePackingExport(7, 'common'), { wrapper })
|
||||
expect(result.current.hasItems).toBe(true)
|
||||
act(() => result.current.exportMarkdown())
|
||||
const file = await lastDownload()
|
||||
expect(file.name).toBe('packing-list-lisbon-porto.md')
|
||||
expect(file.type).toBe('text/markdown;charset=utf-8')
|
||||
expect(file.text).toContain('# Packing List: Lisbon & Porto (Shared)')
|
||||
expect(file.text).toContain('- [ ] 7 × Socks (40 g)')
|
||||
expect(file.text).not.toContain('Diary')
|
||||
expect(file.text).not.toContain(PACKING_PLACEHOLDER_NAME)
|
||||
expect(file.text).not.toContain('Empty list')
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTHOOK-002: exports My list with only the private items', async () => {
|
||||
const { result } = renderHook(() => usePackingExport(7, 'personal'), { wrapper })
|
||||
act(() => result.current.exportMarkdown())
|
||||
const file = await lastDownload()
|
||||
expect(file.text).toContain('(My list)')
|
||||
expect(file.text).toContain('- [ ] Diary')
|
||||
expect(file.text).not.toContain('Socks')
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTHOOK-003: writes the bag names into the CSV', async () => {
|
||||
const { result } = renderHook(() => usePackingExport(7, 'common'), { wrapper })
|
||||
await act(() => result.current.exportCsv())
|
||||
const file = await lastDownload()
|
||||
expect(file.name).toBe('packing-list-lisbon-porto.csv')
|
||||
expect(file.text).toBe('Clothing,7 × Socks,40,Suitcase,\n')
|
||||
expect(packingApi.listBags).toHaveBeenCalledWith(7)
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTHOOK-004: goes without bags offline, with bag tracking off, or when the bags cannot be read', async () => {
|
||||
vi.mocked(isEffectivelyOffline).mockReturnValue(true)
|
||||
const { result, rerender } = renderHook(() => usePackingExport(7, 'common'), { wrapper })
|
||||
await act(() => result.current.exportCsv())
|
||||
expect((await lastDownload()).text).toBe('Clothing,7 × Socks,40,,\n')
|
||||
|
||||
vi.mocked(isEffectivelyOffline).mockReturnValue(false)
|
||||
act(() => useAddonStore.setState({ bagTracking: false }))
|
||||
rerender()
|
||||
await act(() => result.current.exportCsv())
|
||||
expect((await lastDownload()).text).toBe('Clothing,7 × Socks,40,,\n')
|
||||
expect(packingApi.listBags).not.toHaveBeenCalled()
|
||||
|
||||
act(() => useAddonStore.setState({ bagTracking: true }))
|
||||
rerender()
|
||||
vi.mocked(packingApi.listBags).mockRejectedValue(new Error('Network Error'))
|
||||
await act(() => result.current.exportCsv())
|
||||
expect((await lastDownload()).text).toBe('Clothing,7 × Socks,40,,\n')
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTHOOK-005: opens the printable page with the trip, its dates and the list’s labels, and closes it', async () => {
|
||||
const { result } = renderHook(() => usePackingExport(7, 'common'), { wrapper })
|
||||
expect(result.current.printHtml).toBeNull()
|
||||
expect(result.current.printTitle).toBe('Packing List: Lisbon & Porto')
|
||||
await act(() => result.current.openPrint())
|
||||
const html = result.current.printHtml ?? ''
|
||||
expect(html).toContain('<h1>Lisbon & Porto</h1>')
|
||||
expect(html).toContain('Packing List · Shared')
|
||||
expect(html).toContain('<span>Items</span>')
|
||||
expect(html).toContain('<span>Packed</span>')
|
||||
expect(html).toContain('<i style="background:#f97316"></i>Suitcase')
|
||||
expect(html).toContain(`${window.location.origin}/logo-light.svg`)
|
||||
expect(html).toMatch(/Oct\D+9\D+16, 2026/)
|
||||
act(() => result.current.closePrint())
|
||||
await waitFor(() => expect(result.current.printHtml).toBeNull())
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTHOOK-006: has nothing to export for an empty view', () => {
|
||||
act(() => useTripStore.setState({ packingItems: [item({ name: PACKING_PLACEHOLDER_NAME })] }))
|
||||
const { result } = renderHook(() => usePackingExport(7, 'common'), { wrapper })
|
||||
expect(result.current.hasItems).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('tripDateRange', () => {
|
||||
it('FE-PACKEXPORTHOOK-007: formats one day, a range, or nothing, on the calendar day whatever the offset', () => {
|
||||
expect(tripDateRange(null, null, 'en')).toBeNull()
|
||||
expect(tripDateRange('2026-10-09', '2026-10-09', 'en')).toBe('Oct 9, 2026')
|
||||
expect(tripDateRange('2026-10-09', null, 'de')).toBe('9. Okt. 2026')
|
||||
expect(tripDateRange('2026-12-30', '2027-01-02', 'en')).toMatch(/Dec 30, 2026\D+Jan 2, 2027/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('appFontFaces', () => {
|
||||
it('FE-PACKEXPORTHOOK-008: copies the family’s @font-face rules with absolute urls and skips sheets it may not read', () => {
|
||||
const fontFace = (cssText: string) => ({ type: CSSRule.FONT_FACE_RULE, cssText })
|
||||
const doc = {
|
||||
baseURI: 'https://trek.example/trips/7',
|
||||
styleSheets: [
|
||||
{
|
||||
href: 'https://trek.example/assets/index.css',
|
||||
cssRules: [
|
||||
fontFace('@font-face { font-family: Poppins; src: url("./poppins-400.woff2") format("woff2"); }'),
|
||||
fontFace('@font-face { font-family: Geist; src: url(/geist.woff2); }'),
|
||||
{ type: CSSRule.STYLE_RULE, cssText: 'body { font-family: Poppins; }' },
|
||||
],
|
||||
},
|
||||
{ href: null, cssRules: [fontFace("@font-face { font-family: 'Poppins'; src: url(/assets/poppins-700.woff2); }")] },
|
||||
{ href: 'https://cdn.example/x.css', get cssRules(): never { throw new DOMException('blocked', 'SecurityError') } },
|
||||
],
|
||||
} as unknown as Document
|
||||
const css = appFontFaces('Poppins', doc)
|
||||
expect(css).toContain('url("https://trek.example/assets/poppins-400.woff2")')
|
||||
expect(css).toContain('url(https://trek.example/assets/poppins-700.woff2)')
|
||||
expect(css).not.toContain('Geist')
|
||||
expect(css).not.toContain('body {')
|
||||
})
|
||||
|
||||
it('FE-PACKEXPORTHOOK-009: reads the live document by default without throwing', () => {
|
||||
expect(typeof appFontFaces('Poppins')).toBe('string')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,126 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { packingApi } from '../../api/client'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { packingViewItems, type PackingView } from '../../mobile/screens/trip/tabs/listsModel'
|
||||
import { useAddonStore } from '../../store/addonStore'
|
||||
import { useTripStore } from '../../store/tripStore'
|
||||
import { isEffectivelyOffline } from '../../sync/networkMode'
|
||||
import { downloadBlob } from '../../utils/fileDownload'
|
||||
import { PACKING_PLACEHOLDER_NAME } from './packingListPanel.constants'
|
||||
import { packingCsv, packingFileName, packingMarkdown, type PackingBagLookup } from './packingExport'
|
||||
import { packingPrintHtml } from './packingPrintDocument'
|
||||
|
||||
export type { PackingView }
|
||||
|
||||
const noBag: PackingBagLookup = () => undefined
|
||||
|
||||
/** The trip's dates as one range in the reader's language, or null for a trip without them. */
|
||||
export function tripDateRange(start: string | null | undefined, end: string | null | undefined, locale: string): string | null {
|
||||
if (!start) return null
|
||||
// Pinned to UTC: a trip date is a calendar day, and the reader's offset must not move it.
|
||||
const format = new Intl.DateTimeFormat(locale, { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' })
|
||||
const from = new Date(`${start}T00:00:00Z`)
|
||||
if (!end || end === start) return format.format(from)
|
||||
const to = new Date(`${end}T00:00:00Z`)
|
||||
const ranged = format as Intl.DateTimeFormat & { formatRange?: (a: Date, b: Date) => string }
|
||||
return ranged.formatRange ? ranged.formatRange(from, to) : `${format.format(from)} · ${format.format(to)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* The @font-face rules the app loaded for `family`, with every url made absolute.
|
||||
*
|
||||
* The printable page is a srcdoc frame that knows no fonts of its own. Handing it the
|
||||
* app's rules lets it print in Poppins from the same origin, where the trip PDF still
|
||||
* asks Google for it.
|
||||
*/
|
||||
export function appFontFaces(family: string, doc: Document = document): string {
|
||||
const rules: string[] = []
|
||||
for (const sheet of Array.from(doc.styleSheets)) {
|
||||
let sheetRules: CSSRuleList
|
||||
try {
|
||||
sheetRules = sheet.cssRules
|
||||
} catch {
|
||||
continue // a cross-origin sheet does not let its rules be read, and has no font of ours
|
||||
}
|
||||
const base = sheet.href || doc.baseURI
|
||||
for (const rule of Array.from(sheetRules)) {
|
||||
if (rule.type !== CSSRule.FONT_FACE_RULE || !rule.cssText.includes(family)) continue
|
||||
rules.push(rule.cssText.replace(/url\((["']?)([^"')]+)\1\)/g, (_, quote: string, url: string) =>
|
||||
`url(${quote}${new URL(url, base).href}${quote})`))
|
||||
}
|
||||
}
|
||||
return rules.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Export, CSV and print for the view of the packing list on screen (#875, #1420), for
|
||||
* the desktop header and the phone's action menu alike.
|
||||
*
|
||||
* Exactly what the view shows goes out: the Personal list never takes the group's
|
||||
* items along, and the Common one never takes anybody's private ones. The placeholder
|
||||
* rows that keep an empty category on screen are not items and stay behind.
|
||||
*
|
||||
* Bags are read when an export runs rather than kept in sync, because only a CSV row
|
||||
* or the printout needs them; offline, or with bag tracking off, rows go without.
|
||||
*/
|
||||
export function usePackingExport(tripId: number | string, view: PackingView) {
|
||||
const { t, language, locale } = useTranslation()
|
||||
const trip = useTripStore(s => s.trip)
|
||||
const allItems = useTripStore(s => s.packingItems)
|
||||
const bagTracking = useAddonStore(s => s.bagTracking)
|
||||
const items = useMemo(
|
||||
() => packingViewItems(allItems, view).filter(item => item.name !== PACKING_PLACEHOLDER_NAME),
|
||||
[allItems, view],
|
||||
)
|
||||
const [printHtml, setPrintHtml] = useState<string | null>(null)
|
||||
|
||||
const tripTitle = trip?.title
|
||||
const listName = view === 'personal' ? t('packing.viewPersonal') : t('packing.viewCommon')
|
||||
const title = tripTitle ? `${t('packing.title')}: ${tripTitle}` : t('packing.title')
|
||||
const uncategorized = t('packing.defaultCategory')
|
||||
|
||||
const bagLookup = useCallback(async (): Promise<PackingBagLookup> => {
|
||||
if (!bagTracking || isEffectivelyOffline()) return noBag
|
||||
try {
|
||||
const { bags } = await packingApi.listBags(tripId) as { bags?: { id: number; name: string; color?: string | null }[] }
|
||||
const byId = new Map((bags ?? []).map(bag => [bag.id, { name: bag.name, color: bag.color }]))
|
||||
return bagId => byId.get(bagId)
|
||||
} catch {
|
||||
return noBag
|
||||
}
|
||||
}, [bagTracking, tripId])
|
||||
|
||||
const download = useCallback((content: string, type: string, extension: string) => {
|
||||
downloadBlob(new Blob([content], { type: `${type};charset=utf-8` }), packingFileName(tripTitle, extension))
|
||||
}, [tripTitle])
|
||||
|
||||
const exportMarkdown = useCallback(() => {
|
||||
download(packingMarkdown(items, `${title} (${listName})`, uncategorized), 'text/markdown', '.md')
|
||||
}, [download, items, title, listName, uncategorized])
|
||||
|
||||
const exportCsv = useCallback(async () => {
|
||||
download(packingCsv(items, await bagLookup()), 'text/csv', '.csv')
|
||||
}, [download, items, bagLookup])
|
||||
|
||||
const openPrint = useCallback(async () => {
|
||||
setPrintHtml(packingPrintHtml(items, {
|
||||
eyebrow: `${t('packing.title')} · ${listName}`,
|
||||
title: tripTitle || t('packing.title'),
|
||||
dates: tripDateRange(trip?.start_date, trip?.end_date, locale),
|
||||
itemsLabel: t('packing.printItems'),
|
||||
packedLabel: t('packing.printPacked'),
|
||||
weightLabel: t('packing.totalWeight'),
|
||||
bagsLabel: t('packing.bags'),
|
||||
uncategorized,
|
||||
lang: language,
|
||||
origin: window.location.origin,
|
||||
fontCss: appFontFaces('Poppins'),
|
||||
}, await bagLookup()))
|
||||
}, [items, t, listName, tripTitle, trip?.start_date, trip?.end_date, locale, uncategorized, language, bagLookup])
|
||||
|
||||
const closePrint = useCallback(() => setPrintHtml(null), [])
|
||||
|
||||
return { hasItems: items.length > 0, exportMarkdown, exportCsv, openPrint, printHtml, printTitle: title, closePrint }
|
||||
}
|
||||
|
||||
export type PackingExport = ReturnType<typeof usePackingExport>
|
||||
@@ -22,7 +22,8 @@ export const { context: tripListsContext, guides: tripListsGuides } = defineScre
|
||||
['packing-categories', 'tags', 'guide', 5, 3, { slug: PACKING, anchor: 'categories' }, ['check-off-packing', 'apply-packing-template'], true],
|
||||
['check-off-packing', 'checkCircle', 'guide', 5, 3, { slug: PACKING, anchor: 'progress-bar' }, ['packing-categories', 'share-packing-item'], true],
|
||||
['apply-packing-template', 'library', 'guide', 4, 3, { slug: TEMPLATES, anchor: 'applying-a-template' }, ['packing-templates', 'import-packing-list'], true],
|
||||
['import-packing-list', 'fileInput', 'quick', 4, 2, { slug: PACKING, anchor: 'importing-a-list' }, ['apply-packing-template', 'packing-bags'], true],
|
||||
['import-packing-list', 'fileInput', 'quick', 4, 2, { slug: PACKING, anchor: 'importing-a-list' }, ['export-packing-list', 'apply-packing-template'], true],
|
||||
['export-packing-list', 'upload', 'quick', 4, 2, { slug: PACKING, anchor: 'printing-and-exporting' }, ['import-packing-list', 'apply-packing-template'], true],
|
||||
['share-packing-item', 'users', 'guide', 5, 3, { slug: PACKING, anchor: 'the-three-tiers' }, ['packing-categories', 'add-member']],
|
||||
['packing-bags', 'archive', 'tour', 6, 3, { slug: PACKING, anchor: 'bag-tracking' }, ['toggle-addon', 'import-packing-list'], true],
|
||||
['create-todo', 'plus', 'guide', 6, 3, { slug: TODOS, anchor: 'adding-tasks' }, ['todo-filters'], true],
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Eyebrow, FIELD_AREA_CLS, FormSheetFooter, FormSheetHeader } from '../sh
|
||||
import { packingApi } from '../../../../api/client'
|
||||
import { useTripStore } from '../../../../store/tripStore'
|
||||
import { parseImportLines } from '../../../../components/Packing/packingListPanel.helpers'
|
||||
import { PACKING_IMPORT_ACCEPT } from '../../../../components/Packing/packingListPanel.constants'
|
||||
import type { TripPlanner } from '../MTripShell'
|
||||
|
||||
export interface MPackingImportSheetProps {
|
||||
@@ -58,7 +59,8 @@ export default function MPackingImportSheet({ planner, open, onClose }: MPacking
|
||||
<FormSheetHeader title={t('packing.importTitle')} onClose={onClose} closeLabel={t('common.close')} />
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-[18px] pb-[6px] pt-1">
|
||||
<p className="mb-3 font-geist text-[0.71875rem] leading-[1.5] text-m-muted">{t('packing.importHint')}</p>
|
||||
<p className="mb-1 font-geist text-[0.71875rem] leading-[1.5] text-m-muted">{t('packing.importHint')}</p>
|
||||
<p className="mb-3 font-geist text-[0.71875rem] leading-[1.5] text-m-muted">{t('packing.importHintMarkdown')}</p>
|
||||
|
||||
<textarea
|
||||
value={text}
|
||||
@@ -68,7 +70,7 @@ export default function MPackingImportSheet({ planner, open, onClose }: MPacking
|
||||
className={`${FIELD_AREA_CLS} font-geist`}
|
||||
/>
|
||||
|
||||
<input ref={fileInputRef} type="file" accept=".csv,.txt" onChange={handleFile} className="hidden" />
|
||||
<input ref={fileInputRef} type="file" accept={PACKING_IMPORT_ACCEPT} onChange={handleFile} className="hidden" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useEffect, useMemo, useState, useCallback } from 'react'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import {
|
||||
Briefcase, Check, CheckCheck, ChevronDown, ChevronUp, HandHelping,
|
||||
Download, LayoutTemplate, MoreHorizontal, Package, Pencil, Plus, RotateCcw, Save as SaveIcon, Trash2, UserPlus, UserRound,
|
||||
Download, FileSpreadsheet, FileText, LayoutTemplate, MoreHorizontal, Package, Pencil, Plus, Printer, RotateCcw, Save as SaveIcon,
|
||||
Trash2, UserPlus, UserRound,
|
||||
} from 'lucide-react'
|
||||
import MDancingTrek from '../../../components/MDancingTrek'
|
||||
import { useAuthStore } from '../../../../store/authStore'
|
||||
@@ -27,6 +28,8 @@ import {
|
||||
import MBagsSheet from './MBagsSheet'
|
||||
import MPackItemSheet from './MPackItemSheet'
|
||||
import MPackingImportSheet from './MPackingImportSheet'
|
||||
import PackingPrintPreview from '../../../../components/Packing/PackingPrintPreview'
|
||||
import { usePackingExport } from '../../../../components/Packing/usePackingExport'
|
||||
|
||||
type ActionView = 'menu' | 'apply' | 'save'
|
||||
interface CategoryAssignee { user_id: number; username: string }
|
||||
@@ -49,6 +52,9 @@ export default function MPackingListTab({ planner }: { planner: TripPlanner }) {
|
||||
const tripMembers = planner.tripMembers
|
||||
|
||||
const [view, setView] = useState<PackingView>('common')
|
||||
const packingExport = usePackingExport(tripId, view)
|
||||
// Exporting changes nothing, so the menu is there for anyone with a list to take along.
|
||||
const hasActions = canEdit || packingExport.hasItems
|
||||
const [statusFilter, setStatusFilter] = useState<PackingStatusFilter>('all')
|
||||
const [editMode, setEditMode] = useState(false)
|
||||
const [actionsOpen, setActionsOpen] = useState(false)
|
||||
@@ -306,7 +312,7 @@ export default function MPackingListTab({ planner }: { planner: TripPlanner }) {
|
||||
{editMode ? t('packing.editDone') : t('common.edit')}
|
||||
</button>
|
||||
)}
|
||||
{canEdit && (
|
||||
{hasActions && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActionsOpen(v => !v)}
|
||||
@@ -325,11 +331,11 @@ export default function MPackingListTab({ planner }: { planner: TripPlanner }) {
|
||||
</div>
|
||||
|
||||
{/* ── Action menu (spec §4.2) ── */}
|
||||
{actionsOpen && canEdit && (
|
||||
{actionsOpen && hasActions && (
|
||||
<div className="mt-[6px] overflow-hidden rounded-2xl border border-[color:var(--m-rowbr)] bg-[color:var(--m-glass)] backdrop-blur-[24px]">
|
||||
{actionView === 'menu' && (
|
||||
<>
|
||||
{checkedCount > 0 && (
|
||||
{canEdit && checkedCount > 0 && (
|
||||
<ActionRow
|
||||
icon={Trash2}
|
||||
label={t('packing.clearChecked', { count: checkedCount })}
|
||||
@@ -337,13 +343,22 @@ export default function MPackingListTab({ planner }: { planner: TripPlanner }) {
|
||||
onClick={() => { setActionsOpen(false); setConfirmClear(true) }}
|
||||
/>
|
||||
)}
|
||||
{templates.length > 0 && (
|
||||
{canEdit && templates.length > 0 && (
|
||||
<ActionRow icon={LayoutTemplate} label={t('packing.applyTemplate')} onClick={() => setActionView('apply')} />
|
||||
)}
|
||||
{isAdmin && items.length > 0 && (
|
||||
{canEdit && isAdmin && items.length > 0 && (
|
||||
<ActionRow icon={SaveIcon} label={t('packing.saveAsTemplate')} onClick={() => setActionView('save')} />
|
||||
)}
|
||||
<ActionRow icon={Download} label={t('packing.import')} onClick={() => { setActionsOpen(false); setShowImportSheet(true) }} />
|
||||
{canEdit && (
|
||||
<ActionRow icon={Download} label={t('packing.import')} onClick={() => { setActionsOpen(false); setShowImportSheet(true) }} />
|
||||
)}
|
||||
{packingExport.hasItems && (
|
||||
<>
|
||||
<ActionRow icon={Printer} label={t('packing.exportPrint')} onClick={() => { setActionsOpen(false); void packingExport.openPrint() }} />
|
||||
<ActionRow icon={FileText} label={t('packing.exportMarkdown')} onClick={() => { setActionsOpen(false); packingExport.exportMarkdown() }} />
|
||||
<ActionRow icon={FileSpreadsheet} label={t('packing.exportCsv')} onClick={() => { setActionsOpen(false); void packingExport.exportCsv() }} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{actionView === 'apply' && (
|
||||
@@ -512,6 +527,7 @@ export default function MPackingListTab({ planner }: { planner: TripPlanner }) {
|
||||
/>
|
||||
|
||||
<MPackingImportSheet planner={planner} open={showImportSheet} onClose={() => setShowImportSheet(false)} />
|
||||
<PackingPrintPreview html={packingExport.printHtml} title={packingExport.printTitle} onClose={packingExport.closePrint} />
|
||||
|
||||
<MConfirmSheet
|
||||
open={confirmClear}
|
||||
|
||||
@@ -26,6 +26,7 @@ import BookingImportModal from '../components/Planner/BookingImportModal'
|
||||
import AirTrailImportModal from '../components/Planner/AirTrailImportModal'
|
||||
// MemoriesPanel moved to Journey addon
|
||||
import ApplyTemplateButton from '../components/Packing/ApplyTemplateButton'
|
||||
import PackingExportMenu from '../components/Packing/PackingExportMenu'
|
||||
import type { ExpensePrefill } from '../components/Budget/CostsPanel'
|
||||
import type { BookingExpenseRequest } from '../components/Planner/BookingCostsSection.types'
|
||||
import type { BudgetItem } from '../types'
|
||||
@@ -174,6 +175,8 @@ function ListsContainer({ tripId, packingItems, todoItems }: { tripId: number; p
|
||||
{subTab === 'packing' && (() => {
|
||||
const packingAbgehakt = packingItems.filter(i => i.checked).length
|
||||
const sharedBtnClass = 'inline-flex items-center gap-1.5 px-2.5 sm:px-[14px] py-[7px] sm:py-[9px] hover:opacity-[0.88]'
|
||||
// Export and Import carry only their icon, with the name as tooltip and label.
|
||||
const iconBtnClass = 'inline-flex items-center justify-center px-2.5 py-[7px] sm:py-[9px] hover:opacity-[0.88] bg-accent text-accent-text'
|
||||
const sharedBtnStyle: React.CSSProperties = {
|
||||
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
|
||||
borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 500,
|
||||
@@ -204,12 +207,14 @@ function ListsContainer({ tripId, packingItems, todoItems }: { tripId: number; p
|
||||
<span className="hidden sm:inline">{t('packing.saveAsTemplate')}</span>
|
||||
</button>
|
||||
)}
|
||||
<PackingExportMenu tripId={tripId} view={packingView} className={iconBtnClass} style={sharedBtnStyle} />
|
||||
<button type="button" onClick={() => setImportPackingSignal(s => s + 1)}
|
||||
className={`${sharedBtnClass} bg-accent text-accent-text`}
|
||||
className={iconBtnClass}
|
||||
style={sharedBtnStyle}
|
||||
aria-label={t('packing.import')}
|
||||
title={t('packing.import')}
|
||||
>
|
||||
<Download size={14} strokeWidth={2.5} />
|
||||
<span className="hidden sm:inline">{t('packing.import')}</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -90,6 +90,7 @@ vi.mock('../components/Trips/TripFormModal', () => ({ default: stub('tripForm')
|
||||
vi.mock('../components/Trips/TripMembersModal', () => ({ default: stub('membersModal') }))
|
||||
vi.mock('../components/Packing/PackingListPanel', () => ({ default: stub('packingPanel', 'packing-list-panel') }))
|
||||
vi.mock('../components/Packing/ApplyTemplateButton', () => ({ default: stub('applyTemplate', 'apply-template') }))
|
||||
vi.mock('../components/Packing/PackingExportMenu', () => ({ default: stub('exportMenu', 'export-menu') }))
|
||||
vi.mock('../components/Todo/TodoListPanel', () => ({ default: stub('todoPanel', 'todo-list-panel') }))
|
||||
vi.mock('../components/Files/FileManager', () => ({ default: stub('fileManager', 'file-manager') }))
|
||||
vi.mock('../components/Budget/CostsPanel', () => ({
|
||||
@@ -1080,6 +1081,21 @@ describe('TripPlannerPage — lists tab', () => {
|
||||
act(() => { props('packingPanel').onViewChange('personal') })
|
||||
expect(props('applyTemplate').visibility).toBe('personal')
|
||||
})
|
||||
|
||||
it('FE-PAGE-TPW-044b: export and import sit in the header as icons, export following the open view (#875, #1420)', async () => {
|
||||
renderPage({ activeTab: 'listen', packingItems: [buildPackingItem({ checked: 0 })] })
|
||||
await screen.findByTestId('packing-list-panel')
|
||||
|
||||
expect(screen.getByTestId('export-menu')).toBeInTheDocument()
|
||||
expect(props('exportMenu').tripId).toBe(42)
|
||||
expect(props('exportMenu').view).toBe('common')
|
||||
act(() => { props('packingPanel').onViewChange('personal') })
|
||||
expect(props('exportMenu').view).toBe('personal')
|
||||
|
||||
const importButton = screen.getByRole('button', { name: 'Import' })
|
||||
expect(importButton).toHaveAttribute('title', 'Import')
|
||||
expect(importButton).toHaveTextContent('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TripPlannerPage — modals', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { buildPlanner } from '../../../helpers/mobileTrip'
|
||||
import { resetAllStores, seedStore } from '../../../helpers/store'
|
||||
import { fireEvent, render, screen, waitFor } from '../../../helpers/render'
|
||||
|
||||
// FE-MOB-PACKIMP-001 to FE-MOB-PACKIMP-012
|
||||
// FE-MOB-PACKIMP-001 to FE-MOB-PACKIMP-014
|
||||
|
||||
function packItem(overrides: Partial<PackingItem> = {}): PackingItem {
|
||||
return {
|
||||
@@ -141,4 +141,21 @@ describe('MPackingImportSheet', () => {
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('FE-MOB-PACKIMP-013: explains the Markdown form and lets the file picker take .md (#875)', () => {
|
||||
setup()
|
||||
expect(screen.getByText('packing.importHintMarkdown')).toBeInTheDocument()
|
||||
expect(document.querySelector('input[type="file"]')).toHaveAttribute('accept', '.csv,.txt,.md,.markdown,text/markdown')
|
||||
})
|
||||
|
||||
it('FE-MOB-PACKIMP-014: posts a pasted Markdown checklist with categories, quantities and checkmarks', async () => {
|
||||
const spy = vi.spyOn(packingApi, 'bulkImport').mockResolvedValue({ items: [], count: 2 })
|
||||
setup('## Clothing\n- [x] 3x Socks (40 g)\n- [ ] Rain jacket')
|
||||
fireEvent.click(screen.getByRole('button', { name: 'packing.importAction:2' }))
|
||||
await waitFor(() => expect(spy).toHaveBeenCalledTimes(1))
|
||||
expect(spy).toHaveBeenCalledWith(7, [
|
||||
expect.objectContaining({ name: 'Socks', category: 'Clothing', quantity: 3, weight_grams: '40', checked: true }),
|
||||
expect.objectContaining({ name: 'Rain jacket', category: 'Clothing', checked: false }),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,8 +11,9 @@ import { buildUser } from '../../../helpers/factories'
|
||||
import { buildPlanner } from '../../../helpers/mobileTrip'
|
||||
import { resetAllStores, seedStore } from '../../../helpers/store'
|
||||
import { act, fireEvent, render, screen, waitFor, within } from '../../../helpers/render'
|
||||
import * as fileDownload from '../../../../src/utils/fileDownload'
|
||||
|
||||
// FE-MOB-PACKTAB-001 to FE-MOB-PACKTAB-046 (plus 060-062)
|
||||
// FE-MOB-PACKTAB-001 to FE-MOB-PACKTAB-046 (plus 060-068)
|
||||
|
||||
const ME = 7
|
||||
const ANNA = { id: 11, username: 'anna', avatar: null, avatar_url: 'https://cdn.example/anna.png' } as unknown as TripMember
|
||||
@@ -934,4 +935,47 @@ describe('MPackingListTab', () => {
|
||||
expect(screen.queryByText('packing.takenCareOf:owner')).not.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('packing.takenCareOf:owner')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// ── Export (#875, #1420) ──────────────────────────────────────────────
|
||||
|
||||
it('FE-MOB-PACKTAB-066: offers the three exports in the action menu, after Import', async () => {
|
||||
seedStore(useTripStore, { packingItems: ITEMS })
|
||||
await setup()
|
||||
openActions()
|
||||
const labels = screen.getAllByRole('button').map(b => b.textContent)
|
||||
const importAt = labels.indexOf('packing.import')
|
||||
expect(labels.slice(importAt, importAt + 4)).toEqual([
|
||||
'packing.import', 'packing.exportPrint', 'packing.exportMarkdown', 'packing.exportCsv',
|
||||
])
|
||||
})
|
||||
|
||||
it('FE-MOB-PACKTAB-067: a member who may not edit still gets the menu, with the exports and nothing else', async () => {
|
||||
seedStore(useTripStore, { packingItems: ITEMS })
|
||||
await setup({ planner: { can: vi.fn(() => false) } })
|
||||
openActions()
|
||||
expect(screen.getByRole('button', { name: 'packing.exportMarkdown' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'packing.import' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: /packing.clearChecked/ })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'packing.applyTemplate' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('FE-MOB-PACKTAB-068: downloads the open view as Markdown, and opens the print preview', async () => {
|
||||
const download = vi.spyOn(fileDownload, 'downloadBlob').mockImplementation(() => {})
|
||||
seedStore(useTripStore, { packingItems: ITEMS })
|
||||
await setup()
|
||||
openActions()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'packing.exportMarkdown' }))
|
||||
expect(download).toHaveBeenCalledTimes(1)
|
||||
const [blob, name] = download.mock.calls[0]
|
||||
expect(name).toMatch(/^packing-list.*\.md$/)
|
||||
const text = await (blob as Blob).text()
|
||||
expect(text).toContain('- [x] Passport')
|
||||
expect(text).toContain('- [ ] 3 × Socks (120 g)')
|
||||
expect(text).not.toContain('Diary')
|
||||
expect(screen.queryByRole('button', { name: 'packing.exportMarkdown' })).not.toBeInTheDocument()
|
||||
|
||||
openActions()
|
||||
await act(async () => { fireEvent.click(screen.getByRole('button', { name: 'packing.exportPrint' })) })
|
||||
expect(await screen.findByTitle(/Packing List/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2716,7 +2716,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'«مشترك» و«قائمتي» تقسمان قائمة التجهيز إلى اثنتين: المجموعة التي يراها الجميع، وقائمتك أنت. و«الكل» و«مفتوح» و«تم» تضيّق المفتوحة منهما، والشريط في الأعلى يعدّ ما جُهّز.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'«تطبيق قالب» و«استيراد» و«حفظ كقالب» تملأ قائمة أو تحفظها دون كتابتها كلها، والزر الأحمر بجانبها يذكر كم عنصرًا محددًا ويزيلها.',
|
||||
'«تطبيق قالب» و«حفظ كقالب» تملآن قائمة أو تحفظانها دون كتابتها كلها، والأيقونتان بجانبهما تصدّران القائمة كنسخة مطبوعة أو PDF أو ملف، وتستوردان قائمة. والزر الأحمر يذكر كم عنصرًا محددًا ويزيلها.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'لـ«المهام» شريط جانبي خاص بها: بطاقة التقدّم، والفلاتر «الكل» و«مهامي» و«متأخرة» و«منجز»، و«ترتيب حسب» «الأولوية» أو «تاريخ الاستحقاق»، وصف واحد لكل قائمة. والنقر على مهمة يفتحها في اللوحة على اليمين، و«إضافة مهمة جديدة» تفتح نموذج «مهمة جديدة» فوق وسط الشاشة.',
|
||||
// packing-categories
|
||||
@@ -2774,18 +2774,34 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'لصق قائمة تجهيز كاملة',
|
||||
'help.guide.import-packing-list.goal': 'حوّل قائمة عندك في مكان آخر إلى عناصر تجهيز دفعة واحدة.',
|
||||
'help.guide.import-packing-list.step.1': 'انقر «استيراد» في الشريط فوق القائمة.',
|
||||
'help.guide.import-packing-list.step.1': 'انقر زر الاستيراد ذا السهم المتجه لأسفل في الشريط فوق القائمة.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'عنصر واحد في كل سطر: الفئة، الاسم، الوزن بالغرام (اختياري)، الأمتعة (اختياري)، checked/unchecked (اختياري). والنموذج الرمادي في الصندوق يعرض الأشكال الأربعة كلها.',
|
||||
'عنصر واحد في كل سطر: الفئة، الاسم، الوزن بالغرام (اختياري)، الأمتعة (اختياري)، checked/unchecked (اختياري). والنموذج الرمادي في الصندوق يعرض الأشكال الأربعة كلها. وتصلح قائمة Markdown أيضًا: العنوان يسمّي القائمة، و"- [ ]" و"- [x]" تصبح عناصر.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'أو حمّل السطور من ملف عبر «تحميل CSV/TXT». يقبل ملف .csv أو .txt ويستبدل ما في الصندوق.',
|
||||
'أو حمّل السطور من ملف عبر «تحميل CSV/TXT/MD». يقبل ملف .csv أو .txt أو .md ويستبدل ما في الصندوق.',
|
||||
'help.guide.import-packing-list.step.4': 'انقر «استيراد». الزر يعدّ السطور التي فهمها.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'كل سطر صار صفًا، في القائمة التي يسميها حقله الأول، ولم يُمس شيء مما كان موجودًا.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'الفواصل والفواصل المنقوطة وعلامات الجدولة كلها تفصل الحقول، وعلامتا الاقتباس المزدوجتان تجمعان الحقل معًا، فيبقى «Shirt, blue» اسمًا واحدًا. والسطر بقيمة واحدة هو اسم فقط، والسطر بلا قائمة خاصة به يحط في «أخرى».',
|
||||
'الفواصل والفواصل المنقوطة وعلامات الجدولة كلها تفصل الحقول، وعلامتا الاقتباس المزدوجتان تجمعان الحقل معًا، فيبقى «Shirt, blue» اسمًا واحدًا. والسطر بقيمة واحدة هو اسم فقط، والسطر بلا قائمة خاصة به يحط في «أخرى»، و"3x" قبل الاسم تحدد الكمية.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'الأمتعة المذكورة في الحقل الرابع تُنشأ إن لم تكن في الرحلة بعد. وهذا المكان الوحيد الذي يحمّل الأوزان والأمتعة بالجملة؛ أما القالب فلا يجلب إلا الأسماء والقوائم.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'طباعة قائمة التجهيز أو تصديرها',
|
||||
'help.guide.export-packing-list.goal': 'خذ القائمة معك على الورق، أو كملف PDF، أو كملف لتطبيق آخر أو للرحلة التالية.',
|
||||
'help.guide.export-packing-list.step.1': 'انقر زر التصدير ذا السهم المتجه لأعلى في الشريط فوق القائمة.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'«قائمة تحقق Markdown (.md)» و«CSV للاستيراد (.csv)» تحفظان القائمة كملف مباشرةً.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'انقر «طباعة أو حفظ كملف PDF». تعرض المعاينة القائمة كصفحة: الرحلة وتواريخها في الأعلى، ثم كل قائمة كبطاقة فيها مربع للتحديد.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'انقر «طباعة أو حفظ كملف PDF» تحت المعاينة. يفتح المتصفح نافذة الطباعة الخاصة به: اختر طابعة، أو «حفظ بتنسيق PDF» للاحتفاظ بملف.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'النسخة المطبوعة والملفات تحمل العرض المفتوح، «مشترك» أو «قائمتي»، مع الكميات والأوزان وعلامات التحديد.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'ملف CSV هو الصيغة التي يقرؤها «استيراد»، بما فيها الأمتعة، فيصلح قالب تجهيز خاصًا بك: استورده في الرحلة التالية.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'ملف Markdown يُفتح كقائمة تحقق في Obsidian أو Notion أو GitHub، ويعود عبر «استيراد» بالطريقة نفسها.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'تحديد من يرى العنصر ومن يحضره',
|
||||
'help.guide.share-packing-item.goal': 'انقل عنصرًا بين مجموعة الفريق وقائمتك أنت والأشخاص الذين تحضره لأجلهم.',
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'عنصر واحد لكل سطر. الصيغة: الفئة, الاسم, الوزن بالغرام (اختياري), الحقيبة (اختياري), checked/unchecked (اختياري)',
|
||||
'packing.importPlaceholder':
|
||||
'نظافة, فرشاة أسنان\nملابس, قمصان, 200\nمستندات, جواز سفر, , حقيبة يد\nإلكترونيات, شاحن, 50, حقيبة سفر, checked',
|
||||
'packing.importCsv': 'تحميل CSV/TXT',
|
||||
'packing.importCsv': 'تحميل CSV/TXT/MD',
|
||||
'packing.export': 'تصدير',
|
||||
'packing.exportPrint': 'طباعة أو حفظ كملف PDF',
|
||||
'packing.exportMarkdown': 'قائمة تحقق Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV للاستيراد (.csv)',
|
||||
'packing.printItems': 'العناصر',
|
||||
'packing.printPacked': 'جُهّز',
|
||||
'packing.importHintMarkdown':
|
||||
'يمكن أيضًا استخدام قائمة Markdown: العنوان يحدد الفئة، و"- [ ]" و"- [x]" تصبح عناصر، و"3x" قبل الاسم تحدد الكمية.',
|
||||
'packing.importAction': 'استيراد {count}',
|
||||
'packing.importSuccess': 'تم استيراد {count} عنصر',
|
||||
'packing.importError': 'فشل الاستيراد',
|
||||
|
||||
@@ -3129,7 +3129,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Paylaşılan və Mənim siyahım baqaj siyahısını ikiyə bölür: hər kəsin gördüyü ümumi siyahı və sizin şəxsi siyahınız. Hamısı, Açıq və Hazır hansı açıqdırsa onu daraldır, yuxarıdakı zolaq isə yığılanları sayır.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Şablonu tətbiq et, İdxal et və Şablon kimi yadda saxla siyahını əl ilə yazmadan doldurur və ya saxlayır, yanlarındakı qırmızı düymə isə neçə elementin işarələndiyini göstərir və onları silir.',
|
||||
'Şablonu tətbiq et və Şablon kimi yadda saxla siyahını əl ilə yazmadan doldurur və ya saxlayır, yanlarındakı iki ikon isə siyahını çap, PDF və ya fayl kimi ixrac edir və bir siyahı idxal edir. Qırmızı düymə neçə elementin işarələndiyini göstərir və onları silir.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Tapşırıqların öz yan paneli var: irəliləyiş kartı, Hamısı, Mənim tapşırıqlarım, Vaxtı keçmiş və Hazır filtrləri, Prioritet və ya Son tarix üzrə Sırala və hər siyahı üçün bir sətir. Tapşırığa klikləmək onu sağdakı paneldə açır, Yeni tapşırıq əlavə et isə ekranın ortasında Yeni tapşırıq formasını açır.',
|
||||
// packing-categories
|
||||
@@ -3195,19 +3195,36 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Bütöv baqaj siyahısını yapışdırın',
|
||||
'help.guide.import-packing-list.goal': 'Başqa yerdə artıq olan siyahını bir dəfəyə baqaj elementlərinə çevirin.',
|
||||
'help.guide.import-packing-list.step.1': 'Siyahının üstündəki paneldə İdxal et düyməsinə klikləyin.',
|
||||
'help.guide.import-packing-list.step.1': 'Siyahının üstündəki paneldə aşağı oxlu idxal düyməsinə klikləyin.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Hər sətirdə bir element: Kateqoriya, Ad, qramla çəki (istəyə bağlı), Çanta (istəyə bağlı), checked/unchecked (istəyə bağlı). Xanadakı boz nümunə dörd formanın hamısını göstərir.',
|
||||
'Hər sətirdə bir element: Kateqoriya, Ad, qramla çəki (istəyə bağlı), Çanta (istəyə bağlı), checked/unchecked (istəyə bağlı). Xanadakı boz nümunə dörd formanın hamısını göstərir. Markdown siyahısı da işləyir: başlıq siyahını adlandırır, "- [ ]" və "- [x]" isə elementə çevrilir.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Və ya sətirləri CSV/TXT yüklə ilə fayldan yükləyin. O, .csv və ya .txt faylı qəbul edir və xanadakı hər şeyi əvəz edir.',
|
||||
'Və ya sətirləri CSV/TXT/MD yüklə ilə fayldan yükləyin. O, .csv, .txt və ya .md faylı qəbul edir və xanadakı hər şeyi əvəz edir.',
|
||||
'help.guide.import-packing-list.step.4':
|
||||
'İdxal et düyməsinə klikləyin. Düymə başa düşdüyü sətirlərin sayını göstərir.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Hər sətir birinci sahədə adı çəkilən siyahıda bir element olur, əvvəlcədən olanlara isə toxunulmur.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Vergüllər, nöqtəli vergüllər və tab simvolları sahələri ayırır, qoşa dırnaqlar isə sahəni bir yerdə saxlayır, beləliklə "Köynək, mavi" tək ad kimi qalır. Tək dəyərli sətir sadəcə addır, öz siyahısı olmayan sətir isə Digər siyahısına düşür.',
|
||||
'Vergüllər, nöqtəli vergüllər və tab simvolları sahələri ayırır, qoşa dırnaqlar isə sahəni bir yerdə saxlayır, beləliklə "Köynək, mavi" tək ad kimi qalır. Tək dəyərli sətir sadəcə addır, öz siyahısı olmayan sətir Digər siyahısına düşür, adın önündəki "3x" isə miqdarı təyin edir.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Dördüncü sahədə adı çəkilən çanta səyahətdə hələ yoxdursa, yaradılır. Çəkiləri və çantaları toplu şəkildə yükləyən yeganə yer budur; şablon yalnız adları və siyahıları gətirir.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Baqaj siyahısını çap edin və ya ixrac edin',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Siyahını kağız üzərində, PDF kimi və ya başqa tətbiq ya da növbəti səyahət üçün fayl kimi özünüzlə götürün.',
|
||||
'help.guide.export-packing-list.step.1': 'Siyahının üstündəki paneldə yuxarı oxlu ixrac düyməsinə klikləyin.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Markdown yoxlama siyahısı (.md) və İdxal üçün CSV (.csv) siyahını dərhal fayl kimi saxlayır.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Çap et və ya PDF kimi saxla düyməsinə klikləyin. Önizləmə siyahını səhifə kimi göstərir: yuxarıda səyahət və onun tarixləri, sonra hər siyahı işarələmək üçün qutusu olan kart kimi.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Önizləmənin altındakı Çap et və ya PDF kimi saxla düyməsinə klikləyin. Brauzer öz çap pəncərəsini açır: printer seçin və ya faylı saxlamaq üçün PDF kimi yadda saxla seçimini edin.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Çap nüsxəsi və fayllar açıq olan görünüşü, Paylaşılan və ya Mənim siyahım, miqdarlar, çəkilər və işarələrlə birlikdə saxlayır.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV İdxal et funksiyasının oxuduğu formatdır, çantalar da daxil, ona görə də sizin öz baqaj şablonunuz kimi işləyir: onu növbəti səyahətə idxal edin.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Markdown faylı Obsidian, Notion və ya GitHub-da yoxlama siyahısı kimi açılır və İdxal et ilə eyni şəkildə geri gəlir.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Elementi kimin görəcəyini və kimin gətirəcəyini müəyyən edin',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -20,7 +20,15 @@ const packing: TranslationStrings = {
|
||||
'Hər sətirdə bir element. Format: Kateqoriya, Ad, qramla çəki (istəyə bağlı), Çanta (istəyə bağlı), checked/unchecked (istəyə bağlı)',
|
||||
'packing.importPlaceholder':
|
||||
'Gigiyena, Diş fırçası\nGeyim, Köynəklər, 200\nSənədlər, Pasport, , Əl yükü\nElektronika, Şarj cihazı, 50, Çamadan, checked',
|
||||
'packing.importCsv': 'CSV/TXT yüklə',
|
||||
'packing.importCsv': 'CSV/TXT/MD yüklə',
|
||||
'packing.export': 'İxrac et',
|
||||
'packing.exportPrint': 'Çap et və ya PDF kimi saxla',
|
||||
'packing.exportMarkdown': 'Markdown yoxlama siyahısı (.md)',
|
||||
'packing.exportCsv': 'İdxal üçün CSV (.csv)',
|
||||
'packing.printItems': 'Elementlər',
|
||||
'packing.printPacked': 'Yığılıb',
|
||||
'packing.importHintMarkdown':
|
||||
'Markdown siyahısı da işləyir: başlıq kateqoriyanı adlandırır, "- [ ]" və "- [x]" elementə çevrilir, adın önündəki "3x" isə miqdarı təyin edir.',
|
||||
'packing.importAction': '{count} elementi idxal et',
|
||||
'packing.importSuccess': '{count} element idxal edildi',
|
||||
'packing.importError': 'İdxal uğursuz oldu',
|
||||
|
||||
@@ -2967,7 +2967,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Compartilhado e Minha lista partem a lista de mala em duas: o fundo comum que todos veem, e a sua. Todos, Abertos e Prontos estreitam a que estiver aberta, e a barra acima conta o que já está na mala.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Aplicar modelo, Importar e Salvar como modelo preenchem ou guardam uma lista sem digitá-la, e o botão vermelho ao lado diz quantos itens estão marcados e os retira.',
|
||||
'Aplicar modelo e Salvar como modelo preenchem ou guardam uma lista sem digitá-la, e os dois ícones ao lado exportam a lista, como impressão, PDF ou arquivo, e importam uma. O botão vermelho diz quantos itens estão marcados e os retira.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'A fazer tem uma barra lateral própria: o cartão de progresso, os filtros Todos, Minhas tarefas, Atrasada e Concluído, Ordenar por Prioridade ou Data de vencimento, e uma linha por lista. Um clique numa tarefa a abre no painel da direita, e Nova tarefa abre o formulário Nova tarefa sobre o meio da tela.',
|
||||
// packing-categories
|
||||
@@ -3034,18 +3034,35 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Colar uma lista de mala inteira',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Transforme de uma só vez uma lista que você já tem em outro lugar em itens de mala.',
|
||||
'help.guide.import-packing-list.step.1': 'Clique em Importar na barra acima da lista.',
|
||||
'help.guide.import-packing-list.step.1': 'Clique no botão de importar com a seta para baixo na barra acima da lista.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Um item por linha: Categoria, Nome, Peso em g (opcional), Bolsa (opcional), checked/unchecked (opcional). O exemplo cinza na caixa mostra as quatro formas.',
|
||||
'Um item por linha: Categoria, Nome, Peso em g (opcional), Bolsa (opcional), checked/unchecked (opcional). O exemplo cinza na caixa mostra as quatro formas. Uma lista em Markdown também funciona: um título dá nome à lista, e "- [ ]" e "- [x]" viram itens.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Ou carregue as linhas de um arquivo com Carregar CSV/TXT. Ele aceita um .csv ou um .txt e substitui o que estiver na caixa.',
|
||||
'Ou carregue as linhas de um arquivo com Carregar CSV/TXT/MD. Ele aceita um .csv, um .txt ou um .md e substitui o que estiver na caixa.',
|
||||
'help.guide.import-packing-list.step.4': 'Clique em Importar. O botão conta as linhas que entendeu.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Cada linha vira uma entrada, na lista que o seu primeiro campo nomeia, e nada do que já estava lá é tocado.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Vírgulas, pontos e vírgulas e tabulações separam campos, e as aspas mantêm um campo inteiro, de modo que “Camisa, azul” continua sendo um nome só. Uma linha com um único valor é só um nome, e uma linha sem lista própria cai em Outros.',
|
||||
'Vírgulas, pontos e vírgulas e tabulações separam campos, e as aspas mantêm um campo inteiro, de modo que “Camisa, azul” continua sendo um nome só. Uma linha com um único valor é só um nome, uma linha sem lista própria cai em Outros, e "3x" antes de um nome define a quantidade.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Uma bolsa nomeada no quarto campo é criada se a viagem ainda não a tiver. Este é o único lugar que carrega pesos e malas em massa; um modelo traz só nomes e listas.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Imprimir ou exportar a lista de mala',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Leve a lista no papel, em PDF ou como arquivo para outro app ou para a próxima viagem.',
|
||||
'help.guide.export-packing-list.step.1': 'Clique no botão de exportar com a seta para cima na barra acima da lista.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Checklist em Markdown (.md) e CSV para importação (.csv) salvam a lista como arquivo na hora.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Clique em Imprimir ou salvar como PDF. A pré-visualização mostra a lista como uma página: a viagem e as datas no topo, depois cada lista como um cartão com uma caixinha para marcar.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Clique em Imprimir ou salvar como PDF abaixo da pré-visualização. O navegador abre a caixa de diálogo de impressão: escolha uma impressora, ou Salvar como PDF para guardar um arquivo.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'A impressão e os arquivos trazem a visão que estiver aberta, Compartilhado ou Minha lista, com as quantidades, os pesos e as marcações.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'O CSV é o formato que Importar lê, malas incluídas, então ele serve como um modelo de mala seu: importe-o na próxima viagem.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'O arquivo Markdown abre como checklist no Obsidian, no Notion ou no GitHub, e volta pelo Importar do mesmo jeito.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Decidir quem vê um item e quem o leva',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -17,7 +17,15 @@ const packing: TranslationStrings = {
|
||||
'packing.importHint': 'Um item por linha. Formato: Categoria, Nome, Peso (g), Bolsa, checked/unchecked (opcional)',
|
||||
'packing.importPlaceholder':
|
||||
'Higiene, Escova de dentes\nRoupas, Camisetas, 200\nDocumentos, Passaporte, , Mala de mão\nEletrônicos, Carregador, 50, Mala, checked',
|
||||
'packing.importCsv': 'Carregar CSV/TXT',
|
||||
'packing.importCsv': 'Carregar CSV/TXT/MD',
|
||||
'packing.export': 'Exportar',
|
||||
'packing.exportPrint': 'Imprimir ou salvar como PDF',
|
||||
'packing.exportMarkdown': 'Checklist em Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV para importação (.csv)',
|
||||
'packing.printItems': 'Itens',
|
||||
'packing.printPacked': 'Na mala',
|
||||
'packing.importHintMarkdown':
|
||||
'Uma lista em Markdown também funciona: um título define a categoria, "- [ ]" e "- [x]" viram itens e "3x" antes do nome define a quantidade.',
|
||||
'packing.importAction': 'Importar {count}',
|
||||
'packing.importSuccess': '{count} itens importados',
|
||||
'packing.importError': 'Falha na importação',
|
||||
|
||||
@@ -3019,7 +3019,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Compartit i La meva llista parteixen la llista d’equipatge en dues: el fons comú que tothom veu, i la teva. Tot, Pendents i Fet estrenyen la que està oberta, i la barra de sobre compta què hi ha preparat.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Aplica una plantilla, Importa i Desa com a plantilla omplen o conserven una llista sense teclejar-la, i el botó vermell del costat diu quants articles estan marcats i els retira.',
|
||||
'Aplica una plantilla i Desa com a plantilla omplen o conserven una llista sense teclejar-la, i les dues icones del costat exporten la llista, com a impressió, PDF o fitxer, i n’importen una. El botó vermell diu quants articles estan marcats i els retira.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Pendents té una barra lateral pròpia: la targeta de progrés, els filtres Tot, Les meves tasques, Vençuda i Fet, Ordena per Prioritat o Data límit, i una fila per categoria. Un clic en una tasca l’obre al panell de la dreta, i Tasca nova obre el formulari Tasca nova sobre el mig de la pantalla.',
|
||||
// packing-categories
|
||||
@@ -3086,18 +3086,37 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Enganxar una llista d’equipatge sencera',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Converteix d’una sola vegada una llista que ja tens en un altre lloc en articles d’equipatge.',
|
||||
'help.guide.import-packing-list.step.1': 'Fes clic a Importa a la barra de sobre la llista.',
|
||||
'help.guide.import-packing-list.step.1':
|
||||
'Fes clic al botó d’importar amb la fletxa cap avall a la barra de sobre la llista.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Un article per línia: Categoria, Nom, Pes en g (opcional), Equipatge (opcional), checked/unchecked (opcional). L’exemple gris del quadre mostra les quatre formes.',
|
||||
'Un article per línia: Categoria, Nom, Pes en g (opcional), Equipatge (opcional), checked/unchecked (opcional). L’exemple gris del quadre mostra les quatre formes. També funciona una llista en Markdown: un títol dona nom a la llista, i "- [ ]" i "- [x]" es converteixen en articles.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'O carrega les línies d’un fitxer amb Carrega CSV/TXT. Accepta un .csv o un .txt i substitueix el que hi hagi al quadre.',
|
||||
'O carrega les línies d’un fitxer amb Carrega CSV/TXT/MD. Accepta un .csv, un .txt o un .md i substitueix el que hi hagi al quadre.',
|
||||
'help.guide.import-packing-list.step.4': 'Fes clic a Importa. El botó compta les línies que ha entès.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Cada línia és una fila, a la llista que nomena el seu primer camp, i res del que ja hi havia no es toca.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Les comes, els punts i comes i els tabuladors separen camps, i les cometes mantenen un camp sencer, de manera que «Camisa, blava» continua sent un sol nom. Una línia amb un sol valor és només un nom, i una línia sense llista pròpia va a parar a Altres.',
|
||||
'Les comes, els punts i comes i els tabuladors separen camps, i les cometes mantenen un camp sencer, de manera que «Camisa, blava» continua sent un sol nom. Una línia amb un sol valor és només un nom, una línia sense llista pròpia va a parar a Altres, i "3x" davant d’un nom en fixa la quantitat.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Una peça d’equipatge anomenada al quart camp es crea si el viatge encara no la té. Aquest és l’únic lloc que carrega pesos i equipatge en bloc; una plantilla només porta noms i llistes.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Imprimir o exportar la llista d’equipatge',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Emporta’t la llista en paper, en PDF o com a fitxer per a una altra aplicació o per al proper viatge.',
|
||||
'help.guide.export-packing-list.step.1':
|
||||
'Fes clic al botó d’exportar amb la fletxa cap amunt a la barra de sobre la llista.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Llista de comprovació en Markdown (.md) i CSV per importar (.csv) desen la llista com a fitxer de seguida.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Fes clic a Imprimeix o desa com a PDF. La previsualització mostra la llista com una pàgina: el viatge i les seves dates a dalt, i després cada llista com una targeta amb una casella per marcar.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Fes clic a Imprimeix o desa com a PDF sota la previsualització. El navegador obre el seu diàleg d’impressió: tria una impressora, o Desa com a PDF per quedar-te un fitxer.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'La impressió i els fitxers recullen la vista que està oberta, Compartit o La meva llista, amb les quantitats, els pesos i les marques.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'El CSV és el format que llegeix Importa, equipatge inclòs, així que serveix com a plantilla d’equipatge pròpia: importa’l al proper viatge.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'El fitxer Markdown s’obre com a llista de comprovació a Obsidian, Notion o GitHub, i torna a entrar igual per Importa.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Decidir qui veu un article i qui el porta',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -9,7 +9,15 @@ const packing: TranslationStrings = {
|
||||
'Un element per línia. Format: Categoria, Nom, Pes en g (opcional), Bossa (opcional), checked/unchecked (opcional)',
|
||||
'packing.importPlaceholder':
|
||||
'Higiene, Raspall de dents\nRoba, Samarretes, 200\nDocuments, Passaport, , Equipatge de mà\nElectrònica, Carregador, 50, Maleta, checked',
|
||||
'packing.importCsv': 'Carrega CSV/TXT',
|
||||
'packing.importCsv': 'Carrega CSV/TXT/MD',
|
||||
'packing.export': 'Exporta',
|
||||
'packing.exportPrint': 'Imprimeix o desa com a PDF',
|
||||
'packing.exportMarkdown': 'Llista de comprovació en Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV per importar (.csv)',
|
||||
'packing.printItems': 'Elements',
|
||||
'packing.printPacked': 'Preparats',
|
||||
'packing.importHintMarkdown':
|
||||
'També funciona una llista en Markdown: un títol indica la categoria, "- [ ]" i "- [x]" es converteixen en elements, i "3x" davant d’un nom en fixa la quantitat.',
|
||||
'packing.importAction': 'Importa {count}',
|
||||
'packing.importSuccess': '{count} elements importats',
|
||||
'packing.importError': 'Error en importar',
|
||||
|
||||
@@ -2897,7 +2897,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Sdílené a Můj seznam dělí seznam věcí na dvě části: fond, který vidí všichni, a ten váš. Vše, K zabalení a Hotovo zužují ten, který je otevřený, a pruh nahoře počítá, co je zabaleno.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Použít šablonu, Importovat a Uložit jako šablonu naplní nebo zachovají seznam bez psaní a červené tlačítko vedle nich říká, kolik položek je zaškrtnutých, a odklidí je.',
|
||||
'Použít šablonu a Uložit jako šablonu naplní nebo zachovají seznam bez psaní a dvě ikony vedle nich seznam exportují, jako výtisk, PDF nebo soubor, nebo nějaký importují. Červené tlačítko říká, kolik položek je zaškrtnutých, a odklidí je.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Úkoly mají vlastní postranní panel: kartu postupu, filtry Vše, Moje úkoly, Po termínu a Hotové, Řadit podle Priorita nebo Termín splnění a jeden řádek na seznam. Kliknutí na úkol ho otevře v panelu vpravo a Přidat nový úkol otevře uprostřed obrazovky formulář Nový úkol.',
|
||||
// packing-categories
|
||||
@@ -2960,18 +2960,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Vložit celý seznam věcí',
|
||||
'help.guide.import-packing-list.goal': 'Proměňte seznam, který už máte jinde, naráz v položky k zabalení.',
|
||||
'help.guide.import-packing-list.step.1': 'Klikněte v pruhu nad seznamem na Importovat.',
|
||||
'help.guide.import-packing-list.step.1': 'Klikněte v pruhu nad seznamem na tlačítko importu se šipkou dolů.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Jedna položka na řádek: Kategorie, Název, Váha v g (volitelné), Zavazadlo (volitelné), checked/unchecked (volitelné). Šedá ukázka v poli ukazuje všechny čtyři podoby.',
|
||||
'Jedna položka na řádek: Kategorie, Název, Váha v g (volitelné), Zavazadlo (volitelné), checked/unchecked (volitelné). Šedá ukázka v poli ukazuje všechny čtyři podoby. Funguje i seznam v Markdownu: nadpis pojmenuje seznam a z "- [ ]" a "- [x]" se stanou položky.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Nebo načtěte řádky ze souboru přes Načíst CSV/TXT. Bere .csv nebo .txt a nahradí to, co je v poli.',
|
||||
'Nebo načtěte řádky ze souboru přes Načíst CSV/TXT/MD. Bere .csv, .txt nebo .md a nahradí to, co je v poli.',
|
||||
'help.guide.import-packing-list.step.4': 'Klikněte na Importovat. Tlačítko počítá řádky, kterým rozumělo.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Každý řádek je jedna položka, v seznamu, který pojmenuje jeho první pole, a ničeho, co tam už bylo, se to nedotkne.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Pole oddělují čárky, středníky i tabulátory a uvozovky drží pole pohromadě, takže „Košile, modrá“ zůstane jedním názvem. Řádek s jedinou hodnotou je jen název a řádek bez vlastního seznamu přistane v Ostatní.',
|
||||
'Pole oddělují čárky, středníky i tabulátory a uvozovky drží pole pohromadě, takže „Košile, modrá“ zůstane jedním názvem. Řádek s jedinou hodnotou je jen název, řádek bez vlastního seznamu přistane v Ostatní a "3x" před názvem nastaví počet.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Zavazadlo pojmenované ve čtvrtém poli se vytvoří, pokud ho cesta ještě nemá. Tohle je jediné místo, které hromadně načítá hmotnosti a zavazadla; šablona přináší jen názvy a seznamy.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Vytisknout nebo exportovat seznam věcí',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Vezměte si seznam s sebou na papíře, jako PDF nebo jako soubor pro jinou aplikaci či další cestu.',
|
||||
'help.guide.export-packing-list.step.1': 'Klikněte v pruhu nad seznamem na tlačítko exportu se šipkou nahoru.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Kontrolní seznam v Markdownu (.md) a CSV pro import (.csv) uloží seznam rovnou jako soubor.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Klikněte na Vytisknout nebo uložit jako PDF. Náhled ukáže seznam jako stránku: nahoře cestu a její termín, pod ní každý seznam jako kartu s políčkem k zaškrtnutí.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Pod náhledem klikněte na Vytisknout nebo uložit jako PDF. Prohlížeč otevře svůj dialog tisku: vyberte tiskárnu, nebo Uložit jako PDF, chcete-li si ponechat soubor.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Výtisk i soubory obsahují otevřený pohled, Sdílené nebo Můj seznam, s počty, hmotnostmi a zaškrtnutím.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV je formát, který čte Importovat, včetně zavazadel, takže poslouží jako vaše vlastní šablona balení: importujte ho do další cesty.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Soubor Markdown se v Obsidian, Notion nebo GitHub otevře jako kontrolní seznam a přes Importovat se stejně tak vrátí zpět.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Určit, kdo položku vidí a kdo ji přináší',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Jedna položka na řádek. Formát: Kategorie, Název, Váha v g (volitelné), Zavazadlo (volitelné), checked/unchecked (volitelné)',
|
||||
'packing.importPlaceholder':
|
||||
'Hygiena, Zubní kartáček\nOblečení, Trička, 200\nDokumenty, Pas, , Příruční zavazadlo\nElektronika, Nabíječka, 50, Kufr, checked',
|
||||
'packing.importCsv': 'Načíst CSV/TXT',
|
||||
'packing.importCsv': 'Načíst CSV/TXT/MD',
|
||||
'packing.export': 'Exportovat',
|
||||
'packing.exportPrint': 'Vytisknout nebo uložit jako PDF',
|
||||
'packing.exportMarkdown': 'Kontrolní seznam v Markdownu (.md)',
|
||||
'packing.exportCsv': 'CSV pro import (.csv)',
|
||||
'packing.printItems': 'Položky',
|
||||
'packing.printPacked': 'Zabaleno',
|
||||
'packing.importHintMarkdown':
|
||||
'Funguje i seznam v Markdownu: nadpis určuje kategorii, z "- [ ]" a "- [x]" se stanou položky a "3x" před názvem nastaví množství.',
|
||||
'packing.importAction': 'Importovat {count}',
|
||||
'packing.importSuccess': '{count} položek importováno',
|
||||
'packing.importError': 'Import se nezdařil',
|
||||
|
||||
@@ -3049,7 +3049,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Gemeinsam und Meine Liste teilen die Packliste in zwei: den Pool, den alle sehen, und deine eigene. Alle, Offen und Erledigt engen die gerade offene ein, und der Balken darüber zählt, was gepackt ist.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Vorlage anwenden, Importieren und Als Vorlage speichern füllen oder bewahren eine Liste, ohne sie abzutippen, und der rote Knopf daneben nennt, wie viele Einträge abgehakt sind, und räumt sie weg.',
|
||||
'Vorlage anwenden und Als Vorlage speichern füllen oder bewahren eine Liste, ohne sie abzutippen, und die zwei Symbole daneben exportieren die Liste, als Ausdruck, als PDF oder als Datei, und importieren eine. Der rote Knopf nennt, wie viele Einträge abgehakt sind, und räumt sie weg.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Aufgaben hat eine eigene Seitenleiste: die Fortschrittskarte, die Filter Alle, Meine Aufgaben, Überfällig und Erledigt, Sortieren nach Priorität oder Fällig am, und eine Zeile pro Liste. Ein Klick auf eine Aufgabe öffnet sie im Panel rechts, und Neue Aufgabe hinzufügen öffnet das Formular Neue Aufgabe über der Mitte des Bildschirms.',
|
||||
// packing-categories
|
||||
@@ -3118,18 +3118,37 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Eine ganze Packliste hineinkopieren',
|
||||
'help.guide.import-packing-list.goal': 'Mach aus einer Liste, die du anderswo schon hast, in einem Zug Packeinträge.',
|
||||
'help.guide.import-packing-list.step.1': 'Klick auf Importieren in der Leiste über der Liste.',
|
||||
'help.guide.import-packing-list.step.1':
|
||||
'Klick auf den Import-Knopf mit dem Pfeil nach unten in der Leiste über der Liste.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Ein Eintrag pro Zeile: Kategorie, Name, Gewicht in g (optional), Tasche (optional), checked/unchecked (optional). Das graue Beispiel im Feld zeigt alle vier Formen.',
|
||||
'Ein Eintrag pro Zeile: Kategorie, Name, Gewicht in g (optional), Tasche (optional), checked/unchecked (optional). Das graue Beispiel im Feld zeigt alle vier Formen. Auch eine Markdown-Liste geht: Eine Überschrift benennt die Liste, und "- [ ]" und "- [x]" werden zu Einträgen.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Oder lad die Zeilen mit CSV/TXT laden aus einer Datei. Es nimmt eine .csv oder eine .txt und ersetzt, was im Feld steht.',
|
||||
'Oder lad die Zeilen mit CSV/TXT/MD laden aus einer Datei. Es nimmt eine .csv, eine .txt oder eine .md und ersetzt, was im Feld steht.',
|
||||
'help.guide.import-packing-list.step.4': 'Klick auf Importieren. Der Knopf zählt die Zeilen, die er verstanden hat.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Jede Zeile wird ein Eintrag, in der Liste, die ihr erstes Feld nennt, und nichts, was schon da war, wird angerührt.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Kommas, Semikolons und Tabs trennen Felder gleichermaßen, und Anführungszeichen halten ein Feld zusammen, sodass „Hemd, blau“ ein Name bleibt. Eine Zeile mit einem einzigen Wert ist nur ein Name, und eine Zeile ohne eigene Liste landet in Sonstiges.',
|
||||
'Kommas, Semikolons und Tabs trennen Felder gleichermaßen, und Anführungszeichen halten ein Feld zusammen, sodass „Hemd, blau“ ein Name bleibt. Eine Zeile mit einem einzigen Wert ist nur ein Name, eine Zeile ohne eigene Liste landet in Sonstiges, und "3x" vor einem Namen setzt die Menge.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Ein im vierten Feld genanntes Gepäckstück wird angelegt, wenn die Reise es noch nicht hat. Nur hier kommen Gewichte und Gepäck in Masse herein; eine Vorlage bringt nur Namen und Listen.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Die Packliste drucken oder exportieren',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Nimm die Liste auf Papier mit, als PDF oder als Datei für eine andere App oder die nächste Reise.',
|
||||
'help.guide.export-packing-list.step.1':
|
||||
'Klick auf den Export-Knopf mit dem Pfeil nach oben in der Leiste über der Liste.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Markdown-Checkliste (.md) und CSV für den Import (.csv) speichern die Liste sofort als Datei.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Klick auf Drucken oder als PDF speichern. Die Vorschau zeigt die Liste als Seite: oben die Reise und ihr Zeitraum, dann jede Liste als Karte mit einem Kästchen zum Abhaken.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Klick unter der Vorschau auf Drucken oder als PDF speichern. Der Browser öffnet seinen Druckdialog: Wähl einen Drucker, oder Als PDF speichern, um eine Datei zu behalten.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Der Ausdruck und die Dateien enthalten die Ansicht, die gerade offen ist, Gemeinsam oder Meine Liste, mit Mengen, Gewichten und Haken.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'Die CSV ist das Format, das Importieren liest, Gepäck inklusive, und taugt damit als deine eigene Packvorlage: Importier sie in die nächste Reise.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Die Markdown-Datei öffnet sich in Obsidian, Notion oder GitHub als Checkliste und kommt über Importieren genauso wieder herein.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Festlegen, wer einen Eintrag sieht und wer ihn mitbringt',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -19,7 +19,15 @@ const packing: TranslationStrings = {
|
||||
'Ein Eintrag pro Zeile. Format: Kategorie, Name, Gewicht in g (optional), Tasche (optional), checked/unchecked (optional)',
|
||||
'packing.importPlaceholder':
|
||||
'Hygiene, Zahnbürste\nKleidung, T-Shirts, 200\nDokumente, Reisepass, , Handgepäck\nElektronik, Ladekabel, 50, Koffer, checked',
|
||||
'packing.importCsv': 'CSV/TXT laden',
|
||||
'packing.importCsv': 'CSV/TXT/MD laden',
|
||||
'packing.export': 'Exportieren',
|
||||
'packing.exportPrint': 'Drucken oder als PDF speichern',
|
||||
'packing.exportMarkdown': 'Markdown-Checkliste (.md)',
|
||||
'packing.exportCsv': 'CSV für den Import (.csv)',
|
||||
'packing.printItems': 'Einträge',
|
||||
'packing.printPacked': 'Gepackt',
|
||||
'packing.importHintMarkdown':
|
||||
'Auch eine Markdown-Liste geht: Eine Überschrift benennt die Kategorie, "- [ ]" und "- [x]" werden zu Einträgen, und "3x" vor einem Namen setzt die Menge.',
|
||||
'packing.importAction': '{count} importieren',
|
||||
'packing.importSuccess': '{count} Einträge importiert',
|
||||
'packing.importError': 'Import fehlgeschlagen',
|
||||
|
||||
@@ -2985,7 +2985,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Shared and My list split the packing list in two: the pool everyone sees, and your own. All, Open and Done narrow whichever one is open, and the bar above counts what is packed.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Apply template, Import and Save as template fill or keep a list without typing it out, and the red button beside them names how many items are ticked and clears them away.',
|
||||
'Apply template and Save as template fill or keep a list without typing it out, and the two icons beside them export the list, as a printout, a PDF or a file, and import one. The red button names how many items are ticked and clears them away.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'To-Do has a sidebar of its own: the progress card, the filters All, My Tasks, Overdue and Done, Sort by Priority or Due date, and one row per list. A click on a task opens it in the pane on the right, and Add new task opens the New task form over the middle of the screen.',
|
||||
// packing-categories
|
||||
@@ -3051,18 +3051,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Paste a whole packing list in',
|
||||
'help.guide.import-packing-list.goal': 'Turn a list you already have somewhere else into packing items in one go.',
|
||||
'help.guide.import-packing-list.step.1': 'Click Import in the bar above the list.',
|
||||
'help.guide.import-packing-list.step.1': 'Click the import button with the down arrow in the bar above the list.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'One item per line: Category, Name, Weight in g (optional), Bag (optional), checked/unchecked (optional). The grey sample in the box shows all four shapes.',
|
||||
'One item per line: Category, Name, Weight in g (optional), Bag (optional), checked/unchecked (optional). The grey sample in the box shows all four shapes. A Markdown list works too: a heading names the list, and "- [ ]" and "- [x]" become items.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Or load the lines from a file with Load CSV/TXT. It takes a .csv or a .txt and replaces whatever is in the box.',
|
||||
'Or load the lines from a file with Load CSV/TXT/MD. It takes a .csv, a .txt or a .md and replaces whatever is in the box.',
|
||||
'help.guide.import-packing-list.step.4': 'Click Import. The button counts the lines it understood.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Every line is a row, in the list its first field names, and nothing that was already there is touched.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Commas, semicolons and tabs all separate fields, and double quotes hold a field together, so "Shirt, blue" stays one name. A line with a single value is just a name, and a line with no list of its own lands in Other.',
|
||||
'Commas, semicolons and tabs all separate fields, and double quotes hold a field together, so "Shirt, blue" stays one name. A line with a single value is just a name, a line with no list of its own lands in Other, and "3x" in front of a name sets the quantity.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'A bag named in the fourth field is created if the trip does not have it yet. This is the only place that loads weights and bags in bulk; a template brings names and lists only.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Print or export the packing list',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Take the list along on paper, as a PDF, or as a file for another app or the next trip.',
|
||||
'help.guide.export-packing-list.step.1': 'Click the export button with the up arrow in the bar above the list.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Markdown checklist (.md) and CSV for import (.csv) save the list as a file right away.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Click Print or save as PDF. The preview shows the list as a page: the trip and its dates on top, then every list as a card with a box to tick.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Click Print or save as PDF under the preview. The browser opens its print dialog: pick a printer, or Save as PDF to keep a file.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'The printout and the files hold the view that is open, Shared or My list, with the quantities, weights and checkmarks.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'The CSV is the format Import reads, bags included, so it works as a packing template of your own: import it into the next trip.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'The Markdown file opens as a checklist in Obsidian, Notion or GitHub, and comes back in through Import just the same.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Decide who sees an item and who brings it',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'One item per line. Format: Category, Name, Weight in g (optional), Bag (optional), checked/unchecked (optional)',
|
||||
'packing.importPlaceholder':
|
||||
'Hygiene, Toothbrush\nClothing, T-Shirts, 200\nDocuments, Passport, , Carry-on\nElectronics, Charger, 50, Suitcase, checked',
|
||||
'packing.importCsv': 'Load CSV/TXT',
|
||||
'packing.importCsv': 'Load CSV/TXT/MD',
|
||||
'packing.export': 'Export',
|
||||
'packing.exportPrint': 'Print or save as PDF',
|
||||
'packing.exportMarkdown': 'Markdown checklist (.md)',
|
||||
'packing.exportCsv': 'CSV for import (.csv)',
|
||||
'packing.printItems': 'Items',
|
||||
'packing.printPacked': 'Packed',
|
||||
'packing.importHintMarkdown':
|
||||
'A Markdown list works too: a heading names the category, "- [ ]" and "- [x]" become items, and "3x" in front of a name sets the quantity.',
|
||||
'packing.importAction': 'Import {count}',
|
||||
'packing.importSuccess': '{count} items imported',
|
||||
'packing.importError': 'Import failed',
|
||||
|
||||
@@ -3031,7 +3031,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Compartido y Mi lista parten la lista de equipaje en dos: el fondo común que ve todo el mundo, y la tuya. Todo, Pendientes y Hecho estrechan la que esté abierta, y la barra de arriba cuenta lo que está preparado.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Aplicar plantilla, Importar y Guardar como plantilla llenan o conservan una lista sin teclearla, y el botón rojo de al lado dice cuántos artículos están marcados y los quita.',
|
||||
'Aplicar plantilla y Guardar como plantilla llenan o conservan una lista sin teclearla, y los dos iconos de al lado exportan la lista, como impresión, PDF o archivo, e importan una. El botón rojo dice cuántos artículos están marcados y los quita.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Por hacer tiene su propia barra lateral: la tarjeta de progreso, los filtros Todo, Mis tareas, Vencida y Hecho, Ordenar por Prioridad o Fecha límite, y una fila por lista. Un clic en una tarea la abre en el panel de la derecha, y Nueva tarea abre el formulario Nueva tarea sobre el centro de la pantalla.',
|
||||
// packing-categories
|
||||
@@ -3100,18 +3100,37 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Pegar una lista de equipaje entera',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Convierte de una vez una lista que ya tienes en otro sitio en artículos de equipaje.',
|
||||
'help.guide.import-packing-list.step.1': 'Haz clic en Importar en la barra de encima de la lista.',
|
||||
'help.guide.import-packing-list.step.1':
|
||||
'Haz clic en el botón de importar con la flecha hacia abajo en la barra de encima de la lista.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Un artículo por línea: Categoría, Nombre, Peso en g (opcional), Equipaje (opcional), checked/unchecked (opcional). El ejemplo gris del recuadro muestra las cuatro formas.',
|
||||
'Un artículo por línea: Categoría, Nombre, Peso en g (opcional), Equipaje (opcional), checked/unchecked (opcional). El ejemplo gris del recuadro muestra las cuatro formas. También sirve una lista en Markdown: un encabezado da nombre a la lista, y "- [ ]" y "- [x]" se convierten en artículos.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'O carga las líneas desde un archivo con Cargar CSV/TXT. Acepta un .csv o un .txt y sustituye lo que haya en el recuadro.',
|
||||
'O carga las líneas desde un archivo con Cargar CSV/TXT/MD. Acepta un .csv, un .txt o un .md y sustituye lo que haya en el recuadro.',
|
||||
'help.guide.import-packing-list.step.4': 'Haz clic en Importar. El botón cuenta las líneas que ha entendido.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Cada línea es una fila, en la lista que nombra su primer campo, y nada de lo que ya había se toca.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Las comas, los puntos y comas y los tabuladores separan campos, y las comillas mantienen un campo unido, de modo que «Camisa, azul» sigue siendo un solo nombre. Una línea con un único valor es solo un nombre, y una línea sin lista propia cae en Otros.',
|
||||
'Las comas, los puntos y comas y los tabuladores separan campos, y las comillas mantienen un campo unido, de modo que «Camisa, azul» sigue siendo un solo nombre. Una línea con un único valor es solo un nombre, una línea sin lista propia cae en Otros, y "3x" delante de un nombre fija la cantidad.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Un equipaje nombrado en el cuarto campo se crea si el viaje aún no lo tiene. Este es el único sitio que carga pesos y equipaje en bloque; una plantilla trae solo nombres y listas.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Imprimir o exportar la lista de equipaje',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Llévate la lista en papel, en PDF o como archivo para otra aplicación o para el próximo viaje.',
|
||||
'help.guide.export-packing-list.step.1':
|
||||
'Haz clic en el botón de exportar con la flecha hacia arriba en la barra de encima de la lista.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Lista de verificación en Markdown (.md) y CSV para importar (.csv) guardan la lista como archivo al instante.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Haz clic en Imprimir o guardar como PDF. La vista previa muestra la lista como una página: el viaje y sus fechas arriba, y luego cada lista como una tarjeta con una casilla para marcar.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Haz clic en Imprimir o guardar como PDF debajo de la vista previa. El navegador abre su diálogo de impresión: elige una impresora, o Guardar como PDF para quedarte con un archivo.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'La impresión y los archivos recogen la vista que está abierta, Compartido o Mi lista, con las cantidades, los pesos y las marcas.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'El CSV es el formato que lee Importar, equipaje incluido, así que sirve como plantilla de equipaje propia: impórtalo en el próximo viaje.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'El archivo Markdown se abre como lista de verificación en Obsidian, Notion o GitHub, y vuelve a entrar igual por Importar.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Decidir quién ve un artículo y quién lo lleva',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Un elemento por línea. Formato: Categoría, Nombre, Peso en g (opcional), Bolsa (opcional), checked/unchecked (opcional)',
|
||||
'packing.importPlaceholder':
|
||||
'Higiene, Cepillo de dientes\nRopa, Camisetas, 200\nDocumentos, Pasaporte, , Equipaje de mano\nElectrónica, Cargador, 50, Maleta, checked',
|
||||
'packing.importCsv': 'Cargar CSV/TXT',
|
||||
'packing.importCsv': 'Cargar CSV/TXT/MD',
|
||||
'packing.export': 'Exportar',
|
||||
'packing.exportPrint': 'Imprimir o guardar como PDF',
|
||||
'packing.exportMarkdown': 'Lista de verificación en Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV para importar (.csv)',
|
||||
'packing.printItems': 'Artículos',
|
||||
'packing.printPacked': 'Preparados',
|
||||
'packing.importHintMarkdown':
|
||||
'También sirve una lista en Markdown: un encabezado indica la categoría, "- [ ]" y "- [x]" se convierten en artículos y "3x" delante de un nombre fija la cantidad.',
|
||||
'packing.importAction': 'Importar {count}',
|
||||
'packing.importSuccess': '{count} elementos importados',
|
||||
'packing.importError': 'Error al importar',
|
||||
|
||||
@@ -3043,7 +3043,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Jagatud ja Minu nimekiri jagavad pakkimisnimekirja kaheks: ühine osa, mida kõik näevad, ja sinu oma. Kõik, Avatud ja Valmis kitsendavad parajasti avatud nimekirja ning ülal olev riba loendab pakitut.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Rakenda mall, Impordi ja Salvesta mallina täidavad või salvestavad nimekirja ilma seda käsitsi sisestamata ning nende kõrval olev punane nupp näitab, mitu eset on märgitud, ja eemaldab need.',
|
||||
'Rakenda mall ja Salvesta mallina täidavad või salvestavad nimekirja ilma seda käsitsi sisestamata ning nende kõrval olevad kaks ikooni ekspordivad nimekirja väljatrükina, PDF-ina või failina ja impordivad nimekirja. Punane nupp näitab, mitu eset on märgitud, ja eemaldab need.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Ülesannetel on oma külgriba: edenemise kaart, filtrid Kõik, Minu ülesanded, Üle tähtaja ja Valmis, Sordi valikutega Tähtsus või Tähtaeg ning üks rida iga nimekirja kohta. Klõps ülesandel avab selle paremal paanil ja Lisa uus ülesanne avab ekraani keskel vormi Uus ülesanne.',
|
||||
// packing-categories
|
||||
@@ -3108,18 +3108,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Kleebi sisse terve pakkimisnimekiri',
|
||||
'help.guide.import-packing-list.goal': 'Muuda mujal olemasolev nimekiri korraga pakkimisesemeteks.',
|
||||
'help.guide.import-packing-list.step.1': 'Klõpsa nimekirja kohal olevas ribas nuppu Impordi.',
|
||||
'help.guide.import-packing-list.step.1': 'Klõpsa nimekirja kohal olevas ribas allanoolega impordinuppu.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Üks ese rea kohta: Kategooria, Nimi, Kaal grammides (valikuline), Kott (valikuline), checked/unchecked (valikuline). Kastis olev hall näidis näitab kõiki nelja kuju.',
|
||||
'Üks ese rea kohta: Kategooria, Nimi, Kaal grammides (valikuline), Kott (valikuline), checked/unchecked (valikuline). Kastis olev hall näidis näitab kõiki nelja kuju. Sobib ka Markdowni loend: pealkiri annab nimekirjale nime ning "- [ ]" ja "- [x]" muutuvad esemeteks.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Või laadi read failist nupuga Laadi CSV/TXT. See võtab vastu .csv- või .txt-faili ja asendab kogu kasti sisu.',
|
||||
'Või laadi read failist nupuga Laadi CSV/TXT/MD. See võtab vastu .csv-, .txt- või .md-faili ja asendab kogu kasti sisu.',
|
||||
'help.guide.import-packing-list.step.4': 'Klõpsa nuppu Impordi. Nupp näitab, mitu rida see ära tundis.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Igast reast saab kirje nimekirjas, mille nimi on rea esimeses väljas, ja olemasolevat sisu ei muudeta.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Välju eraldavad komad, semikoolonid ja tabulaatorid ning jutumärgid hoiavad välja koos, nii et "Särk, sinine" jääb üheks nimeks. Ühe väärtusega rida on lihtsalt nimi ja ilma oma nimekirjata rida satub nimekirja Muu.',
|
||||
'Välju eraldavad komad, semikoolonid ja tabulaatorid ning jutumärgid hoiavad välja koos, nii et "Särk, sinine" jääb üheks nimeks. Ühe väärtusega rida on lihtsalt nimi, ilma oma nimekirjata rida satub nimekirja Muu ja "3x" nime ees määrab koguse.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Neljandas väljas nimetatud kott luuakse, kui reisil seda veel pole. See on ainus koht, kus saab kaale ja kotte hulgi laadida; mall toob ainult nimed ja nimekirjad.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Prindi või ekspordi pakkimisnimekiri',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Võta nimekiri kaasa paberil, PDF-ina või failina teise rakenduse või järgmise reisi jaoks.',
|
||||
'help.guide.export-packing-list.step.1': 'Klõpsa nimekirja kohal olevas ribas ülesnoolega ekspordinuppu.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Markdowni kontrollnimekiri (.md) ja CSV importimiseks (.csv) salvestavad nimekirja kohe failina.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Klõpsa valikut Prindi või salvesta PDF-ina. Eelvaade näitab nimekirja lehena: üleval reis ja selle kuupäevad, seejärel iga nimekiri kaardina, millel on märkeruut.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Klõpsa eelvaate all nuppu Prindi või salvesta PDF-ina. Brauser avab oma printimisakna: vali printer või Salvesta PDF-ina, et fail alles hoida.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Väljatrükk ja failid sisaldavad parajasti avatud vaadet, Ühine või Minu nimekiri, koos koguste, kaalude ja linnukestega.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV on vorming, mida Impordi loeb, kotid kaasa arvatud, nii et see toimib sinu enda pakkimismallina: impordi see järgmisse reisi.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Markdowni fail avaneb Obsidianis, Notionis või GitHubis kontrollnimekirjana ja tuleb nupu Impordi kaudu samamoodi tagasi.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Otsusta, kes eset näeb ja kes selle kaasa võtab',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Üks ese rea kohta. Vorming: kategooria, nimi, kaal grammides (valikuline), kott (valikuline), checked/unchecked (valikuline)',
|
||||
'packing.importPlaceholder':
|
||||
'Hügieen, Hambahari\nRiided, T-särgid, 200\nDokumendid, Pass, , Käsipagas\nElektroonika, Laadija, 50, Kohver, checked',
|
||||
'packing.importCsv': 'Laadi CSV/TXT',
|
||||
'packing.importCsv': 'Laadi CSV/TXT/MD',
|
||||
'packing.export': 'Ekspordi',
|
||||
'packing.exportPrint': 'Prindi või salvesta PDF-ina',
|
||||
'packing.exportMarkdown': 'Markdowni kontrollnimekiri (.md)',
|
||||
'packing.exportCsv': 'CSV importimiseks (.csv)',
|
||||
'packing.printItems': 'Esemed',
|
||||
'packing.printPacked': 'Pakitud',
|
||||
'packing.importHintMarkdown':
|
||||
'Sobib ka Markdowni loend: pealkiri määrab kategooria, "- [ ]" ja "- [x]" muutuvad esemeteks ning "3x" nime ees määrab koguse.',
|
||||
'packing.importAction': 'Impordi {count}',
|
||||
'packing.importSuccess': '{count} eset imporditud',
|
||||
'packing.importError': 'Import ebaõnnestus',
|
||||
|
||||
@@ -3090,7 +3090,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Partagé et Ma liste coupent la liste de bagages en deux : le fonds commun que tout le monde voit, et la vôtre. Tous, À faire et Fait restreignent celle qui est ouverte, et la barre au-dessus compte ce qui est emballé.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Appliquer un modèle, Importer et Enregistrer comme modèle remplissent ou conservent une liste sans la retaper, et le bouton rouge à côté indique combien d’articles sont cochés et les efface.',
|
||||
'Appliquer un modèle et Enregistrer comme modèle remplissent ou conservent une liste sans la retaper, et les deux icônes à côté exportent la liste, en impression, en PDF ou en fichier, et en importent une. Le bouton rouge indique combien d’articles sont cochés et les efface.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'À faire a sa propre barre latérale : la carte de progression, les filtres Tout, Mes tâches, En retard et Terminé, Trier par Priorité ou Date d’échéance, et une ligne par liste. Un clic sur une tâche l’ouvre dans le panneau de droite, et Nouvelle tâche ouvre le formulaire Nouvelle tâche au milieu de l’écran.',
|
||||
// packing-categories
|
||||
@@ -3159,18 +3159,37 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Coller une liste de bagages entière',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Transformez d’un seul coup une liste que vous avez déjà ailleurs en articles de bagages.',
|
||||
'help.guide.import-packing-list.step.1': 'Cliquez sur Importer dans la barre au-dessus de la liste.',
|
||||
'help.guide.import-packing-list.step.1':
|
||||
'Cliquez sur le bouton d’import avec la flèche vers le bas dans la barre au-dessus de la liste.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Un article par ligne : Catégorie, Nom, Poids en g (facultatif), Bagage (facultatif), checked/unchecked (facultatif). L’exemple gris dans le cadre montre les quatre formes.',
|
||||
'Un article par ligne : Catégorie, Nom, Poids en g (facultatif), Bagage (facultatif), checked/unchecked (facultatif). L’exemple gris dans le cadre montre les quatre formes. Une liste Markdown fonctionne aussi : un titre nomme la liste, et "- [ ]" et "- [x]" deviennent des articles.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Ou chargez les lignes depuis un fichier avec Charger CSV/TXT. Il prend un .csv ou un .txt et remplace ce qui est dans le cadre.',
|
||||
'Ou chargez les lignes depuis un fichier avec Charger CSV/TXT/MD. Il prend un .csv, un .txt ou un .md et remplace ce qui est dans le cadre.',
|
||||
'help.guide.import-packing-list.step.4': 'Cliquez sur Importer. Le bouton compte les lignes qu’il a comprises.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Chaque ligne devient un article, dans la liste que nomme son premier champ, et rien de ce qui était déjà là n’est touché.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Les virgules, les points-virgules et les tabulations séparent les champs de la même façon, et les guillemets tiennent un champ ensemble, de sorte que « Chemise, bleue » reste un seul nom. Une ligne avec une seule valeur n’est qu’un nom, et une ligne sans liste à elle atterrit dans Autre.',
|
||||
'Les virgules, les points-virgules et les tabulations séparent les champs de la même façon, et les guillemets tiennent un champ ensemble, de sorte que « Chemise, bleue » reste un seul nom. Une ligne avec une seule valeur n’est qu’un nom, une ligne sans liste à elle atterrit dans Autre, et "3x" devant un nom fixe la quantité.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Un bagage nommé dans le quatrième champ est créé si le voyage ne l’a pas encore. C’est le seul endroit qui charge des poids et des bagages en masse ; un modèle n’apporte que des noms et des listes.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Imprimer ou exporter la liste de bagages',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Emportez la liste sur papier, en PDF ou sous forme de fichier pour une autre application ou le prochain voyage.',
|
||||
'help.guide.export-packing-list.step.1':
|
||||
'Cliquez sur le bouton d’export avec la flèche vers le haut dans la barre au-dessus de la liste.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Checklist Markdown (.md) et CSV pour l’import (.csv) enregistrent aussitôt la liste dans un fichier.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Cliquez sur Imprimer ou enregistrer en PDF. L’aperçu montre la liste sous forme de page : le voyage et ses dates en haut, puis chaque liste sous forme de carte avec une case à cocher.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Cliquez sur Imprimer ou enregistrer en PDF sous l’aperçu. Le navigateur ouvre sa boîte de dialogue d’impression : choisissez une imprimante, ou Enregistrer au format PDF pour garder un fichier.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'L’impression et les fichiers reprennent la vue ouverte, Partagé ou Ma liste, avec les quantités, les poids et les coches.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'Le CSV est le format que lit Importer, bagages compris, il sert donc de modèle de bagages bien à vous : importez-le dans le prochain voyage.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Le fichier Markdown s’ouvre comme une checklist dans Obsidian, Notion ou GitHub, et revient de la même façon par Importer.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Décider qui voit un article et qui l’apporte',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Un élément par ligne. Format : Catégorie, Nom, Poids en g (facultatif), Bagage (facultatif), checked/unchecked (facultatif)',
|
||||
'packing.importPlaceholder':
|
||||
'Hygiène, Brosse à dents\nVêtements, T-shirts, 200\nDocuments, Passeport, , Bagage cabine\nÉlectronique, Chargeur, 50, Valise, checked',
|
||||
'packing.importCsv': 'Charger CSV/TXT',
|
||||
'packing.importCsv': 'Charger CSV/TXT/MD',
|
||||
'packing.export': 'Exporter',
|
||||
'packing.exportPrint': 'Imprimer ou enregistrer en PDF',
|
||||
'packing.exportMarkdown': 'Checklist Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV pour l’import (.csv)',
|
||||
'packing.printItems': 'Articles',
|
||||
'packing.printPacked': 'Emballés',
|
||||
'packing.importHintMarkdown':
|
||||
'Une liste Markdown fonctionne aussi : un titre indique la catégorie, "- [ ]" et "- [x]" deviennent des articles, et "3x" devant un nom fixe la quantité.',
|
||||
'packing.importAction': 'Importer {count}',
|
||||
'packing.importSuccess': '{count} éléments importés',
|
||||
'packing.importError': "Échec de l'import",
|
||||
|
||||
@@ -3099,7 +3099,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Το Κοινό και η Η λίστα μου χωρίζουν τη λίστα αποσκευών στα δύο: τη δεξαμενή που βλέπουν όλοι και τη δική σας. Τα Όλα, Ανοιχτά και Ολοκληρωμένα στενεύουν όποιο είναι ανοιχτό, και η μπάρα από πάνω μετρά τι έχει συσκευαστεί.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Η Εφαρμογή προτύπου, η Εισαγωγή και η Αποθήκευση ως πρότυπο γεμίζουν ή κρατούν μια λίστα χωρίς να τη γράψετε, και το κόκκινο κουμπί δίπλα τους λέει πόσα αντικείμενα είναι επιλεγμένα και τα καθαρίζει.',
|
||||
'Η Εφαρμογή προτύπου και η Αποθήκευση ως πρότυπο γεμίζουν ή κρατούν μια λίστα χωρίς να τη γράψετε, και τα δύο εικονίδια δίπλα τους εξάγουν τη λίστα, ως εκτύπωση, ως PDF ή ως αρχείο, και εισάγουν μία. Το κόκκινο κουμπί λέει πόσα αντικείμενα είναι επιλεγμένα και τα καθαρίζει.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Οι Εργασίες έχουν δική τους πλαϊνή στήλη: την κάρτα προόδου, τα φίλτρα Όλα, Οι Εργασίες μου, Εκπρόθεσμα και Ολοκληρωμένα, την Ταξινόμηση κατά Προτεραιότητα ή Ημερομηνία λήξης, και μια σειρά ανά λίστα. Ένα κλικ σε μια εργασία την ανοίγει στο παράθυρο δεξιά, και η Προσθήκη νέας εργασίας ανοίγει τη φόρμα Νέα εργασία πάνω από το μέσο της οθόνης.',
|
||||
// packing-categories
|
||||
@@ -3168,18 +3168,37 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Επικολλήστε ολόκληρη λίστα αποσκευών',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Μετατρέψτε μια λίστα που έχετε ήδη κάπου αλλού σε αντικείμενα αποσκευών με μία κίνηση.',
|
||||
'help.guide.import-packing-list.step.1': 'Κάντε κλικ στην Εισαγωγή στη μπάρα πάνω από τη λίστα.',
|
||||
'help.guide.import-packing-list.step.1':
|
||||
'Κάντε κλικ στο κουμπί εισαγωγής με το βέλος προς τα κάτω στη μπάρα πάνω από τη λίστα.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Ένα αντικείμενο ανά γραμμή: Κατηγορία, Όνομα, Βάρος σε g (προαιρετικό), Τσάντα (προαιρετικό), checked/unchecked (προαιρετικό). Το γκρι δείγμα στο πλαίσιο δείχνει και τις τέσσερις μορφές.',
|
||||
'Ένα αντικείμενο ανά γραμμή: Κατηγορία, Όνομα, Βάρος σε g (προαιρετικό), Τσάντα (προαιρετικό), checked/unchecked (προαιρετικό). Το γκρι δείγμα στο πλαίσιο δείχνει και τις τέσσερις μορφές. Λειτουργεί και μια λίστα Markdown: μια επικεφαλίδα δίνει όνομα στη λίστα, και τα "- [ ]" και "- [x]" γίνονται αντικείμενα.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Ή φορτώστε τις γραμμές από αρχείο με τη Φόρτωση CSV/TXT. Δέχεται ένα .csv ή ένα .txt και αντικαθιστά ό,τι υπάρχει στο πλαίσιο.',
|
||||
'Ή φορτώστε τις γραμμές από αρχείο με τη Φόρτωση CSV/TXT/MD. Δέχεται ένα .csv, ένα .txt ή ένα .md και αντικαθιστά ό,τι υπάρχει στο πλαίσιο.',
|
||||
'help.guide.import-packing-list.step.4': 'Κάντε κλικ στην Εισαγωγή. Το κουμπί μετρά τις γραμμές που κατάλαβε.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Κάθε γραμμή γίνεται μια σειρά, στη λίστα που ονομάζει το πρώτο της πεδίο, και τίποτα από όσα υπήρχαν ήδη δεν πειράζεται.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Κόμματα, ερωτηματικά (;) και στηλοθέτες χωρίζουν όλα τα πεδία, και τα διπλά εισαγωγικά κρατούν ένα πεδίο μαζί, ώστε το «Πουκάμισο, μπλε» να μείνει ένα όνομα. Μια γραμμή με μία μόνο τιμή είναι απλώς ένα όνομα, και μια γραμμή χωρίς δική της λίστα προσγειώνεται στα Άλλα.',
|
||||
'Κόμματα, ερωτηματικά (;) και στηλοθέτες χωρίζουν όλα τα πεδία, και τα διπλά εισαγωγικά κρατούν ένα πεδίο μαζί, ώστε το «Πουκάμισο, μπλε» να μείνει ένα όνομα. Μια γραμμή με μία μόνο τιμή είναι απλώς ένα όνομα, μια γραμμή χωρίς δική της λίστα προσγειώνεται στα Άλλα, και το "3x" μπροστά από ένα όνομα ορίζει την ποσότητα.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Μια τσάντα που ονομάζεται στο τέταρτο πεδίο δημιουργείται αν το ταξίδι δεν την έχει ακόμη. Αυτό είναι το μόνο σημείο που φορτώνει βάρη και τσάντες μαζικά· ένα πρότυπο φέρνει μόνο ονόματα και λίστες.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Εκτυπώστε ή εξαγάγετε τη λίστα αποσκευών',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Πάρτε τη λίστα μαζί σας σε χαρτί, ως PDF ή ως αρχείο για άλλη εφαρμογή ή για το επόμενο ταξίδι.',
|
||||
'help.guide.export-packing-list.step.1':
|
||||
'Κάντε κλικ στο κουμπί εξαγωγής με το βέλος προς τα πάνω στη μπάρα πάνω από τη λίστα.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Η Λίστα ελέγχου Markdown (.md) και το CSV για εισαγωγή (.csv) αποθηκεύουν αμέσως τη λίστα ως αρχείο.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Κάντε κλικ στην Εκτύπωση ή αποθήκευση ως PDF. Η προεπισκόπηση δείχνει τη λίστα ως σελίδα: επάνω το ταξίδι και οι ημερομηνίες του, και μετά κάθε λίστα ως κάρτα με ένα πλαίσιο για τσεκάρισμα.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Κάντε κλικ στην Εκτύπωση ή αποθήκευση ως PDF κάτω από την προεπισκόπηση. Ο περιηγητής ανοίγει το δικό του παράθυρο εκτύπωσης: διαλέξτε εκτυπωτή, ή Αποθήκευση ως PDF για να κρατήσετε ένα αρχείο.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Η εκτύπωση και τα αρχεία περιέχουν την προβολή που είναι ανοιχτή, Κοινό ή Η λίστα μου, με τις ποσότητες, τα βάρη και τα τσεκαρίσματα.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'Το CSV είναι η μορφή που διαβάζει η Εισαγωγή, μαζί με τις τσάντες, οπότε λειτουργεί ως δικό σας πρότυπο αποσκευών: εισαγάγετέ το στο επόμενο ταξίδι.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Το αρχείο Markdown ανοίγει ως λίστα ελέγχου στο Obsidian, στο Notion ή στο GitHub, και επιστρέφει με τον ίδιο τρόπο μέσω της Εισαγωγής.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Αποφασίστε ποιος βλέπει ένα αντικείμενο και ποιος το φέρνει',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -19,7 +19,15 @@ const packing: TranslationStrings = {
|
||||
'Ένα αντικείμενο ανά γραμμή. Μορφή: Κατηγορία, Όνομα, Βάρος σε g (προαιρετικό), Τσάντα (προαιρετικό), checked/unchecked (προαιρετικό)',
|
||||
'packing.importPlaceholder':
|
||||
'Υγιεινή, Οδοντόβουρτσα\nΡούχα, T-Shirts, 200\nΈγγραφα, Διαβατήριο, , Χειραποσκευή\nΗλεκτρονικά, Φορτιστής, 50, Βαλίτσα, checked',
|
||||
'packing.importCsv': 'Φόρτωση CSV/TXT',
|
||||
'packing.importCsv': 'Φόρτωση CSV/TXT/MD',
|
||||
'packing.export': 'Εξαγωγή',
|
||||
'packing.exportPrint': 'Εκτύπωση ή αποθήκευση ως PDF',
|
||||
'packing.exportMarkdown': 'Λίστα ελέγχου Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV για εισαγωγή (.csv)',
|
||||
'packing.printItems': 'Αντικείμενα',
|
||||
'packing.printPacked': 'Συσκευασμένα',
|
||||
'packing.importHintMarkdown':
|
||||
'Λειτουργεί και μια λίστα Markdown: μια επικεφαλίδα ορίζει την κατηγορία, τα "- [ ]" και "- [x]" γίνονται αντικείμενα και το "3x" μπροστά από ένα όνομα ορίζει την ποσότητα.',
|
||||
'packing.importAction': 'Εισαγωγή {count}',
|
||||
'packing.importSuccess': '{count} αντικείμενα εισήχθησαν',
|
||||
'packing.importError': 'Η εισαγωγή απέτυχε',
|
||||
|
||||
@@ -3028,7 +3028,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'A Megosztott és a Saját lista kettéosztja a csomagolási listát: a mindenki által látott közösre és a sajátodra. Az Összes, a Nyitott és a Kész szűkíti azt, amelyik nyitva van, a fenti sáv pedig számolja, mi van becsomagolva.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'A Sablon alkalmazása, az Importálás és a Mentés sablonként gépelés nélkül tölti fel vagy őrzi meg a listát, a mellettük lévő piros gomb pedig megmondja, hány tétel van kipipálva, és eltakarítja őket.',
|
||||
'A Sablon alkalmazása és a Mentés sablonként gépelés nélkül tölti fel vagy őrzi meg a listát, a mellettük lévő két ikon pedig exportálja a listát, nyomtatásként, PDF-ként vagy fájlként, illetve importál egyet. A piros gomb megmondja, hány tétel van kipipálva, és eltakarítja őket.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'A Teendőknek saját oldalsávja van: a haladási kártya, a Mind, a Saját feladataim, a Lejárt és a Kész szűrők, a Rendezés Prioritás vagy Határidő szerint, és listánként egy sor. Egy feladatra kattintva a jobb oldali panelen nyílik meg, az Új feladat pedig a képernyő közepén nyitja meg az Új feladat űrlapot.',
|
||||
// packing-categories
|
||||
@@ -3096,19 +3096,38 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Egy egész csomagolási lista beillesztése',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Alakítsd át egy mozdulattal csomagolási tételekké azt a listát, ami máshol már megvan.',
|
||||
'help.guide.import-packing-list.step.1': 'Kattints a lista fölötti sávban az Importálás gombra.',
|
||||
'help.guide.import-packing-list.step.1':
|
||||
'Kattints a lista fölötti sávban a lefelé mutató nyíllal jelölt importálás gombra.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Soronként egy tétel: Kategória, Név, Súly g-ban (opcionális), Táska (opcionális), checked/unchecked (opcionális). A mezőben lévő szürke minta mind a négy alakot mutatja.',
|
||||
'Soronként egy tétel: Kategória, Név, Súly g-ban (opcionális), Táska (opcionális), checked/unchecked (opcionális). A mezőben lévő szürke minta mind a négy alakot mutatja. Markdown-lista is használható: a címsor adja a lista nevét, a "- [ ]" és "- [x]" sorokból pedig tételek lesznek.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Vagy töltsd be a sorokat fájlból a CSV/TXT betöltése gombbal. Egy .csv vagy egy .txt fájlt fogad, és lecseréli, ami a mezőben van.',
|
||||
'Vagy töltsd be a sorokat fájlból a CSV/TXT/MD betöltése gombbal. Egy .csv, egy .txt vagy egy .md fájlt fogad, és lecseréli, ami a mezőben van.',
|
||||
'help.guide.import-packing-list.step.4':
|
||||
'Kattints az Importálás gombra. A gomb megszámolja a sorokat, amelyeket megértett.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Minden sorból egy tétel lesz, abban a listában, amit az első mezője megnevez, és semmihez nem nyúl, ami már ott volt.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'A mezőket vessző, pontosvessző és tabulátor is elválasztja, az idézőjelek pedig egyben tartanak egy mezőt, így az „Ing, kék” egyetlen név marad. Az egyetlen értékből álló sor csak egy név, a saját lista nélküli sor pedig az Egyéb listába kerül.',
|
||||
'A mezőket vessző, pontosvessző és tabulátor is elválasztja, az idézőjelek pedig egyben tartanak egy mezőt, így az „Ing, kék” egyetlen név marad. Az egyetlen értékből álló sor csak egy név, a saját lista nélküli sor az Egyéb listába kerül, a név előtti "3x" pedig a mennyiséget adja meg.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'A negyedik mezőben megnevezett táska létrejön, ha az utazásnak még nincs meg. Ez az egyetlen hely, ami tömegesen tölt be súlyokat és táskákat; egy sablon csak neveket és listákat hoz.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'A csomagolási lista nyomtatása vagy exportálása',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Vidd magaddal a listát papíron, PDF-ként vagy fájlként egy másik alkalmazásba vagy a következő utazásra.',
|
||||
'help.guide.export-packing-list.step.1':
|
||||
'Kattints a lista fölötti sávban a felfelé mutató nyíllal jelölt exportálás gombra.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'A Markdown ellenőrzőlista (.md) és a CSV importáláshoz (.csv) azonnal fájlba menti a listát.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Kattints a Nyomtatás vagy mentés PDF-ként elemre. Az előnézet oldalként mutatja a listát: felül az utazás és a dátumai, alatta minden lista kártyaként, egy kipipálható négyzettel.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Kattints az előnézet alatt a Nyomtatás vagy mentés PDF-ként gombra. A böngésző megnyitja a nyomtatási párbeszédablakát: válassz nyomtatót, vagy a Mentés PDF-ként lehetőséget, ha fájlt szeretnél megtartani.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'A nyomat és a fájlok a nyitott nézetet tartalmazzák, a Megosztott vagy a Saját lista nézetet, a mennyiségekkel, súlyokkal és pipákkal együtt.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'A CSV az a formátum, amit az Importálás beolvas, a táskákkal együtt, így saját csomagolási sablonként is működik: importáld a következő utazásba.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'A Markdown-fájl ellenőrzőlistaként nyílik meg az Obsidianban, a Notionban vagy a GitHubon, és az Importálással ugyanúgy visszahozható.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Döntsd el, ki látja a tételt és ki hozza',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -19,7 +19,15 @@ const packing: TranslationStrings = {
|
||||
'Soronként egy tétel. Formátum: Kategória, Név, Súly g-ban (opcionális), Táska (opcionális), checked/unchecked (opcionális)',
|
||||
'packing.importPlaceholder':
|
||||
'Tisztálkodás, Fogkefe\nRuházat, Pólók, 200\nDokumentumok, Útlevél, , Kézipoggyász\nElektronika, Töltő, 50, Bőrönd, checked',
|
||||
'packing.importCsv': 'CSV/TXT betöltése',
|
||||
'packing.importCsv': 'CSV/TXT/MD betöltése',
|
||||
'packing.export': 'Exportálás',
|
||||
'packing.exportPrint': 'Nyomtatás vagy mentés PDF-ként',
|
||||
'packing.exportMarkdown': 'Markdown ellenőrzőlista (.md)',
|
||||
'packing.exportCsv': 'CSV importáláshoz (.csv)',
|
||||
'packing.printItems': 'Tételek',
|
||||
'packing.printPacked': 'Becsomagolva',
|
||||
'packing.importHintMarkdown':
|
||||
'Markdown-lista is használható: a címsor adja a kategóriát, a "- [ ]" és "- [x]" sorokból tételek lesznek, a név előtti "3x" pedig a mennyiséget adja meg.',
|
||||
'packing.importAction': '{count} importálása',
|
||||
'packing.importSuccess': '{count} tétel importálva',
|
||||
'packing.importError': 'Importálás sikertelen',
|
||||
|
||||
@@ -3033,7 +3033,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'“Bersama” dan “Daftar saya” membelah daftar bawaan menjadi dua: kumpulan yang dilihat semua orang, dan milik Anda sendiri. “Semua”, “Belum” dan “Selesai” mempersempit mana pun yang terbuka, dan bilah di atas menghitung apa yang sudah dikemas.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'“Terapkan template”, “Impor” dan “Simpan sebagai template” mengisi atau menyimpan sebuah daftar tanpa mengetiknya, dan tombol merah di sebelahnya menyebut berapa item yang tercentang lalu menyingkirkannya.',
|
||||
'“Terapkan template” dan “Simpan sebagai template” mengisi atau menyimpan sebuah daftar tanpa mengetiknya, dan dua ikon di sebelahnya mengekspor daftar, sebagai cetakan, PDF, atau file, dan mengimpor daftar. Tombol merah menyebut berapa item yang tercentang lalu menyingkirkannya.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'“Tugas” punya bilah sisi sendiri: kartu kemajuan, filter “Semua”, “Tugasku”, “Terlambat” dan “Selesai”, “Urutkan” menurut “Prioritas” atau “Tenggat waktu”, dan satu baris per daftar. Klik pada sebuah tugas membukanya di panel sebelah kanan, dan “Tugas baru” membuka formulir “Tugas baru” di atas bagian tengah layar.',
|
||||
// packing-categories
|
||||
@@ -3102,18 +3102,35 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Menempelkan seluruh daftar bawaan',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Ubah daftar yang sudah Anda punya di tempat lain menjadi item bawaan sekaligus.',
|
||||
'help.guide.import-packing-list.step.1': 'Klik “Impor” di bilah atas daftar.',
|
||||
'help.guide.import-packing-list.step.1': 'Klik tombol impor dengan panah ke bawah di bilah atas daftar.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Satu item per baris: Kategori, Nama, Berat dalam g (opsional), Tas (opsional), checked/unchecked (opsional). Contoh abu-abu di dalam kotak memperlihatkan keempat bentuknya.',
|
||||
'Satu item per baris: Kategori, Nama, Berat dalam g (opsional), Tas (opsional), checked/unchecked (opsional). Contoh abu-abu di dalam kotak memperlihatkan keempat bentuknya. Daftar Markdown juga bisa: judul menjadi nama daftar, dan "- [ ]" serta "- [x]" menjadi item.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Atau muat barisnya dari sebuah file dengan “Muat CSV/TXT”. Ia menerima .csv atau .txt dan menggantikan apa pun yang ada di kotak.',
|
||||
'Atau muat barisnya dari sebuah file dengan “Muat CSV/TXT/MD”. Ia menerima .csv, .txt, atau .md dan menggantikan apa pun yang ada di kotak.',
|
||||
'help.guide.import-packing-list.step.4': 'Klik “Impor”. Tombolnya menghitung baris yang berhasil dipahami.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Setiap baris menjadi satu baris item, di daftar yang disebut bidang pertamanya, dan tidak ada yang sudah ada di sana yang tersentuh.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Koma, titik koma dan tab sama-sama memisahkan bidang, dan tanda kutip ganda menyatukan sebuah bidang, jadi “Shirt, blue” tetap satu nama. Baris dengan satu nilai hanyalah sebuah nama, dan baris tanpa daftar sendiri mendarat di “Lainnya”.',
|
||||
'Koma, titik koma dan tab sama-sama memisahkan bidang, dan tanda kutip ganda menyatukan sebuah bidang, jadi “Shirt, blue” tetap satu nama. Baris dengan satu nilai hanyalah sebuah nama, baris tanpa daftar sendiri mendarat di “Lainnya”, dan "3x" di depan nama menentukan jumlahnya.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Tas yang disebut di bidang keempat dibuat jika perjalanan belum memilikinya. Ini satu-satunya tempat yang memuat berat dan tas secara massal; sebuah template hanya membawa nama dan daftar.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Mencetak atau mengekspor daftar bawaan',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Bawa daftar di atas kertas, sebagai PDF, atau sebagai file untuk aplikasi lain atau perjalanan berikutnya.',
|
||||
'help.guide.export-packing-list.step.1': 'Klik tombol ekspor dengan panah ke atas di bilah atas daftar.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'“Daftar periksa Markdown (.md)” dan “CSV untuk impor (.csv)” langsung menyimpan daftar sebagai file.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Klik “Cetak atau simpan sebagai PDF”. Pratinjau menampilkan daftar sebagai satu halaman: perjalanan dan tanggalnya di atas, lalu setiap daftar sebagai kartu dengan kotak untuk dicentang.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Klik “Cetak atau simpan sebagai PDF” di bawah pratinjau. Browser membuka dialog cetaknya: pilih printer, atau “Simpan sebagai PDF” untuk menyimpan file.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Hasil cetak dan file berisi tampilan yang sedang terbuka, “Bersama” atau “Daftar saya”, lengkap dengan jumlah, berat, dan tanda centang.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV adalah format yang dibaca “Impor”, termasuk tas, jadi bisa dipakai sebagai template bawaan milik Anda sendiri: impor ke perjalanan berikutnya.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'File Markdown terbuka sebagai daftar periksa di Obsidian, Notion, atau GitHub, dan bisa masuk kembali lewat “Impor” dengan cara yang sama.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Menentukan siapa melihat sebuah item dan siapa membawanya',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Satu item per baris. Format: Kategori, Nama, Berat dalam g (opsional), Tas (opsional), checked/unchecked (opsional)',
|
||||
'packing.importPlaceholder':
|
||||
'Kebersihan, Sikat Gigi\nPakaian, Kaos, 200\nDokumen, Paspor, , Kabin\nElektronik, Charger, 50, Koper, checked',
|
||||
'packing.importCsv': 'Muat CSV/TXT',
|
||||
'packing.importCsv': 'Muat CSV/TXT/MD',
|
||||
'packing.export': 'Ekspor',
|
||||
'packing.exportPrint': 'Cetak atau simpan sebagai PDF',
|
||||
'packing.exportMarkdown': 'Daftar periksa Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV untuk impor (.csv)',
|
||||
'packing.printItems': 'Item',
|
||||
'packing.printPacked': 'Sudah dikemas',
|
||||
'packing.importHintMarkdown':
|
||||
'Daftar Markdown juga bisa: judul menentukan kategori, "- [ ]" dan "- [x]" menjadi item, dan "3x" di depan nama menentukan jumlahnya.',
|
||||
'packing.importAction': 'Impor {count}',
|
||||
'packing.importSuccess': '{count} item berhasil diimpor',
|
||||
'packing.importError': 'Impor gagal',
|
||||
|
||||
@@ -3014,7 +3014,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Condiviso e La mia lista dividono la lista valigia in due: il fondo comune che vedono tutti, e la tua. Tutti, Da fare e Fatto restringono quella aperta, e la barra sopra conta che cosa è in valigia.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Applica modello, Importa e Salva come modello riempiono o conservano una lista senza digitarla, e il pulsante rosso accanto dice quanti elementi sono spuntati e li porta via.',
|
||||
'Applica modello e Salva come modello riempiono o conservano una lista senza digitarla, e le due icone accanto esportano la lista, come stampa, come PDF o come file, e ne importano una. Il pulsante rosso dice quanti elementi sono spuntati e li porta via.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Da fare ha una barra laterale propria: la scheda di avanzamento, i filtri Tutti, Le mie attività, Scaduta e Fatto, Ordina per Priorità o Scadenza, e una riga per lista. Un clic su un’attività la apre nel pannello a destra, e Nuova attività apre il modulo Nuova attività sopra il centro dello schermo.',
|
||||
// packing-categories
|
||||
@@ -3082,18 +3082,37 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Incollare dentro un’intera lista valigia',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Trasforma in un colpo solo una lista che hai già altrove in elementi della valigia.',
|
||||
'help.guide.import-packing-list.step.1': 'Clicca Importa nella barra sopra la lista.',
|
||||
'help.guide.import-packing-list.step.1':
|
||||
'Clicca il pulsante di importazione con la freccia in giù nella barra sopra la lista.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Un elemento per riga: Categoria, Nome, Peso in g (opzionale), Borsa (opzionale), checked/unchecked (opzionale). L’esempio grigio nel riquadro mostra tutte e quattro le forme.',
|
||||
'Un elemento per riga: Categoria, Nome, Peso in g (opzionale), Borsa (opzionale), checked/unchecked (opzionale). L’esempio grigio nel riquadro mostra tutte e quattro le forme. Funziona anche un elenco in Markdown: un titolo dà il nome alla lista, e "- [ ]" e "- [x]" diventano elementi.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Oppure carica le righe da un file con Carica CSV/TXT. Accetta un .csv o un .txt e sostituisce quello che sta nel riquadro.',
|
||||
'Oppure carica le righe da un file con Carica CSV/TXT/MD. Accetta un .csv, un .txt o un .md e sostituisce quello che sta nel riquadro.',
|
||||
'help.guide.import-packing-list.step.4': 'Clicca Importa. Il pulsante conta le righe che ha capito.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Ogni riga diventa un elemento, nella lista che nomina il suo primo campo, e niente di quello che c’era già viene toccato.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Virgole, punti e virgola e tabulazioni separano i campi allo stesso modo, e le virgolette tengono insieme un campo, così «Camicia, blu» resta un solo nome. Una riga con un unico valore è solo un nome, e una riga senza una lista propria finisce in Altro.',
|
||||
'Virgole, punti e virgola e tabulazioni separano i campi allo stesso modo, e le virgolette tengono insieme un campo, così «Camicia, blu» resta un solo nome. Una riga con un unico valore è solo un nome, una riga senza una lista propria finisce in Altro, e "3x" davanti a un nome imposta la quantità.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Una borsa nominata nel quarto campo viene creata se il viaggio non ce l’ha ancora. Questo è l’unico posto che carica pesi e valigie in blocco; un modello porta solo nomi e liste.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Stampare o esportare la lista valigia',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Porta la lista con te su carta, come PDF o come file per un’altra app o per il prossimo viaggio.',
|
||||
'help.guide.export-packing-list.step.1':
|
||||
'Clicca il pulsante di esportazione con la freccia in su nella barra sopra la lista.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Checklist in Markdown (.md) e CSV per l’importazione (.csv) salvano subito la lista come file.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Clicca Stampa o salva come PDF. L’anteprima mostra la lista come una pagina: in alto il viaggio e le sue date, poi ogni lista come una scheda con una casella da spuntare.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Clicca Stampa o salva come PDF sotto l’anteprima. Il browser apre la sua finestra di stampa: scegli una stampante, oppure Salva come PDF per tenere un file.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'La stampa e i file contengono la vista aperta, Condiviso o La mia lista, con quantità, pesi e spunte.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'Il CSV è il formato che legge Importa, valigie comprese, quindi funziona come un tuo modello di valigia: importalo nel prossimo viaggio.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Il file Markdown si apre come checklist in Obsidian, Notion o GitHub, e rientra allo stesso modo tramite Importa.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Decidere chi vede un elemento e chi lo porta',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Un elemento per riga. Formato: Categoria, Nome, Peso in g (opzionale), Borsa (opzionale), checked/unchecked (opzionale)',
|
||||
'packing.importPlaceholder':
|
||||
'Igiene, Spazzolino\nAbbigliamento, Magliette, 200\nDocumenti, Passaporto, , Bagaglio a mano\nElettronica, Caricabatterie, 50, Valigia, checked',
|
||||
'packing.importCsv': 'Carica CSV/TXT',
|
||||
'packing.importCsv': 'Carica CSV/TXT/MD',
|
||||
'packing.export': 'Esporta',
|
||||
'packing.exportPrint': 'Stampa o salva come PDF',
|
||||
'packing.exportMarkdown': 'Checklist in Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV per l’importazione (.csv)',
|
||||
'packing.printItems': 'Articoli',
|
||||
'packing.printPacked': 'In valigia',
|
||||
'packing.importHintMarkdown':
|
||||
'Funziona anche un elenco in Markdown: un titolo indica la categoria, "- [ ]" e "- [x]" diventano articoli e "3x" davanti a un nome imposta la quantità.',
|
||||
'packing.importAction': 'Importa {count}',
|
||||
'packing.importSuccess': '{count} elementi importati',
|
||||
'packing.importError': 'Importazione non riuscita',
|
||||
|
||||
@@ -2890,7 +2890,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'「みんなで」と「マイリスト」が持ち物リストを2つに分けます。全員が見るプールと、あなた自身のものです。「すべて」「未完了」「完了」が開いているほうを絞り込み、上のバーが梱包済みの数を数えます。',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'「テンプレートを適用」「インポート」「テンプレートとして保存」は、打ち込まずにリストを埋めたり残したりします。その隣の赤いボタンはチェック済みのアイテムが何件あるかを出し、まとめて片づけます。',
|
||||
'「テンプレートを適用」と「テンプレートとして保存」は、打ち込まずにリストを埋めたり残したりします。その隣の2つのアイコンは、リストを印刷物、PDF、ファイルとしてエクスポートし、リストをインポートします。赤いボタンはチェック済みのアイテムが何件あるかを出し、まとめて片づけます。',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'「ToDo」には専用のサイドバーがあります。進捗カード、「すべて」「自分のタスク」「期限切れ」「完了」のフィルター、「優先度」か「期限」での「並び替え」、そしてリストごとに1行です。タスクをクリックすると右のパネルで開き、「新しいタスクを追加...」は画面の中央に「新しいタスク」のフォームを開きます。',
|
||||
// packing-categories
|
||||
@@ -2952,18 +2952,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': '持ち物リストをまるごと貼り付ける',
|
||||
'help.guide.import-packing-list.goal': 'どこかにすでにあるリストを、一度で持ち物のアイテムに変えます。',
|
||||
'help.guide.import-packing-list.step.1': 'リストの上のバーで「インポート」をクリックします。',
|
||||
'help.guide.import-packing-list.step.1': 'リストの上のバーで、下向き矢印のインポートボタンをクリックします。',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'1行につき1項目です。カテゴリ, 名前, 重量(g・任意), バッグ(任意), checked/unchecked(任意)。ボックスの中の灰色の見本が4つの形をすべて見せています。',
|
||||
'1行につき1項目です。カテゴリ, 名前, 重量(g・任意), バッグ(任意), checked/unchecked(任意)。ボックスの中の灰色の見本が4つの形をすべて見せています。Markdown のリストも使えます。見出しがリストの名前になり、「- [ ]」と「- [x]」がアイテムになります。',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'または「CSV/TXTを読み込む」で行をファイルから読み込みます。.csvか.txtを受け取り、ボックスの中身を置き換えます。',
|
||||
'または「CSV/TXT/MDを読み込む」で行をファイルから読み込みます。.csv、.txt、.md のいずれかを受け取り、ボックスの中身を置き換えます。',
|
||||
'help.guide.import-packing-list.step.4': '「インポート」をクリックします。ボタンは読み取れた行の数を出します。',
|
||||
'help.guide.import-packing-list.result':
|
||||
'どの行も1つの行になり、最初のフィールドが指すリストに入ります。もとからあったものには何も触れません。',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'カンマ、セミコロン、タブがどれもフィールドを区切り、二重引用符はフィールドをひとまとめにするので、「Shirt, blue」は1つの名前のままです。値が1つだけの行はただの名前で、自分のリストを持たない行は「その他」に入ります。',
|
||||
'カンマ、セミコロン、タブがどれもフィールドを区切り、二重引用符はフィールドをひとまとめにするので、「Shirt, blue」は1つの名前のままです。値が1つだけの行はただの名前で、自分のリストを持たない行は「その他」に入ります。名前の前に「3x」を付けると数量になります。',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'4番目のフィールドで名前を挙げたバッグは、旅行にまだなければ作られます。重さとバッグをまとめて読み込めるのはここだけで、テンプレートが運ぶのは名前とリストだけです。',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': '持ち物リストを印刷またはエクスポートする',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'リストを紙で、PDF で、またはほかのアプリや次の旅行のためのファイルとして持ち出します。',
|
||||
'help.guide.export-packing-list.step.1': 'リストの上のバーで、上向き矢印のエクスポートボタンをクリックします。',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'「Markdown チェックリスト (.md)」と「インポート用 CSV (.csv)」は、リストをすぐにファイルとして保存します。',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'「印刷または PDF として保存」をクリックします。プレビューはリストを1ページとして見せます。上に旅行とその日付、その下にリストごとのカードが並び、それぞれにチェック用のボックスが付きます。',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'プレビューの下の「印刷または PDF として保存」をクリックします。ブラウザの印刷ダイアログが開くので、プリンターを選ぶか、ファイルとして残すなら「PDF に保存」を選びます。',
|
||||
'help.guide.export-packing-list.result':
|
||||
'印刷物とファイルには、開いているビュー、つまり「みんなで」か「マイリスト」が、数量、重さ、チェックとともに入ります。',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV は「インポート」が読む形式で、バッグも含まれます。そのため自分だけの持ち物テンプレートとして使えます。次の旅行にインポートしてください。',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Markdown ファイルは Obsidian、Notion、GitHub でチェックリストとして開き、同じように「インポート」で戻せます。',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'アイテムを誰が見て誰が持っていくかを決める',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -17,7 +17,15 @@ const packing: TranslationStrings = {
|
||||
'packing.importHint': '1行につき1項目。形式:カテゴリ, 名前, 重量(g・任意), バッグ(任意), checked/unchecked(任意)',
|
||||
'packing.importPlaceholder':
|
||||
'衛生用品, 歯ブラシ\n衣類, Tシャツ, 200\n書類, パスポート, , 機内持ち込み\n電子機器, 充電器, 50, スーツケース, checked',
|
||||
'packing.importCsv': 'CSV/TXTを読み込む',
|
||||
'packing.importCsv': 'CSV/TXT/MDを読み込む',
|
||||
'packing.export': 'エクスポート',
|
||||
'packing.exportPrint': '印刷または PDF として保存',
|
||||
'packing.exportMarkdown': 'Markdown チェックリスト (.md)',
|
||||
'packing.exportCsv': 'インポート用 CSV (.csv)',
|
||||
'packing.printItems': 'アイテム',
|
||||
'packing.printPacked': '梱包済み',
|
||||
'packing.importHintMarkdown':
|
||||
'Markdown のリストも使えます。見出しがカテゴリになり、「- [ ]」と「- [x]」がアイテムになり、名前の前の「3x」で数量を指定できます。',
|
||||
'packing.importAction': '{count}件をインポート',
|
||||
'packing.importSuccess': '{count}件インポートしました',
|
||||
'packing.importError': 'インポートに失敗しました',
|
||||
|
||||
@@ -2791,7 +2791,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'“공동”과 “내 목록”이 짐 목록을 둘로 나눕니다. 모두가 보는 공용 묶음과, 내 것입니다. “전체”, “미완료”, “완료”가 열려 있는 쪽을 좁히고, 위쪽 막대가 얼마나 챙겼는지 셉니다.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'“템플릿 적용”, “가져오기”, “템플릿으로 저장”은 일일이 입력하지 않고도 목록을 채우거나 남겨 둡니다. 그 옆의 빨간 버튼은 체크된 항목이 몇 개인지 말하고 그것들을 치웁니다.',
|
||||
'“템플릿 적용”과 “템플릿으로 저장”은 일일이 입력하지 않고도 목록을 채우거나 남겨 둡니다. 그 옆의 아이콘 두 개는 목록을 인쇄물, PDF, 파일로 내보내고, 목록을 가져옵니다. 빨간 버튼은 체크된 항목이 몇 개인지 말하고 그것들을 치웁니다.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'“할 일”에는 전용 사이드바가 있습니다. 진행 카드, “전체”, “내 작업”, “기한 초과”, “완료” 필터, “우선순위”나 “마감일”로 하는 “정렬 기준”, 그리고 목록마다 한 행입니다. 작업을 클릭하면 오른쪽 패널에서 열리고, “새 작업 추가”는 화면 가운데에 “새 작업” 양식을 엽니다.',
|
||||
// packing-categories
|
||||
@@ -2851,18 +2851,34 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': '짐 목록을 통째로 붙여넣기',
|
||||
'help.guide.import-packing-list.goal': '다른 곳에 이미 있는 목록을 한 번에 짐 항목으로 바꿉니다.',
|
||||
'help.guide.import-packing-list.step.1': '목록 위 막대에서 “가져오기”를 클릭합니다.',
|
||||
'help.guide.import-packing-list.step.1': '목록 위 막대에서 아래쪽 화살표가 있는 가져오기 버튼을 클릭합니다.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'한 줄에 한 항목입니다. 카테고리, 이름, 무게(g, 선택), 가방(선택), checked/unchecked(선택). 상자 안의 회색 예시가 네 가지 형태를 모두 보여 줍니다.',
|
||||
'한 줄에 한 항목입니다. 카테고리, 이름, 무게(g, 선택), 가방(선택), checked/unchecked(선택). 상자 안의 회색 예시가 네 가지 형태를 모두 보여 줍니다. Markdown 목록도 됩니다. 제목이 목록 이름이 되고, "- [ ]"와 "- [x]"는 항목이 됩니다.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'또는 “CSV/TXT 불러오기”로 파일에서 줄을 읽어 옵니다. .csv나 .txt를 받아서 상자 안에 있던 것을 대체합니다.',
|
||||
'또는 “CSV/TXT/MD 불러오기”로 파일에서 줄을 읽어 옵니다. .csv, .txt, .md를 받아서 상자 안에 있던 것을 대체합니다.',
|
||||
'help.guide.import-packing-list.step.4': '“가져오기”를 클릭합니다. 버튼이 알아들은 줄 수를 셉니다.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'모든 줄이 한 행이 되어 첫 필드가 가리키는 목록에 들어가고, 이미 있던 것은 아무것도 건드리지 않습니다.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'쉼표, 세미콜론, 탭이 모두 필드를 나누고, 큰따옴표는 필드를 하나로 묶습니다. 그래서 “Shirt, blue”는 한 이름으로 남습니다. 값이 하나뿐인 줄은 그냥 이름이고, 자기 목록이 없는 줄은 “기타”에 들어갑니다.',
|
||||
'쉼표, 세미콜론, 탭이 모두 필드를 나누고, 큰따옴표는 필드를 하나로 묶습니다. 그래서 “Shirt, blue”는 한 이름으로 남습니다. 값이 하나뿐인 줄은 그냥 이름이고, 자기 목록이 없는 줄은 “기타”에 들어가며, 이름 앞의 "3x"는 수량을 정합니다.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'네 번째 필드에 적힌 가방은 여행에 아직 없으면 만들어집니다. 무게와 가방을 한꺼번에 불러오는 곳은 여기뿐이고, 템플릿은 이름과 목록만 가져옵니다.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': '짐 목록 인쇄하거나 내보내기',
|
||||
'help.guide.export-packing-list.goal': '목록을 종이로, PDF로, 또는 다른 앱이나 다음 여행에 쓸 파일로 가져갑니다.',
|
||||
'help.guide.export-packing-list.step.1': '목록 위 막대에서 위쪽 화살표가 있는 내보내기 버튼을 클릭합니다.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'“Markdown 체크리스트 (.md)”와 “가져오기용 CSV (.csv)”는 목록을 바로 파일로 저장합니다.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'“인쇄 또는 PDF로 저장”을 클릭합니다. 미리 보기가 목록을 한 페이지로 보여 줍니다. 위에는 여행과 날짜가, 그 아래에는 목록마다 체크 상자가 달린 카드가 있습니다.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'미리 보기 아래의 “인쇄 또는 PDF로 저장”을 클릭합니다. 브라우저가 인쇄 대화 상자를 엽니다. 프린터를 고르거나, 파일로 남기려면 “PDF로 저장”을 고릅니다.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'인쇄물과 파일에는 지금 열려 있는 보기, “공동” 또는 “내 목록”이 수량, 무게, 체크 표시와 함께 담깁니다.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV는 “가져오기”가 읽는 형식이고 가방까지 담기므로, 나만의 짐 템플릿으로 쓸 수 있습니다. 다음 여행에 가져오면 됩니다.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Markdown 파일은 Obsidian, Notion, GitHub에서 체크리스트로 열리고, “가져오기”로 똑같이 다시 들여올 수 있습니다.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': '누가 항목을 보고 누가 가져갈지 정하기',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -17,7 +17,15 @@ const packing: TranslationStrings = {
|
||||
'packing.importHint': '한 줄에 하나의 항목. 형식: 카테고리, 이름, 무게(g, 선택), 가방(선택), checked/unchecked(선택)',
|
||||
'packing.importPlaceholder':
|
||||
'위생, 칫솔\n의류, 티셔츠, 200\n서류, 여권, , 기내 수하물\n전자기기, 충전기, 50, 캐리어, checked',
|
||||
'packing.importCsv': 'CSV/TXT 불러오기',
|
||||
'packing.importCsv': 'CSV/TXT/MD 불러오기',
|
||||
'packing.export': '내보내기',
|
||||
'packing.exportPrint': '인쇄 또는 PDF로 저장',
|
||||
'packing.exportMarkdown': 'Markdown 체크리스트 (.md)',
|
||||
'packing.exportCsv': '가져오기용 CSV (.csv)',
|
||||
'packing.printItems': '항목',
|
||||
'packing.printPacked': '완료',
|
||||
'packing.importHintMarkdown':
|
||||
'Markdown 목록도 사용할 수 있습니다. 제목은 카테고리가 되고, "- [ ]"와 "- [x]"는 항목이 되며, 이름 앞의 "3x"는 수량을 지정합니다.',
|
||||
'packing.importAction': '{count}개 가져오기',
|
||||
'packing.importSuccess': '{count}개 항목을 가져왔습니다',
|
||||
'packing.importError': '가져오기 실패',
|
||||
|
||||
@@ -3034,7 +3034,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Gedeeld en Mijn lijst splitsen de inpaklijst in tweeën: de pot die iedereen ziet, en die van jou. Alle, Openstaand en Klaar versmallen degene die open is, en de balk erboven telt wat er ingepakt is.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Sjabloon toepassen, Importeren en Opslaan als sjabloon vullen of bewaren een lijst zonder hem uit te typen, en de rode knop ernaast noemt hoeveel items aangevinkt zijn en ruimt ze op.',
|
||||
'Sjabloon toepassen en Opslaan als sjabloon vullen of bewaren een lijst zonder hem uit te typen, en de twee pictogrammen ernaast exporteren de lijst, als afdruk, als PDF of als bestand, en importeren er een. De rode knop noemt hoeveel items aangevinkt zijn en ruimt ze op.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Taken heeft een eigen zijbalk: de voortgangskaart, de filters Alles, Mijn taken, Verlopen en Klaar, Sorteren op Prioriteit of Vervaldatum, en één rij per lijst. Een klik op een taak opent hem in het paneel rechts, en Nieuwe taak opent het formulier Nieuwe taak over het midden van het scherm.',
|
||||
// packing-categories
|
||||
@@ -3101,18 +3101,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Een hele inpaklijst erin plakken',
|
||||
'help.guide.import-packing-list.goal': 'Maak in één keer inpakitems van een lijst die je ergens anders al hebt.',
|
||||
'help.guide.import-packing-list.step.1': 'Klik op Importeren in de balk boven de lijst.',
|
||||
'help.guide.import-packing-list.step.1': 'Klik op de importknop met de pijl omlaag in de balk boven de lijst.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Eén item per regel: Categorie, Naam, Gewicht in g (optioneel), Bagage (optioneel), checked/unchecked (optioneel). Het grijze voorbeeld in het vak toont alle vier de vormen.',
|
||||
'Eén item per regel: Categorie, Naam, Gewicht in g (optioneel), Bagage (optioneel), checked/unchecked (optioneel). Het grijze voorbeeld in het vak toont alle vier de vormen. Een Markdown-lijst werkt ook: een kop geeft de lijst een naam, en "- [ ]" en "- [x]" worden items.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Of laad de regels uit een bestand met CSV/TXT laden. Het neemt een .csv of een .txt en vervangt wat er in het vak staat.',
|
||||
'Of laad de regels uit een bestand met CSV/TXT/MD laden. Het neemt een .csv, een .txt of een .md en vervangt wat er in het vak staat.',
|
||||
'help.guide.import-packing-list.step.4': 'Klik op Importeren. De knop telt de regels die hij begrepen heeft.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Elke regel wordt een rij, in de lijst die zijn eerste veld noemt, en niets van wat er al stond wordt aangeraakt.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Komma’s, puntkomma’s en tabs scheiden allemaal velden, en aanhalingstekens houden een veld bij elkaar, zodat „Shirt, blauw“ één naam blijft. Een regel met één enkele waarde is alleen een naam, en een regel zonder eigen lijst komt in Overig terecht.',
|
||||
'Komma’s, puntkomma’s en tabs scheiden allemaal velden, en aanhalingstekens houden een veld bij elkaar, zodat „Shirt, blauw“ één naam blijft. Een regel met één enkele waarde is alleen een naam, een regel zonder eigen lijst komt in Overig terecht, en "3x" voor een naam stelt het aantal in.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Een bagagestuk dat in het vierde veld genoemd wordt, wordt aangemaakt als de reis het nog niet heeft. Dit is de enige plek die gewichten en bagage in bulk laadt; een sjabloon brengt alleen namen en lijsten.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'De inpaklijst afdrukken of exporteren',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Neem de lijst mee op papier, als PDF of als bestand voor een andere app of de volgende reis.',
|
||||
'help.guide.export-packing-list.step.1': 'Klik op de exportknop met de pijl omhoog in de balk boven de lijst.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Markdown-checklist (.md) en CSV voor import (.csv) slaan de lijst meteen op als bestand.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Klik op Afdrukken of opslaan als pdf. Het voorbeeld toont de lijst als pagina: bovenaan de reis en de datums, dan elke lijst als kaart met een vakje om af te vinken.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Klik onder het voorbeeld op Afdrukken of opslaan als pdf. De browser opent zijn afdrukvenster: kies een printer, of Opslaan als pdf om een bestand te bewaren.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'De afdruk en de bestanden bevatten de weergave die open is, Gedeeld of Mijn lijst, met de aantallen, gewichten en vinkjes.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'De CSV is het formaat dat Importeren leest, bagage inbegrepen, dus hij werkt als je eigen inpaksjabloon: importeer hem in de volgende reis.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Het Markdown-bestand opent als checklist in Obsidian, Notion of GitHub, en komt via Importeren net zo goed weer terug.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Bepalen wie een item ziet en wie het meeneemt',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Eén item per regel. Formaat: Categorie, Naam, Gewicht in g (optioneel), Tas (optioneel), checked/unchecked (optioneel)',
|
||||
'packing.importPlaceholder':
|
||||
'Hygiëne, Tandenborstel\nKleding, T-shirts, 200\nDocumenten, Paspoort, , Handbagage\nElektronica, Oplader, 50, Koffer, checked',
|
||||
'packing.importCsv': 'CSV/TXT laden',
|
||||
'packing.importCsv': 'CSV/TXT/MD laden',
|
||||
'packing.export': 'Exporteren',
|
||||
'packing.exportPrint': 'Afdrukken of opslaan als pdf',
|
||||
'packing.exportMarkdown': 'Markdown-checklist (.md)',
|
||||
'packing.exportCsv': 'CSV voor import (.csv)',
|
||||
'packing.printItems': 'Items',
|
||||
'packing.printPacked': 'Ingepakt',
|
||||
'packing.importHintMarkdown':
|
||||
'Een Markdown-lijst werkt ook: een kop geeft de categorie aan, "- [ ]" en "- [x]" worden items en "3x" voor een naam stelt het aantal in.',
|
||||
'packing.importAction': '{count} importeren',
|
||||
'packing.importSuccess': '{count} items geïmporteerd',
|
||||
'packing.importError': 'Import mislukt',
|
||||
|
||||
@@ -2962,7 +2962,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Wspólne i Moja lista dzielą listę pakowania na dwie części: pulę, którą widzą wszyscy, i Twoją własną. Wszystkie, Do spakowania i Spakowane zawężają tę, która jest otwarta, a pasek u góry liczy, co jest spakowane.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Zastosuj szablon, Importuj i Zapisz jako szablon wypełniają albo zachowują listę bez pisania jej od nowa, a czerwony przycisk obok nich mówi, ile przedmiotów jest zaznaczonych, i sprząta je.',
|
||||
'Zastosuj szablon i Zapisz jako szablon wypełniają albo zachowują listę bez pisania jej od nowa, a dwie ikony obok nich eksportują listę, jako wydruk, PDF albo plik, i importują listę. Czerwony przycisk mówi, ile przedmiotów jest zaznaczonych, i sprząta je.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Do zrobienia ma własny panel boczny: kartę postępu, filtry Wszystkie, Moje zadania, Przeterminowane i Gotowe, Sortuj wg Priorytet albo Termin, oraz jeden wiersz na listę. Kliknięcie zadania otwiera je w panelu po prawej, a Nowe zadanie otwiera na środku ekranu formularz Nowe zadanie.',
|
||||
// packing-categories
|
||||
@@ -3028,18 +3028,35 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Wkleić całą listę pakowania',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Zamień listę, którą masz już gdzie indziej, w przedmioty do spakowania za jednym razem.',
|
||||
'help.guide.import-packing-list.step.1': 'Kliknij Importuj na pasku nad listą.',
|
||||
'help.guide.import-packing-list.step.1': 'Kliknij przycisk importu ze strzałką w dół na pasku nad listą.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Jedna pozycja w wierszu: Kategoria, Nazwa, Waga w gramach (opcjonalnie), Torba (opcjonalnie), checked/unchecked (opcjonalnie). Szary przykład w polu pokazuje wszystkie cztery kształty.',
|
||||
'Jedna pozycja w wierszu: Kategoria, Nazwa, Waga w gramach (opcjonalnie), Torba (opcjonalnie), checked/unchecked (opcjonalnie). Szary przykład w polu pokazuje wszystkie cztery kształty. Działa też lista w Markdown: nagłówek nadaje nazwę liście, a "- [ ]" i "- [x]" stają się pozycjami.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Albo wczytaj wiersze z pliku przez Załaduj CSV/TXT. Przyjmuje .csv albo .txt i zastępuje to, co jest w polu.',
|
||||
'Albo wczytaj wiersze z pliku przez Załaduj CSV/TXT/MD. Przyjmuje .csv, .txt albo .md i zastępuje to, co jest w polu.',
|
||||
'help.guide.import-packing-list.step.4': 'Kliknij Importuj. Przycisk liczy wiersze, które zrozumiał.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Każdy wiersz to jedna pozycja, na liście, którą nazywa jego pierwsze pole, i nic z tego, co już tam było, nie zostaje ruszone.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Pola rozdzielają przecinki, średniki i tabulatory, a cudzysłowy trzymają pole razem, więc „Koszula, niebieska” zostaje jedną nazwą. Wiersz z jedną wartością to sama nazwa, a wiersz bez własnej listy ląduje w Inne.',
|
||||
'Pola rozdzielają przecinki, średniki i tabulatory, a cudzysłowy trzymają pole razem, więc „Koszula, niebieska” zostaje jedną nazwą. Wiersz z jedną wartością to sama nazwa, wiersz bez własnej listy ląduje w Inne, a "3x" przed nazwą ustawia ilość.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Torba nazwana w czwartym polu zostaje utworzona, jeśli podróż jeszcze jej nie ma. To jedyne miejsce, które wczytuje wagi i torby hurtowo; szablon przynosi tylko nazwy i listy.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Wydrukować albo wyeksportować listę pakowania',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Zabierz listę ze sobą na papierze, jako PDF albo jako plik dla innej aplikacji lub na następną podróż.',
|
||||
'help.guide.export-packing-list.step.1': 'Kliknij przycisk eksportu ze strzałką w górę na pasku nad listą.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Lista kontrolna w Markdown (.md) i CSV do importu (.csv) od razu zapisują listę jako plik.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Kliknij Drukuj lub zapisz jako PDF. Podgląd pokazuje listę jako stronę: u góry podróż i jej daty, potem każdą listę jako kartę z polem do zaznaczenia.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Kliknij Drukuj lub zapisz jako PDF pod podglądem. Przeglądarka otwiera swoje okno drukowania: wybierz drukarkę albo Zapisz jako PDF, żeby zachować plik.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Wydruk i pliki zawierają widok, który jest otwarty, Wspólne albo Moja lista, z ilościami, wagami i zaznaczeniami.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV to format, który czyta Importuj, razem z torbami, więc działa jak Twój własny szablon pakowania: zaimportuj go do następnej podróży.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Plik Markdown otwiera się jako lista kontrolna w Obsidian, Notion albo GitHub i tak samo wraca przez Importuj.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Zdecydować, kto widzi przedmiot i kto go przynosi',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Jedna pozycja w wierszu. Format: kategoria, nazwa, waga w gramach (opcjonalnie), Torba (opcjonalnie), checked/unchecked (opcjonalnie)',
|
||||
'packing.importPlaceholder':
|
||||
'Higiena, Szczoteczka do zębów\nOdzież, Koszulki, 200\nDokumenty, Paszport, , Podręczny\nElektronika, Ładowarka, 50, Walizka, checked',
|
||||
'packing.importCsv': 'Załaduj CSV/TXT',
|
||||
'packing.importCsv': 'Załaduj CSV/TXT/MD',
|
||||
'packing.export': 'Eksportuj',
|
||||
'packing.exportPrint': 'Drukuj lub zapisz jako PDF',
|
||||
'packing.exportMarkdown': 'Lista kontrolna w Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV do importu (.csv)',
|
||||
'packing.printItems': 'Pozycje',
|
||||
'packing.printPacked': 'Spakowane',
|
||||
'packing.importHintMarkdown':
|
||||
'Działa też lista w Markdown: nagłówek określa kategorię, "- [ ]" i "- [x]" stają się pozycjami, a "3x" przed nazwą ustawia ilość.',
|
||||
'packing.importAction': 'Importuj {count}',
|
||||
'packing.importSuccess': '{count} pozycji zaimportowanych',
|
||||
'packing.importError': 'Import nie powiódł się',
|
||||
|
||||
@@ -2978,7 +2978,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Общий и Мой список делят список вещей надвое: пул, который видят все, и ваш собственный. Все, Не собрано и Собрано сужают тот, который открыт, а полоса сверху считает, что собрано.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Применить шаблон, Импорт и Сохранить как шаблон наполняют или сохраняют список без набора вручную, а красная кнопка рядом называет, сколько вещей отмечено, и убирает их.',
|
||||
'Применить шаблон и Сохранить как шаблон наполняют или сохраняют список без набора вручную, а два значка рядом с ними экспортируют список, как распечатку, PDF или файл, и импортируют список. Красная кнопка называет, сколько вещей отмечено, и убирает их.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'У Задачи есть своя боковая панель: карточка прогресса, фильтры Все, Мои задачи, Просроченные и Выполненные, Сортировать по Приоритет или Срок выполнения и по одной строке на список. Нажатие на задачу открывает её в панели справа, а Новая задача открывает посреди экрана форму Новая задача.',
|
||||
// packing-categories
|
||||
@@ -3045,18 +3045,35 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Вставить целый список вещей',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Превратите список, который у вас уже есть где-то ещё, в вещи для сборов за один раз.',
|
||||
'help.guide.import-packing-list.step.1': 'Нажмите Импорт в полосе над списком.',
|
||||
'help.guide.import-packing-list.step.1': 'Нажмите кнопку импорта со стрелкой вниз в полосе над списком.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Один предмет на строку: Категория, Название, Вес в г (необязательно), Багаж (необязательно), checked/unchecked (необязательно). Серый образец в поле показывает все четыре вида.',
|
||||
'Один предмет на строку: Категория, Название, Вес в г (необязательно), Багаж (необязательно), checked/unchecked (необязательно). Серый образец в поле показывает все четыре вида. Подойдёт и список в Markdown: заголовок задаёт название списка, а "- [ ]" и "- [x]" становятся вещами.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Или загрузите строки из файла через Загрузить CSV/TXT. Он берёт .csv или .txt и заменяет то, что в поле.',
|
||||
'Или загрузите строки из файла через Загрузить CSV/TXT/MD. Он берёт .csv, .txt или .md и заменяет то, что в поле.',
|
||||
'help.guide.import-packing-list.step.4': 'Нажмите Импортировать. Кнопка считает строки, которые поняла.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Каждая строка это одна вещь, в том списке, который называет её первое поле, и ничто из того, что уже было, не затрагивается.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Поля разделяют запятые, точки с запятой и табуляции, а двойные кавычки держат поле вместе, так что «Рубашка, синяя» остаётся одним названием. Строка с одним значением это просто название, а строка без своего списка попадает в Другое.',
|
||||
'Поля разделяют запятые, точки с запятой и табуляции, а двойные кавычки держат поле вместе, так что «Рубашка, синяя» остаётся одним названием. Строка с одним значением это просто название, строка без своего списка попадает в Другое, а "3x" перед названием задаёт количество.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Багаж, названный в четвёртом поле, создаётся, если в поездке его ещё нет. Это единственное место, которое загружает вес и багаж массово; шаблон приносит только названия и списки.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Распечатать или экспортировать список вещей',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Возьмите список с собой на бумаге, в PDF или файлом для другого приложения или следующей поездки.',
|
||||
'help.guide.export-packing-list.step.1': 'Нажмите кнопку экспорта со стрелкой вверх в полосе над списком.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Чек-лист в Markdown (.md) и CSV для импорта (.csv) сразу сохраняют список файлом.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Нажмите Печать или сохранение в PDF. Предпросмотр показывает список как страницу: наверху поездка и её даты, затем каждый список карточкой с полем для отметки.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Нажмите Печать или сохранение в PDF под предпросмотром. Браузер откроет своё окно печати: выберите принтер или Сохранить как PDF, чтобы сохранить файл.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Распечатка и файлы содержат открытый вид, Общий или Мой список, с количеством, весом и отметками.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV это формат, который читает Импорт, вместе с багажом, поэтому он работает как ваш собственный шаблон сборов: импортируйте его в следующую поездку.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Файл Markdown открывается как чек-лист в Obsidian, Notion или GitHub и точно так же возвращается через Импорт.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Решить, кто видит вещь и кто её приносит',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Один предмет на строку. Формат: Категория, Название, Вес в г (необязательно), Сумка (необязательно), checked/unchecked (необязательно)',
|
||||
'packing.importPlaceholder':
|
||||
'Гигиена, Зубная щётка\nОдежда, Футболки, 200\nДокументы, Паспорт, , Ручная кладь\nЭлектроника, Зарядка, 50, Чемодан, checked',
|
||||
'packing.importCsv': 'Загрузить CSV/TXT',
|
||||
'packing.importCsv': 'Загрузить CSV/TXT/MD',
|
||||
'packing.export': 'Экспорт',
|
||||
'packing.exportPrint': 'Печать или сохранение в PDF',
|
||||
'packing.exportMarkdown': 'Чек-лист в Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV для импорта (.csv)',
|
||||
'packing.printItems': 'Вещи',
|
||||
'packing.printPacked': 'Собрано',
|
||||
'packing.importHintMarkdown':
|
||||
'Подойдёт и список в Markdown: заголовок задаёт категорию, "- [ ]" и "- [x]" становятся пунктами, а "3x" перед названием задаёт количество.',
|
||||
'packing.importAction': 'Импортировать {count}',
|
||||
'packing.importSuccess': '{count} предметов импортировано',
|
||||
'packing.importError': 'Ошибка импорта',
|
||||
|
||||
@@ -3055,7 +3055,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Zdieľané a Môj zoznam rozdelia zoznam vecí na dve časti: spoločný, ktorý vidia všetci, a váš vlastný. Všetky, Otvorené a Hotovo zúžia ten, ktorý je otvorený, a lišta nad nimi počíta, čo je zabalené.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Použiť šablónu, Importovať a Uložiť ako šablónu naplnia alebo uchovajú zoznam bez vypisovania a červené tlačidlo vedľa nich uvádza, koľko položiek je zaškrtnutých, a odstráni ich.',
|
||||
'Použiť šablónu a Uložiť ako šablónu naplnia alebo uchovajú zoznam bez vypisovania a dve ikony vedľa nich zoznam exportujú, ako výtlačok, PDF alebo súbor, alebo nejaký importujú. Červené tlačidlo uvádza, koľko položiek je zaškrtnutých, a odstráni ich.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Úlohy majú vlastný bočný panel: kartu priebehu, filtre Všetky, Moje úlohy, Po termíne a Hotovo, Zoradiť podľa Priorita alebo Termín splnenia a jeden riadok na zoznam. Kliknutím na úlohu ju otvoríte v paneli vpravo a Pridať novú úlohu otvorí formulár Nová úloha uprostred obrazovky.',
|
||||
// packing-categories
|
||||
@@ -3119,18 +3119,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Vložiť celý zoznam vecí naraz',
|
||||
'help.guide.import-packing-list.goal': 'Premeňte zoznam, ktorý už máte inde, na položky balenia jedným krokom.',
|
||||
'help.guide.import-packing-list.step.1': 'Kliknite na Importovať v lište nad zoznamom.',
|
||||
'help.guide.import-packing-list.step.1': 'Kliknite na tlačidlo importu so šípkou nadol v lište nad zoznamom.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Jedna položka na riadok: Kategória, Názov, Hmotnosť v g (voliteľné), Zavazadlo (voliteľné), checked/unchecked (voliteľné). Sivá ukážka v poli zobrazuje všetky štyri varianty.',
|
||||
'Jedna položka na riadok: Kategória, Názov, Hmotnosť v g (voliteľné), Zavazadlo (voliteľné), checked/unchecked (voliteľné). Sivá ukážka v poli zobrazuje všetky štyri varianty. Funguje aj zoznam v Markdowne: nadpis pomenuje zoznam a z "- [ ]" a "- [x]" sa stanú položky.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Alebo načítajte riadky zo súboru cez Načítať CSV/TXT. Prijme súbor .csv alebo .txt a nahradí všetko, čo je v poli.',
|
||||
'Alebo načítajte riadky zo súboru cez Načítať CSV/TXT/MD. Prijme súbor .csv, .txt alebo .md a nahradí všetko, čo je v poli.',
|
||||
'help.guide.import-packing-list.step.4': 'Kliknite na Importovať. Tlačidlo ukazuje počet riadkov, ktoré rozpoznalo.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Každý riadok sa stane položkou v zozname, ktorý uvádza jeho prvé pole, a nič z toho, čo tam už bolo, sa nezmení.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Polia oddeľujú čiarky, bodkočiarky aj tabulátory a dvojité úvodzovky držia pole pohromade, takže "Tričko, modré" zostane jedným názvom. Riadok s jedinou hodnotou je iba názov a riadok bez vlastného zoznamu skončí v kategórii Ostatné.',
|
||||
'Polia oddeľujú čiarky, bodkočiarky aj tabulátory a dvojité úvodzovky držia pole pohromade, takže "Tričko, modré" zostane jedným názvom. Riadok s jedinou hodnotou je iba názov, riadok bez vlastného zoznamu skončí v kategórii Ostatné a "3x" pred názvom nastaví množstvo.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Zavazadlo uvedené vo štvrtom poli sa vytvorí, ak ho cesta ešte nemá. Je to jediné miesto, kde sa hmotnosti a zavazadlá načítajú hromadne; šablóna prináša iba názvy a zoznamy.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Vytlačiť alebo exportovať zoznam vecí',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Vezmite si zoznam so sebou na papieri, ako PDF alebo ako súbor pre inú aplikáciu či ďalšiu cestu.',
|
||||
'help.guide.export-packing-list.step.1': 'Kliknite na tlačidlo exportu so šípkou nahor v lište nad zoznamom.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Kontrolný zoznam v Markdowne (.md) a CSV na import (.csv) uložia zoznam hneď ako súbor.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Kliknite na Vytlačiť alebo uložiť ako PDF. Náhľad zobrazí zoznam ako stranu: hore cestu a jej termín, potom každý zoznam ako kartu s políčkom na zaškrtnutie.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Pod náhľadom kliknite na Vytlačiť alebo uložiť ako PDF. Prehliadač otvorí svoje dialógové okno tlače: vyberte tlačiareň, alebo Uložiť ako PDF, ak si chcete ponechať súbor.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Výtlačok aj súbory obsahujú otvorené zobrazenie, Zdieľané alebo Môj zoznam, s množstvami, hmotnosťami a zaškrtnutiami.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV je formát, ktorý číta Importovať, vrátane zavazadiel, takže poslúži ako vaša vlastná šablóna balenia: importujte ho do ďalšej cesty.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Súbor Markdown sa v Obsidian, Notion alebo GitHub otvorí ako kontrolný zoznam a cez Importovať sa rovnako vráti späť.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Určiť, kto položku vidí a kto ju prinesie',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Jedna položka na riadok. Formát: Kategória, Názov, Hmotnosť v g (voliteľné), Zavazadlo (voliteľné), checked/unchecked (voliteľné)',
|
||||
'packing.importPlaceholder':
|
||||
'Hygiena, Zubná kefka\nOblečenie, Tričká, 200\nDokumenty, Pas, , Príručná batožina\nElektronika, Nabíjačka, 50, Kufor, checked',
|
||||
'packing.importCsv': 'Načítať CSV/TXT',
|
||||
'packing.importCsv': 'Načítať CSV/TXT/MD',
|
||||
'packing.export': 'Exportovať',
|
||||
'packing.exportPrint': 'Vytlačiť alebo uložiť ako PDF',
|
||||
'packing.exportMarkdown': 'Kontrolný zoznam v Markdowne (.md)',
|
||||
'packing.exportCsv': 'CSV na import (.csv)',
|
||||
'packing.printItems': 'Položky',
|
||||
'packing.printPacked': 'Zabalené',
|
||||
'packing.importHintMarkdown':
|
||||
'Funguje aj zoznam v Markdowne: nadpis určuje kategóriu, z "- [ ]" a "- [x]" sa stanú položky a "3x" pred názvom nastaví množstvo.',
|
||||
'packing.importAction': 'Importovať {count}',
|
||||
'packing.importSuccess': '{count} položiek importovaných',
|
||||
'packing.importError': 'Import zlyhal',
|
||||
|
||||
@@ -2926,7 +2926,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Delat och Min lista delar packlistan i två: poolen alla ser, och din egen. Alla, Öppna och Klar smalnar av den som är öppen, och stapeln ovanför räknar vad som är packat.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Använd mall, Importera och Spara som mall fyller eller behåller en lista utan att du skriver den, och den röda knappen bredvid dem säger hur många föremål som är markerade och sopar bort dem.',
|
||||
'Använd mall och Spara som mall fyller eller behåller en lista utan att du skriver den, och de två ikonerna bredvid dem exporterar listan, som utskrift, som PDF eller som fil, och importerar en. Den röda knappen säger hur många föremål som är markerade och sopar bort dem.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Att göra har en egen sidopanel: framstegskortet, filtren Alla, Mina uppgifter, Förfallen och Slutförda, Sortera efter Prioritet eller Förfallodag, och en rad per lista. Ett klick på en uppgift öppnar den i rutan till höger, och Lägg till ny uppgift öppnar formuläret Ny uppgift över mitten av skärmen.',
|
||||
// packing-categories
|
||||
@@ -2994,18 +2994,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Klistra in en hel packlista',
|
||||
'help.guide.import-packing-list.goal': 'Gör om en lista du redan har någon annanstans till packföremål i ett svep.',
|
||||
'help.guide.import-packing-list.step.1': 'Klicka på Importera i raden ovanför listan.',
|
||||
'help.guide.import-packing-list.step.1': 'Klicka på importknappen med pilen nedåt i raden ovanför listan.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Ett föremål per rad: Kategori, Namn, Vikt i g (valfritt), Väska (valfritt), checked/unchecked (valfritt). Det grå exemplet i rutan visar alla fyra formerna.',
|
||||
'Ett föremål per rad: Kategori, Namn, Vikt i g (valfritt), Väska (valfritt), checked/unchecked (valfritt). Det grå exemplet i rutan visar alla fyra formerna. En Markdown-lista fungerar också: en rubrik namnger listan, och "- [ ]" och "- [x]" blir föremål.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Eller ladda raderna från en fil med Ladda CSV/TXT. Den tar en .csv eller en .txt och ersätter det som står i rutan.',
|
||||
'Eller ladda raderna från en fil med Ladda CSV/TXT/MD. Den tar en .csv, en .txt eller en .md och ersätter det som står i rutan.',
|
||||
'help.guide.import-packing-list.step.4': 'Klicka på Importera. Knappen räknar raderna den förstod.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Varje rad blir ett föremål, i den lista som dess första fält namnger, och inget som redan fanns där rörs.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Kommatecken, semikolon och tabbar skiljer alla fält åt, och citattecken håller ihop ett fält, så ”Skjorta, blå” förblir ett namn. En rad med ett enda värde är bara ett namn, och en rad utan egen lista hamnar i Övrigt.',
|
||||
'Kommatecken, semikolon och tabbar skiljer alla fält åt, och citattecken håller ihop ett fält, så ”Skjorta, blå” förblir ett namn. En rad med ett enda värde är bara ett namn, en rad utan egen lista hamnar i Övrigt, och "3x" framför ett namn anger antalet.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'En väska som namnges i det fjärde fältet skapas om resan inte redan har den. Det här är enda stället som laddar in vikter och väskor i bulk; en mall tar bara med namn och listor.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Skriv ut eller exportera packlistan',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Ta med listan på papper, som PDF eller som fil för en annan app eller nästa resa.',
|
||||
'help.guide.export-packing-list.step.1': 'Klicka på exportknappen med pilen uppåt i raden ovanför listan.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Checklista i Markdown (.md) och CSV för import (.csv) sparar listan som en fil direkt.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Klicka på Skriv ut eller spara som PDF. Förhandsvisningen visar listan som en sida: resan och dess datum överst, sedan varje lista som ett kort med en ruta att bocka i.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Klicka på Skriv ut eller spara som PDF under förhandsvisningen. Webbläsaren öppnar sin utskriftsdialog: välj en skrivare, eller Spara som PDF för att behålla en fil.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Utskriften och filerna innehåller den vy som är öppen, Delat eller Min lista, med antal, vikter och bockar.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV-filen har det format som Importera läser, väskor inräknade, så den fungerar som en egen packmall: importera den till nästa resa.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Markdown-filen öppnas som en checklista i Obsidian, Notion eller GitHub och kommer tillbaka in via Importera på samma sätt.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Bestäm vem som ser ett föremål och vem som tar med det',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Ett föremål per rad. Format: Kategori, Namn, Vikt i g (valfritt), Väska (valfritt), checked/unchecked (valfritt)',
|
||||
'packing.importPlaceholder':
|
||||
'Hygien, Tandborste\nKläder, T-shirts, 200\nDokument, Pass, , Handbagage\nElektronik, Laddare, 50, Resväska, checked',
|
||||
'packing.importCsv': 'Ladda CSV/TXT',
|
||||
'packing.importCsv': 'Ladda CSV/TXT/MD',
|
||||
'packing.export': 'Exportera',
|
||||
'packing.exportPrint': 'Skriv ut eller spara som PDF',
|
||||
'packing.exportMarkdown': 'Checklista i Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV för import (.csv)',
|
||||
'packing.printItems': 'Saker',
|
||||
'packing.printPacked': 'Packade',
|
||||
'packing.importHintMarkdown':
|
||||
'En Markdown-lista fungerar också: en rubrik anger kategorin, "- [ ]" och "- [x]" blir objekt och "3x" före ett namn anger antalet.',
|
||||
'packing.importAction': 'Importera {count}',
|
||||
'packing.importSuccess': '{count} föremål imported',
|
||||
'packing.importError': 'Importen misslyckades',
|
||||
|
||||
@@ -2555,7 +2555,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'แชร์แล้ว และ รายการของฉัน แบ่งรายการจัดกระเป๋าเป็นสองส่วน: ส่วนกลางที่ทุกคนเห็น และส่วนของคุณเอง ทั้งหมด, ยังไม่ได้จัด และ เสร็จแล้ว ใช้กรองส่วนที่เปิดอยู่ และแถบด้านบนนับสิ่งที่จัดแล้ว',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'ใช้เทมเพลต, นำเข้า และ บันทึกเป็นเทมเพลต ใช้เติมหรือเก็บรายการโดยไม่ต้องพิมพ์เอง และปุ่มสีแดงข้างๆ แสดงจำนวนรายการที่ติ๊กไว้และลบรายการเหล่านั้นออก',
|
||||
'ใช้เทมเพลต และ บันทึกเป็นเทมเพลต ใช้เติมหรือเก็บรายการโดยไม่ต้องพิมพ์เอง และไอคอนสองอันข้างๆ ใช้ส่งออกรายการเป็นงานพิมพ์ PDF หรือไฟล์ และใช้นำเข้ารายการ ปุ่มสีแดงแสดงจำนวนรายการที่ติ๊กไว้และลบรายการเหล่านั้นออก',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'สิ่งที่ต้องทำ มีแถบด้านข้างของตัวเอง: การ์ดความคืบหน้า ตัวกรอง ทั้งหมด, งานของฉัน, เกินกำหนด และ เสร็จแล้ว, เรียงตาม ลำดับความสำคัญ หรือ วันครบกำหนด และหนึ่งแถวต่อหนึ่งรายการ คลิกงานเพื่อเปิดในแผงทางขวา และ เพิ่มงานใหม่ จะเปิดแบบฟอร์ม งานใหม่ ตรงกลางหน้าจอ',
|
||||
// packing-categories
|
||||
@@ -2607,17 +2607,33 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'วางรายการจัดกระเป๋าทั้งรายการ',
|
||||
'help.guide.import-packing-list.goal': 'เปลี่ยนรายการที่คุณมีอยู่ที่อื่นให้เป็นสิ่งของสำหรับจัดกระเป๋าในครั้งเดียว',
|
||||
'help.guide.import-packing-list.step.1': 'คลิก นำเข้า ในแถบเหนือรายการ',
|
||||
'help.guide.import-packing-list.step.1': 'คลิกปุ่มนำเข้าที่มีลูกศรชี้ลงในแถบเหนือรายการ',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'หนึ่งสิ่งของต่อบรรทัด: หมวดหมู่, ชื่อ, น้ำหนักเป็นกรัม (ไม่บังคับ), กระเป๋า (ไม่บังคับ), checked/unchecked (ไม่บังคับ) ตัวอย่างสีเทาในช่องแสดงรูปแบบทั้งสี่แบบ',
|
||||
'หนึ่งสิ่งของต่อบรรทัด: หมวดหมู่, ชื่อ, น้ำหนักเป็นกรัม (ไม่บังคับ), กระเป๋า (ไม่บังคับ), checked/unchecked (ไม่บังคับ) ตัวอย่างสีเทาในช่องแสดงรูปแบบทั้งสี่แบบ ใช้รายการ Markdown ได้เช่นกัน: หัวข้อจะเป็นชื่อรายการ และ "- [ ]" กับ "- [x]" จะกลายเป็นสิ่งของ',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'หรือโหลดบรรทัดจากไฟล์ด้วย โหลด CSV/TXT ซึ่งรับไฟล์ .csv หรือ .txt และจะแทนที่ทุกอย่างที่อยู่ในช่อง',
|
||||
'หรือโหลดบรรทัดจากไฟล์ด้วย โหลด CSV/TXT/MD ซึ่งรับไฟล์ .csv, .txt หรือ .md และจะแทนที่ทุกอย่างที่อยู่ในช่อง',
|
||||
'help.guide.import-packing-list.step.4': 'คลิก นำเข้า ปุ่มจะแสดงจำนวนบรรทัดที่อ่านเข้าใจได้',
|
||||
'help.guide.import-packing-list.result': 'ทุกบรรทัดกลายเป็นหนึ่งแถว อยู่ในรายการที่ระบุในช่องแรก และสิ่งที่มีอยู่เดิมจะไม่ถูกแตะต้อง',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'เครื่องหมายจุลภาค อัฒภาค และแท็บ ล้วนใช้แบ่งช่องข้อมูลได้ และอัญประกาศคู่ใช้รวมช่องข้อมูลไว้ด้วยกัน ดังนั้น "เสื้อ, สีน้ำเงิน" จะยังเป็นชื่อเดียว บรรทัดที่มีค่าเดียวจะเป็นเพียงชื่อ และบรรทัดที่ไม่ได้ระบุรายการจะไปอยู่ใน อื่นๆ',
|
||||
'เครื่องหมายจุลภาค อัฒภาค และแท็บ ล้วนใช้แบ่งช่องข้อมูลได้ และอัญประกาศคู่ใช้รวมช่องข้อมูลไว้ด้วยกัน ดังนั้น "เสื้อ, สีน้ำเงิน" จะยังเป็นชื่อเดียว บรรทัดที่มีค่าเดียวจะเป็นเพียงชื่อ บรรทัดที่ไม่ได้ระบุรายการจะไปอยู่ใน อื่นๆ และ "3x" หน้าชื่อจะกำหนดจำนวน',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'กระเป๋าที่ระบุในช่องที่สี่จะถูกสร้างขึ้นหากทริปยังไม่มี นี่เป็นที่เดียวที่โหลดน้ำหนักและกระเป๋าได้ทีละมากๆ ส่วนเทมเพลตจะนำมาเพียงชื่อและรายการเท่านั้น',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'พิมพ์หรือส่งออกรายการจัดกระเป๋า',
|
||||
'help.guide.export-packing-list.goal': 'นำรายการติดตัวไปเป็นกระดาษ เป็น PDF หรือเป็นไฟล์สำหรับแอปอื่นหรือทริปถัดไป',
|
||||
'help.guide.export-packing-list.step.1': 'คลิกปุ่มส่งออกที่มีลูกศรชี้ขึ้นในแถบเหนือรายการ',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'รายการตรวจสอบ Markdown (.md) และ CSV สำหรับนำเข้า (.csv) จะบันทึกรายการเป็นไฟล์ทันที',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'คลิก พิมพ์หรือบันทึกเป็น PDF ตัวอย่างก่อนพิมพ์จะแสดงรายการเป็นหน้ากระดาษ: ด้านบนเป็นทริปและวันที่ จากนั้นแต่ละรายการเป็นการ์ดพร้อมช่องให้ติ๊ก',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'คลิก พิมพ์หรือบันทึกเป็น PDF ใต้ตัวอย่างก่อนพิมพ์ เบราว์เซอร์จะเปิดหน้าต่างการพิมพ์: เลือกเครื่องพิมพ์ หรือเลือก บันทึกเป็น PDF เพื่อเก็บเป็นไฟล์',
|
||||
'help.guide.export-packing-list.result':
|
||||
'งานพิมพ์และไฟล์จะมีมุมมองที่เปิดอยู่ คือ แชร์แล้ว หรือ รายการของฉัน พร้อมจำนวน น้ำหนัก และเครื่องหมายติ๊ก',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV คือรูปแบบที่ นำเข้า อ่านได้ รวมถึงกระเป๋าด้วย จึงใช้เป็นเทมเพลตจัดกระเป๋าของคุณเองได้: นำเข้าไฟล์นี้ในทริปถัดไป',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'ไฟล์ Markdown จะเปิดเป็นรายการตรวจสอบใน Obsidian, Notion หรือ GitHub และนำกลับเข้ามาผ่าน นำเข้า ได้เช่นเดียวกัน',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'กำหนดว่าใครเห็นสิ่งของและใครเป็นคนนำไป',
|
||||
'help.guide.share-packing-item.goal': 'ย้ายสิ่งของระหว่างส่วนกลางของกลุ่ม รายการของคุณเอง และคนที่คุณนำไปให้',
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
"หนึ่งรายการต่อบรรทัด รูปแบบ: หมวดหมู่ ชื่อ น้ำหนักเป็นกรัม (ไม่จำเป็น) กระเป๋า (ไม่จำเป็น) เลือก/ไม่เลือก (ไม่จำเป็น)",
|
||||
'packing.importPlaceholder':
|
||||
"สุขอนามัย, แปรงสีฟัน\nเสื้อผ้า เสื้อยืด 200\nเอกสาร, หนังสือเดินทาง, , กระเป๋าถือขึ้นเครื่อง\nเครื่องใช้ไฟฟ้า ที่ชาร์จ 50 กระเป๋าเดินทาง ตรวจเช็คแล้ว",
|
||||
'packing.importCsv': "โหลด CSV/TXT",
|
||||
'packing.importCsv': "โหลด CSV/TXT/MD",
|
||||
'packing.export': 'ส่งออก',
|
||||
'packing.exportPrint': 'พิมพ์หรือบันทึกเป็น PDF',
|
||||
'packing.exportMarkdown': 'รายการตรวจสอบ Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV สำหรับนำเข้า (.csv)',
|
||||
'packing.printItems': 'รายการ',
|
||||
'packing.printPacked': 'จัดแล้ว',
|
||||
'packing.importHintMarkdown':
|
||||
'ใช้รายการ Markdown ได้เช่นกัน: หัวข้อจะเป็นหมวดหมู่ "- [ ]" และ "- [x]" จะกลายเป็นรายการ และ "3x" หน้าชื่อจะกำหนดจำนวน',
|
||||
'packing.importAction': "นำเข้า {count}",
|
||||
'packing.importSuccess': "{count} รายการที่นำเข้า",
|
||||
'packing.importError': "การนำเข้าล้มเหลว",
|
||||
|
||||
@@ -2965,7 +2965,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Ortak ve Listem paket listesini ikiye böler: herkesin gördüğü havuz ve sizin kendi listeniz. Tüm, Açık ve Tamamlamak hangisi açıksa onu daraltır ve üstteki çubuk neyin paketlendiğini sayar.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Şablon uygula, İçe aktar ve Şablon olarak kaydet bir listeyi yazmadan doldurur ya da saklar, yanlarındaki kırmızı düğme ise kaç öğenin işaretli olduğunu söyler ve onları temizler.',
|
||||
'Şablon uygula ve Şablon olarak kaydet bir listeyi yazmadan doldurur ya da saklar, yanlarındaki iki simge ise listeyi çıktı, PDF ya da dosya olarak dışa aktarır ve bir liste içe aktarır. Kırmızı düğme kaç öğenin işaretli olduğunu söyler ve onları temizler.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Yapılacaklar kendi kenar çubuğuna sahiptir: ilerleme kartı, Tümü, Görevlerim, Gecikmiş ve Tamamlandı filtreleri, Sırala Öncelik ya da Son tarih ve liste başına bir satır. Bir göreve tıklamak onu sağdaki bölmede açar, Yeni görev ekle ise ekranın ortasında Yeni görev formunu açar.',
|
||||
// packing-categories
|
||||
@@ -3032,18 +3032,35 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Bütün bir paket listesini yapıştırın',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Başka bir yerde zaten var olan bir listeyi tek seferde paket öğelerine çevirin.',
|
||||
'help.guide.import-packing-list.step.1': 'Listenin üstündeki çubukta İçe aktar düğmesine tıklayın.',
|
||||
'help.guide.import-packing-list.step.1': 'Listenin üstündeki çubukta aşağı oklu içe aktarma düğmesine tıklayın.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Satır başına bir öğe: Kategori, Ad, g cinsinden ağırlık (isteğe bağlı), Çanta (isteğe bağlı), checked/unchecked (isteğe bağlı). Kutudaki gri örnek dört biçimi de gösterir.',
|
||||
'Satır başına bir öğe: Kategori, Ad, g cinsinden ağırlık (isteğe bağlı), Çanta (isteğe bağlı), checked/unchecked (isteğe bağlı). Kutudaki gri örnek dört biçimi de gösterir. Markdown listesi de olur: başlık listeye adını verir, "- [ ]" ve "- [x]" ise öğeye dönüşür.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Ya da satırları CSV/TXT Yükle ile bir dosyadan yükleyin. Bir .csv ya da bir .txt alır ve kutuda ne varsa onun yerini alır.',
|
||||
'Ya da satırları CSV/TXT/MD Yükle ile bir dosyadan yükleyin. Bir .csv, bir .txt ya da bir .md alır ve kutuda ne varsa onun yerini alır.',
|
||||
'help.guide.import-packing-list.step.4': 'İçe aktar düğmesine tıklayın. Düğme anladığı satırları sayar.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Her satır bir öğedir, ilk alanının adlandırdığı listede, ve zaten orada olan hiçbir şeye dokunulmaz.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Alanları virgüller, noktalı virgüller ve sekmeler ayırır, çift tırnaklar ise bir alanı bir arada tutar, böylece “Gömlek, mavi” tek bir ad olarak kalır. Tek değerli bir satır sadece bir addır ve kendi listesi olmayan bir satır Diğer içine iner.',
|
||||
'Alanları virgüller, noktalı virgüller ve sekmeler ayırır, çift tırnaklar ise bir alanı bir arada tutar, böylece “Gömlek, mavi” tek bir ad olarak kalır. Tek değerli bir satır sadece bir addır, kendi listesi olmayan bir satır Diğer içine iner ve bir adın önündeki "3x" adedi belirler.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Dördüncü alanda adı geçen bir çanta, gezide henüz yoksa oluşturulur. Ağırlıkları ve çantaları toplu yükleyen tek yer burasıdır; bir şablon yalnızca adları ve listeleri getirir.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Paket listesini yazdırın veya dışa aktarın',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Listeyi kâğıt üzerinde, PDF olarak ya da başka bir uygulama veya sonraki gezi için dosya olarak yanınıza alın.',
|
||||
'help.guide.export-packing-list.step.1': 'Listenin üstündeki çubukta yukarı oklu dışa aktarma düğmesine tıklayın.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Markdown kontrol listesi (.md) ve İçe aktarma için CSV (.csv) listeyi hemen dosya olarak kaydeder.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Yazdır veya PDF olarak kaydet seçeneğine tıklayın. Önizleme listeyi bir sayfa olarak gösterir: üstte gezi ve tarihleri, ardından her liste işaretlenecek bir kutuyla birlikte bir kart olarak.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Önizlemenin altındaki Yazdır veya PDF olarak kaydet düğmesine tıklayın. Tarayıcı kendi yazdırma penceresini açar: bir yazıcı seçin ya da dosya olarak saklamak için PDF olarak kaydet seçeneğini seçin.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Çıktı ve dosyalar açık olan görünümü, Ortak ya da Listem, adetler, ağırlıklar ve işaretlerle birlikte içerir.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV, İçe aktar özelliğinin okuduğu biçimdir, çantalar dahil, bu yüzden size ait bir paket şablonu gibi çalışır: onu sonraki geziye içe aktarın.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Markdown dosyası Obsidian, Notion veya GitHub içinde bir kontrol listesi olarak açılır ve İçe aktar ile aynı şekilde geri gelir.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Bir öğeyi kimin göreceğine ve kimin getireceğine karar verin',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Satır başına bir öğe. Biçim: Kategori, Ad, Ağırlık (g, isteğe bağlı), Çanta (isteğe bağlı), işaretli/işaretsiz (isteğe bağlı)',
|
||||
'packing.importPlaceholder':
|
||||
'Hijyen, Diş fırçası\\nGiyim, Tişört, 200\\nBelgeler, Pasaport, , El bagajı\\nElektronik, Şarj cihazı, 50, Bavul, işaretli',
|
||||
'packing.importCsv': 'CSV/TXT Yükle',
|
||||
'packing.importCsv': 'CSV/TXT/MD Yükle',
|
||||
'packing.export': 'Dışa aktar',
|
||||
'packing.exportPrint': 'Yazdır veya PDF olarak kaydet',
|
||||
'packing.exportMarkdown': 'Markdown kontrol listesi (.md)',
|
||||
'packing.exportCsv': 'İçe aktarma için CSV (.csv)',
|
||||
'packing.printItems': 'Öğeler',
|
||||
'packing.printPacked': 'Paketlendi',
|
||||
'packing.importHintMarkdown':
|
||||
'Markdown listesi de olur: başlık kategoriyi belirler, "- [ ]" ve "- [x]" öğeye dönüşür, bir adın önündeki "3x" ise miktarı belirler.',
|
||||
'packing.importAction': '{count} Öğeyi içe aktar',
|
||||
'packing.importSuccess': '{count} öğe içe aktarıldı',
|
||||
'packing.importError': 'İçe aktarma başarısız oldu',
|
||||
|
||||
@@ -2946,7 +2946,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'Спільне і Мій список ділять список речей надвоє: пул, який бачать усі, і ваш власний. Усі, Не зібрано та Зібрано звужують той, який відкритий, а смуга вгорі рахує, що зібрано.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'Застосувати шаблон, Імпорт і Зберегти як шаблон наповнюють або зберігають список без набирання вручну, а червона кнопка поруч називає, скільки речей позначено, і прибирає їх.',
|
||||
'Застосувати шаблон і Зберегти як шаблон наповнюють або зберігають список без набирання вручну, а дві іконки поруч експортують список, як роздруківку, PDF або файл, та імпортують список. Червона кнопка називає, скільки речей позначено, і прибирає їх.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'Задачі мають власну бічну панель: картку прогресу, фільтри Усі, Мої задачі, Просрочені та Виконані, Сорзувати за Пріоритет або Строк виконання і по одному рядку на список. Натискання на задачу відкриває її в панелі праворуч, а Нова задача відкриває посеред екрана форму Нова задача.',
|
||||
// packing-categories
|
||||
@@ -3010,18 +3010,35 @@ const help: TranslationStrings = {
|
||||
// import-packing-list
|
||||
'help.guide.import-packing-list.title': 'Вставити цілий список речей',
|
||||
'help.guide.import-packing-list.goal': 'Перетворіть список, який у вас уже є деінде, на речі для зборів за один раз.',
|
||||
'help.guide.import-packing-list.step.1': 'Натисніть Імпорт у смузі над списком.',
|
||||
'help.guide.import-packing-list.step.1': 'Натисніть кнопку імпорту зі стрілкою вниз у смузі над списком.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Один предмет у рядок: Категорія, Назва, Вага в г (необов’язково), Багаж (необов’язково), checked/unchecked (необов’язково). Сірий зразок у полі показує всі чотири види.',
|
||||
'Один предмет у рядок: Категорія, Назва, Вага в г (необов’язково), Багаж (необов’язково), checked/unchecked (необов’язково). Сірий зразок у полі показує всі чотири види. Підійде і список у Markdown: заголовок задає назву списку, а "- [ ]" і "- [x]" стають речами.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Або завантажте рядки з файлу через Завантажити CSV/TXT. Він бере .csv або .txt і замінює те, що в полі.',
|
||||
'Або завантажте рядки з файлу через Завантажити CSV/TXT/MD. Він бере .csv, .txt або .md і замінює те, що в полі.',
|
||||
'help.guide.import-packing-list.step.4': 'Натисніть Імпортувати. Кнопка рахує рядки, які зрозуміла.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Кожен рядок це одна річ, у тому списку, який називає її перше поле, і нічого з того, що вже було, не зачіпається.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Поля розділяють коми, крапки з комою і табуляції, а подвійні лапки тримають поле разом, тож «Сорочка, синя» лишається однією назвою. Рядок з одним значенням це просто назва, а рядок без власного списку потрапляє в Інше.',
|
||||
'Поля розділяють коми, крапки з комою і табуляції, а подвійні лапки тримають поле разом, тож «Сорочка, синя» лишається однією назвою. Рядок з одним значенням це просто назва, рядок без власного списку потрапляє в Інше, а "3x" перед назвою задає кількість.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Багаж, названий у четвертому полі, створюється, якщо в поїздці його ще немає. Це єдине місце, яке завантажує вагу й багаж масово; шаблон приносить лише назви і списки.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'Роздрукувати або експортувати список речей',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Візьміть список із собою на папері, у PDF або файлом для іншого застосунку чи наступної поїздки.',
|
||||
'help.guide.export-packing-list.step.1': 'Натисніть кнопку експорту зі стрілкою вгору у смузі над списком.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'Чек-лист у Markdown (.md) і CSV для імпорту (.csv) одразу зберігають список файлом.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Натисніть Друк або збереження в PDF. Попередній перегляд показує список як сторінку: угорі поїздка та її дати, далі кожен список карткою з полем для позначки.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Натисніть Друк або збереження в PDF під попереднім переглядом. Браузер відкриє своє вікно друку: виберіть принтер або Зберегти як PDF, щоб зберегти файл.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Роздруківка й файли містять відкритий вигляд, Спільне або Мій список, з кількістю, вагою та позначками.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV це формат, який читає Імпорт, разом із багажем, тож він працює як ваш власний шаблон зборів: імпортуйте його в наступну поїздку.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Файл Markdown відкривається як чек-лист в Obsidian, Notion або GitHub і так само повертається через Імпорт.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Вирішити, хто бачить річ і хто її приносить',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||
@@ -18,7 +18,15 @@ const packing: TranslationStrings = {
|
||||
'Один предмет у рядок. Формат: Категорія, Назва, Вага в г (необов’язково), Сумка (необов’язково), checked/unchecked (необов’язково)',
|
||||
'packing.importPlaceholder':
|
||||
'Гігієна, Зубна щітка\nОдяг, Футболки, 200\nДокументи, Паспорт, , Ручна поклажа\nЕлектроніка, Зарядний пристрій, 50, Валіза, checked',
|
||||
'packing.importCsv': 'Завантажити CSV/TXT',
|
||||
'packing.importCsv': 'Завантажити CSV/TXT/MD',
|
||||
'packing.export': 'Експорт',
|
||||
'packing.exportPrint': 'Друк або збереження в PDF',
|
||||
'packing.exportMarkdown': 'Чек-лист у Markdown (.md)',
|
||||
'packing.exportCsv': 'CSV для імпорту (.csv)',
|
||||
'packing.printItems': 'Речі',
|
||||
'packing.printPacked': 'Зібрано',
|
||||
'packing.importHintMarkdown':
|
||||
'Підійде і список у Markdown: заголовок задає категорію, "- [ ]" і "- [x]" стають пунктами, а "3x" перед назвою задає кількість.',
|
||||
'packing.importAction': 'Імпортувати {count}',
|
||||
'packing.importSuccess': '{count} предметів імпортовано',
|
||||
'packing.importError': 'Помилка імпорту',
|
||||
|
||||
@@ -2950,7 +2950,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.trip-lists.bullet.4':
|
||||
'“Chung” và “Danh sách của tôi” chia danh sách đóng gói làm hai: kho chung mọi người đều thấy, và của riêng bạn. “Tất cả”, “Mở” và “Xong” thu hẹp bên nào đang mở, và thanh phía trên đếm những gì đã đóng gói.',
|
||||
'help.ctx.trip-lists.bullet.5':
|
||||
'“Áp dụng mẫu”, “Nhập” và “Lưu dưới dạng mẫu” lấp đầy hoặc giữ lại một danh sách mà không phải gõ ra, và nút đỏ bên cạnh nêu có bao nhiêu mục đã được đánh dấu rồi dọn chúng đi.',
|
||||
'“Áp dụng mẫu” và “Lưu dưới dạng mẫu” lấp đầy hoặc giữ lại một danh sách mà không phải gõ ra, còn hai biểu tượng bên cạnh xuất danh sách, dưới dạng bản in, PDF hoặc tập tin, và nhập một danh sách. Nút đỏ nêu có bao nhiêu mục đã được đánh dấu rồi dọn chúng đi.',
|
||||
'help.ctx.trip-lists.bullet.6':
|
||||
'“Việc cần làm” có thanh bên riêng: thẻ tiến độ, các bộ lọc “Tất cả”, “Nhiệm vụ của tôi”, “Quá hạn” và “Xong”, “Sắp xếp theo” “Sự ưu tiên” hoặc “Ngày đến hạn”, và một hàng cho mỗi danh sách. Nhấp vào một nhiệm vụ sẽ mở nó ở khung bên phải, và “Thêm nhiệm vụ mới” mở biểu mẫu “Nhiệm vụ mới” ngay giữa màn hình.',
|
||||
// packing-categories
|
||||
@@ -3018,18 +3018,35 @@ const help: TranslationStrings = {
|
||||
'help.guide.import-packing-list.title': 'Dán nguyên một danh sách đóng gói vào',
|
||||
'help.guide.import-packing-list.goal':
|
||||
'Biến một danh sách bạn đã có ở nơi khác thành các mục đóng gói trong một lần.',
|
||||
'help.guide.import-packing-list.step.1': 'Nhấp “Nhập” ở thanh phía trên danh sách.',
|
||||
'help.guide.import-packing-list.step.1': 'Nhấp nút nhập có mũi tên hướng xuống ở thanh phía trên danh sách.',
|
||||
'help.guide.import-packing-list.step.2':
|
||||
'Mỗi dòng một mục: Danh mục, Tên, Trọng lượng tính bằng g (tùy chọn), Túi (tùy chọn), checked/unchecked (tùy chọn). Mẫu màu xám trong ô cho thấy cả bốn dạng.',
|
||||
'Mỗi dòng một mục: Danh mục, Tên, Trọng lượng tính bằng g (tùy chọn), Túi (tùy chọn), checked/unchecked (tùy chọn). Mẫu màu xám trong ô cho thấy cả bốn dạng. Danh sách Markdown cũng dùng được: tiêu đề đặt tên cho danh sách, còn "- [ ]" và "- [x]" trở thành mục.',
|
||||
'help.guide.import-packing-list.step.3':
|
||||
'Hoặc nạp các dòng từ một tập tin bằng “Tải CSV/TXT”. Nó nhận một tập tin .csv hoặc .txt và thay thế mọi thứ đang có trong ô.',
|
||||
'Hoặc nạp các dòng từ một tập tin bằng “Tải CSV/TXT/MD”. Nó nhận một tập tin .csv, .txt hoặc .md và thay thế mọi thứ đang có trong ô.',
|
||||
'help.guide.import-packing-list.step.4': 'Nhấp “Nhập”. Nút đó đếm số dòng nó hiểu được.',
|
||||
'help.guide.import-packing-list.result':
|
||||
'Mỗi dòng thành một hàng, nằm trong danh sách mà trường đầu tiên của nó nêu tên, và không có gì vốn đã ở đó bị đụng tới.',
|
||||
'help.guide.import-packing-list.tip.1':
|
||||
'Dấu phẩy, dấu chấm phẩy và dấu tab đều tách các trường, còn dấu nháy kép giữ một trường lại với nhau, nên “Shirt, blue” vẫn là một cái tên. Một dòng chỉ có một giá trị thì đó là tên, và một dòng không có danh sách riêng sẽ rơi vào “Khác”.',
|
||||
'Dấu phẩy, dấu chấm phẩy và dấu tab đều tách các trường, còn dấu nháy kép giữ một trường lại với nhau, nên “Shirt, blue” vẫn là một cái tên. Một dòng chỉ có một giá trị thì đó là tên, một dòng không có danh sách riêng sẽ rơi vào “Khác”, và "3x" trước tên sẽ đặt số lượng.',
|
||||
'help.guide.import-packing-list.tip.2':
|
||||
'Túi được nêu tên ở trường thứ tư sẽ được tạo nếu chuyến đi chưa có nó. Đây là nơi duy nhất nạp trọng lượng và túi hàng loạt; một mẫu chỉ mang theo tên và danh sách.',
|
||||
// export-packing-list
|
||||
'help.guide.export-packing-list.title': 'In hoặc xuất danh sách đóng gói',
|
||||
'help.guide.export-packing-list.goal':
|
||||
'Mang danh sách theo trên giấy, dưới dạng PDF, hoặc dưới dạng tập tin cho ứng dụng khác hay cho chuyến đi sau.',
|
||||
'help.guide.export-packing-list.step.1': 'Nhấp nút xuất có mũi tên hướng lên ở thanh phía trên danh sách.',
|
||||
'help.guide.export-packing-list.step.2':
|
||||
'“Danh sách kiểm tra Markdown (.md)” và “CSV để nhập (.csv)” lưu danh sách thành tập tin ngay lập tức.',
|
||||
'help.guide.export-packing-list.step.3':
|
||||
'Nhấp “In hoặc lưu thành PDF”. Bản xem trước hiển thị danh sách như một trang: chuyến đi và ngày tháng ở trên cùng, rồi mỗi danh sách là một thẻ có ô để đánh dấu.',
|
||||
'help.guide.export-packing-list.step.4':
|
||||
'Nhấp “In hoặc lưu thành PDF” bên dưới bản xem trước. Trình duyệt mở hộp thoại in của nó: chọn một máy in, hoặc “Lưu dưới dạng PDF” để giữ một tập tin.',
|
||||
'help.guide.export-packing-list.result':
|
||||
'Bản in và các tập tin chứa chế độ xem đang mở, “Chung” hoặc “Danh sách của tôi”, kèm số lượng, trọng lượng và dấu tích.',
|
||||
'help.guide.export-packing-list.tip.1':
|
||||
'CSV là định dạng mà “Nhập” đọc được, kể cả túi, nên nó dùng được như một mẫu đóng gói của riêng bạn: hãy nhập nó vào chuyến đi sau.',
|
||||
'help.guide.export-packing-list.tip.2':
|
||||
'Tập tin Markdown mở ra thành danh sách kiểm tra trong Obsidian, Notion hoặc GitHub, và cũng quay lại được qua “Nhập” như vậy.',
|
||||
// share-packing-item
|
||||
'help.guide.share-packing-item.title': 'Quyết định ai thấy một mục và ai mang nó',
|
||||
'help.guide.share-packing-item.goal':
|
||||
|
||||