mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(places): add a place straight into the day that is open
The places pool sits one click away from the plan, and a place you already know belongs to today had to go through the day picker afterwards to get there. With a day open, the add button splits: the left half still opens an empty form, the right half opens one that assigns the place to that day on save. The plumbing was already there. placeFormDayId has assigned a freshly created place to a day since #1998; nothing in the places rail ever set it. The main label shortens to "New place" while the split is out, so both buttons fit side by side in the rail without either one truncating. The second button stays mounted and collapsed rather than unmounted, so it has something to animate out of: width, padding and opacity over 180ms, and nothing at all under prefers-reduced-motion. Collapsed it is also out of the tab order and hidden from screen readers, because a zero-width button is still focusable otherwise. Both columns of the place form are 320px now. Two panels of different widths flanking one form read as a mistake rather than as a hierarchy, and neither of them is the more important one.
This commit is contained in:
@@ -183,7 +183,10 @@ export default function CollectionPicker({ bias, onSelect, t }: CollectionPicker
|
||||
]
|
||||
|
||||
return (
|
||||
<aside className="w-full sm:w-64 shrink-0 flex flex-col rounded-xl border border-edge bg-surface-secondary overflow-hidden self-stretch">
|
||||
// Same 320px as the details column on the other side of the form: two panels
|
||||
// of different widths flanking one form read as a mistake rather than a
|
||||
// hierarchy, and neither of them is the more important one.
|
||||
<aside className="w-full sm:w-80 shrink-0 flex flex-col rounded-xl border border-edge bg-surface-secondary overflow-hidden self-stretch">
|
||||
<div className="flex items-center gap-2 px-3 py-2.5 border-b border-edge shrink-0">
|
||||
<Bookmark size={15} className="text-accent" />
|
||||
<span className="text-[13px] font-semibold text-content">{t('collections.picker.title')}</span>
|
||||
|
||||
@@ -88,6 +88,39 @@ describe('PlacesSidebar', () => {
|
||||
expect(onAddPlace).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
/**
|
||||
* The split add button (a day is open).
|
||||
*
|
||||
* Two buttons rather than one, because the pool sits a click away from the plan
|
||||
* and a place you already know belongs to today should not need a second trip
|
||||
* through the day picker. The second one stays mounted and collapsed so it has
|
||||
* something to animate out of.
|
||||
*/
|
||||
it('FE-COMP-PLACES-005b: with no day open there is one add button, at full length', () => {
|
||||
render(<PlacesSidebar {...defaultProps} selectedDayId={null} onAddPlaceToSelectedDay={vi.fn()} />);
|
||||
|
||||
expect(screen.getAllByText(/Add Place\/Activity/i).length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('New place')).not.toBeInTheDocument();
|
||||
// Present in the DOM so it can animate, but out of reach until a day is open.
|
||||
const quick = screen.getByTestId('add-place-to-day');
|
||||
expect(quick).toHaveAttribute('aria-hidden', 'true');
|
||||
expect(quick).toHaveAttribute('tabindex', '-1');
|
||||
});
|
||||
|
||||
it('FE-COMP-PLACES-005c: an open day splits the button and shortens the main label', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onAddPlaceToSelectedDay = vi.fn();
|
||||
render(<PlacesSidebar {...defaultProps} selectedDayId={7} onAddPlaceToSelectedDay={onAddPlaceToSelectedDay} />);
|
||||
|
||||
// Shortened so both fit side by side without either truncating.
|
||||
expect(screen.getByText('New place')).toBeInTheDocument();
|
||||
|
||||
const quick = screen.getByTestId('add-place-to-day');
|
||||
expect(quick).toHaveAttribute('tabindex', '0');
|
||||
await user.click(quick);
|
||||
expect(onAddPlaceToSelectedDay).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('FE-COMP-PLACES-006: clicking a place calls onPlaceClick with place id', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onPlaceClick = vi.fn();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Search, Plus, X, Upload, FileDown, ChevronDown, Check, MapPin, Star } from 'lucide-react'
|
||||
import { Search, Plus, X, Upload, FileDown, ChevronDown, Check, MapPin, Star, CalendarPlus } from 'lucide-react'
|
||||
import { getCategoryIcon } from '../shared/categoryIcons'
|
||||
import Tooltip from '../shared/Tooltip'
|
||||
import type { SidebarState } from './usePlacesSidebar'
|
||||
@@ -21,27 +21,66 @@ export function PlacesDropOverlay({ t }: SidebarState) {
|
||||
|
||||
export function PlacesHeader(S: SidebarState) {
|
||||
const {
|
||||
canEditPlaces, onAddPlace, t, setFileImportOpen, setListImportOpen, hasMultipleListImportProviders,
|
||||
canEditPlaces, onAddPlace, onAddPlaceToSelectedDay, selectedDayId, t, setFileImportOpen, setListImportOpen, hasMultipleListImportProviders,
|
||||
places, categories, categoryFilters, search, setSearch, plannedIds, hasTracks,
|
||||
filter, setFilter, setSelectedIds, selectMode, setSelectMode,
|
||||
catDropOpen, setCatDropOpen, toggleCategoryFilter, setCategoryFilters,
|
||||
ratingFilter, setRatingFilter,
|
||||
starDropOpen, setStarDropOpen,
|
||||
} = S
|
||||
const dayOpen = selectedDayId != null
|
||||
return (
|
||||
<div className="border-b border-edge-faint" style={{ padding: '14px 16px 10px', flexShrink: 0 }}>
|
||||
{canEditPlaces && <button type="button"
|
||||
onClick={onAddPlace}
|
||||
className="bg-accent text-accent-text"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
|
||||
width: '100%', padding: '8px 12px', borderRadius: 12, border: 'none',
|
||||
fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 500,
|
||||
cursor: 'pointer', fontFamily: 'inherit', marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Plus size={14} strokeWidth={2} /> {t('places.addPlace')}
|
||||
</button>}
|
||||
{canEditPlaces && <div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
|
||||
<button type="button"
|
||||
onClick={onAddPlace}
|
||||
className="bg-accent text-accent-text"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
|
||||
flex: 1, minWidth: 0, padding: '8px 12px', borderRadius: 12, border: 'none',
|
||||
fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 500,
|
||||
cursor: 'pointer', fontFamily: 'inherit',
|
||||
}}
|
||||
>
|
||||
<Plus size={14} strokeWidth={2} />
|
||||
{/* Shortened while the second button is out, so both fit side by side
|
||||
in a 320px rail without either one truncating. */}
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{t(dayOpen ? 'places.addPlaceShort' : 'places.addPlace')}
|
||||
</span>
|
||||
</button>
|
||||
{/* Kept mounted and collapsed rather than unmounted, so it has something
|
||||
to animate out of: a button that only exists while a day is open
|
||||
would appear and vanish on the spot. Hidden from the tab order and
|
||||
from screen readers in the same breath, because a zero-width button
|
||||
is still focusable otherwise. */}
|
||||
{onAddPlaceToSelectedDay && (
|
||||
<Tooltip label={t('places.addToSelectedDay')} disabled={!dayOpen}>
|
||||
<button type="button"
|
||||
onClick={onAddPlaceToSelectedDay}
|
||||
aria-label={t('places.addToSelectedDay')}
|
||||
data-testid="add-place-to-day"
|
||||
aria-hidden={!dayOpen}
|
||||
tabIndex={dayOpen ? 0 : -1}
|
||||
className="bg-accent text-accent-text motion-reduce:transition-none"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
|
||||
maxWidth: dayOpen ? 160 : 0,
|
||||
opacity: dayOpen ? 1 : 0,
|
||||
padding: dayOpen ? '8px 12px' : 0,
|
||||
borderRadius: 12, border: 'none', flexShrink: 0,
|
||||
fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 500,
|
||||
cursor: 'pointer', fontFamily: 'inherit', overflow: 'hidden', whiteSpace: 'nowrap',
|
||||
pointerEvents: dayOpen ? 'auto' : 'none',
|
||||
transition: 'max-width 180ms ease, opacity 140ms ease, padding 180ms ease',
|
||||
}}
|
||||
>
|
||||
<CalendarPlus size={14} strokeWidth={2} style={{ flexShrink: 0 }} />
|
||||
{t('places.addToDayShort')}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>}
|
||||
{canEditPlaces && <>
|
||||
<div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
|
||||
<button type="button"
|
||||
|
||||
@@ -31,6 +31,15 @@ export interface PlacesSidebarProps {
|
||||
selectedPlaceId: number | null
|
||||
onPlaceClick: (placeId: number | null) => void
|
||||
onAddPlace: () => void
|
||||
/**
|
||||
* Create a place and drop it straight into the day that is open.
|
||||
*
|
||||
* Only reachable while a day is selected, which is what the split button in
|
||||
* the header is about: the pool is one click away from the plan, and adding a
|
||||
* place you already know belongs to today should not need a second trip
|
||||
* through the day picker.
|
||||
*/
|
||||
onAddPlaceToSelectedDay?: () => void
|
||||
onAssignToDay: (placeId: number, dayId: number) => void
|
||||
onEditPlace: (place: Place) => void
|
||||
onDeletePlace: (placeId: number) => void
|
||||
|
||||
@@ -260,7 +260,7 @@ function TripPlannerPageDesktop(): React.ReactElement | null {
|
||||
startResizeLeft, startResizeRight,
|
||||
selectedPlaceId, selectedAssignmentId, setSelectedPlaceId, selectAssignment,
|
||||
showDayDetail, setShowDayDetail, dayDetailCollapsed, setDayDetailCollapsed,
|
||||
showPlaceForm, setShowPlaceForm, editingPlace, setEditingPlace,
|
||||
showPlaceForm, setShowPlaceForm, editingPlace, setEditingPlace, setPlaceFormDayId,
|
||||
prefillCoords, setPrefillCoords, editingAssignmentId, setEditingAssignmentId,
|
||||
stopDraft, setStopDraft, saveStopDraft, saveStopDraftAsNight, stopDraftToForm, stopDraftDuplicate, reorderRoadtripStop,
|
||||
setRoadtripStopKind,
|
||||
@@ -674,7 +674,8 @@ function TripPlannerPageDesktop(): React.ReactElement | null {
|
||||
selectedDayId={selectedDayId}
|
||||
selectedPlaceId={selectedPlaceId}
|
||||
onPlaceClick={handlePlaceClick}
|
||||
onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true) }}
|
||||
onAddPlace={() => { setEditingPlace(null); setPlaceFormDayId(null); setShowPlaceForm(true) }}
|
||||
onAddPlaceToSelectedDay={selectedDayId != null ? () => { setEditingPlace(null); setPlaceFormDayId(selectedDayId); setShowPlaceForm(true) } : undefined}
|
||||
onAssignToDay={handleAssignToDay}
|
||||
onEditPlace={(place) => openPlaceEditor(place)}
|
||||
onDeletePlace={(placeId) => handleDeletePlace(placeId)}
|
||||
|
||||
@@ -215,6 +215,7 @@ function baseState(): HookState {
|
||||
setDayDetailCollapsed: vi.fn(),
|
||||
showPlaceForm: false,
|
||||
setShowPlaceForm: vi.fn(),
|
||||
setPlaceFormDayId: vi.fn(),
|
||||
editingPlace: null,
|
||||
setEditingPlace: vi.fn(),
|
||||
prefillCoords: null,
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'إضافة مكان/نشاط',
|
||||
'places.addToSelectedDay': 'أضف إلى اليوم المفتوح',
|
||||
'places.addPlaceShort': 'مكان جديد',
|
||||
'places.addToDayShort': 'إلى اليوم',
|
||||
'places.importFile': 'استيراد ملف',
|
||||
'places.trackIndicator': 'مسار / طريق',
|
||||
'places.sidebarDrop': 'أفلت للاستيراد',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Adicionar lugar/atividade',
|
||||
'places.addToSelectedDay': 'Adicionar ao dia aberto',
|
||||
'places.addPlaceShort': 'Novo local',
|
||||
'places.addToDayShort': 'Para o dia',
|
||||
'places.importFile': 'Importar arquivo',
|
||||
'places.trackIndicator': 'Trilha / rota',
|
||||
'places.sidebarDrop': 'Solte para importar',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Afegeix un lloc / activitat',
|
||||
'places.addToSelectedDay': 'Afegeix al dia obert',
|
||||
'places.addPlaceShort': 'Lloc nou',
|
||||
'places.addToDayShort': 'Al dia',
|
||||
'places.importFile': 'Importa un fitxer',
|
||||
'places.trackIndicator': 'Track / ruta',
|
||||
'places.sidebarDrop': 'Deixa anar per importar',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Přidat místo/aktivitu',
|
||||
'places.addToSelectedDay': 'Přidat k otevřenému dni',
|
||||
'places.addPlaceShort': 'Nové místo',
|
||||
'places.addToDayShort': 'Ke dni',
|
||||
'places.importFile': 'Importovat soubor',
|
||||
'places.trackIndicator': 'Trasa',
|
||||
'places.sidebarDrop': 'Pusťte pro import',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Ort/Aktivität hinzufügen',
|
||||
'places.addToSelectedDay': 'Zum geöffneten Tag hinzufügen',
|
||||
'places.addPlaceShort': 'Neuer Ort',
|
||||
'places.addToDayShort': 'Zum Tag',
|
||||
'places.importFile': 'Dateimport',
|
||||
'places.trackIndicator': 'Strecke / Route',
|
||||
'places.sidebarDrop': 'Ablegen zum Importieren',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Add Place/Activity',
|
||||
'places.addToSelectedDay': 'Add to the open day',
|
||||
'places.addPlaceShort': 'New place',
|
||||
'places.addToDayShort': 'To day',
|
||||
'places.importFile': 'Import file',
|
||||
'places.trackIndicator': 'Track / route',
|
||||
'places.sidebarDrop': 'Drop to import',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Añadir lugar/actividad',
|
||||
'places.addToSelectedDay': 'Añadir al día abierto',
|
||||
'places.addPlaceShort': 'Nuevo lugar',
|
||||
'places.addToDayShort': 'Al día',
|
||||
'places.importFile': 'Importar archivo',
|
||||
'places.trackIndicator': 'Ruta / recorrido',
|
||||
'places.sidebarDrop': 'Soltar para importar',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Ajouter un lieu/activité',
|
||||
'places.addToSelectedDay': 'Ajouter au jour ouvert',
|
||||
'places.addPlaceShort': 'Nouveau lieu',
|
||||
'places.addToDayShort': 'Au jour',
|
||||
'places.importFile': 'Importer un fichier',
|
||||
'places.trackIndicator': 'Parcours / itinéraire',
|
||||
'places.sidebarDrop': 'Déposer pour importer',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Προσθήκη Μέρους/Δραστηριότητας',
|
||||
'places.addToSelectedDay': 'Προσθήκη στην ανοιχτή ημέρα',
|
||||
'places.addPlaceShort': 'Νέο σημείο',
|
||||
'places.addToDayShort': 'Στην ημέρα',
|
||||
'places.importFile': 'Εισαγωγή αρχείου',
|
||||
'places.trackIndicator': 'Ίχνος / διαδρομή',
|
||||
'places.sidebarDrop': 'Αφήστε για εισαγωγή',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Hely/Tevékenység hozzáadása',
|
||||
'places.addToSelectedDay': 'Hozzáadás a megnyitott naphoz',
|
||||
'places.addPlaceShort': 'Új hely',
|
||||
'places.addToDayShort': 'A naphoz',
|
||||
'places.importFile': 'Fájl importálása',
|
||||
'places.trackIndicator': 'Nyomvonal / útvonal',
|
||||
'places.sidebarDrop': 'Ejtse el az importáláshoz',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Tambah Tempat/Aktivitas',
|
||||
'places.addToSelectedDay': 'Tambahkan ke hari yang dibuka',
|
||||
'places.addPlaceShort': 'Tempat baru',
|
||||
'places.addToDayShort': 'Ke hari',
|
||||
'places.importFile': 'Impor file',
|
||||
'places.trackIndicator': 'Jalur / rute',
|
||||
'places.sidebarDrop': 'Lepas untuk mengimpor',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Aggiungi Luogo/Attività',
|
||||
'places.addToSelectedDay': 'Aggiungi al giorno aperto',
|
||||
'places.addPlaceShort': 'Nuovo luogo',
|
||||
'places.addToDayShort': 'Al giorno',
|
||||
'places.importFile': 'Importa file',
|
||||
'places.trackIndicator': 'Percorso / itinerario',
|
||||
'places.sidebarDrop': 'Rilascia per importare',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': '場所/アクティビティを追加',
|
||||
'places.addToSelectedDay': '開いている日に追加',
|
||||
'places.addPlaceShort': '新しい場所',
|
||||
'places.addToDayShort': 'この日へ',
|
||||
'places.importFile': 'ファイルをインポート',
|
||||
'places.trackIndicator': 'トラック / ルート',
|
||||
'places.sidebarDrop': 'ドロップしてインポート',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': '장소/활동 추가',
|
||||
'places.addToSelectedDay': '열린 날짜에 추가',
|
||||
'places.addPlaceShort': '새 장소',
|
||||
'places.addToDayShort': '해당 날짜로',
|
||||
'places.importFile': '파일 가져오기',
|
||||
'places.trackIndicator': '트랙 / 경로',
|
||||
'places.sidebarDrop': '끌어다 가져오기',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Plaats/activiteit toevoegen',
|
||||
'places.addToSelectedDay': 'Aan de geopende dag toevoegen',
|
||||
'places.addPlaceShort': 'Nieuwe plek',
|
||||
'places.addToDayShort': 'Naar dag',
|
||||
'places.importFile': 'Bestand importeren',
|
||||
'places.trackIndicator': 'Track / route',
|
||||
'places.sidebarDrop': 'Loslaten om te importeren',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Dodaj miejsce/atrakcję',
|
||||
'places.addToSelectedDay': 'Dodaj do otwartego dnia',
|
||||
'places.addPlaceShort': 'Nowe miejsce',
|
||||
'places.addToDayShort': 'Do dnia',
|
||||
'places.importFile': 'Importuj plik',
|
||||
'places.trackIndicator': 'Trasa / ślad',
|
||||
'places.sidebarDrop': 'Upuść, aby zaimportować',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Добавить место/активность',
|
||||
'places.addToSelectedDay': 'Добавить в открытый день',
|
||||
'places.addPlaceShort': 'Новое место',
|
||||
'places.addToDayShort': 'В день',
|
||||
'places.importFile': 'Импортировать файл',
|
||||
'places.trackIndicator': 'Трек / маршрут',
|
||||
'places.sidebarDrop': 'Отпустите для импорта',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Lägg till plats/aktivitet',
|
||||
'places.addToSelectedDay': 'Lägg till i den öppna dagen',
|
||||
'places.addPlaceShort': 'Ny plats',
|
||||
'places.addToDayShort': 'Till dagen',
|
||||
'places.importFile': 'Importera fil',
|
||||
'places.trackIndicator': 'Spår / rutt',
|
||||
'places.sidebarDrop': 'Släpp för att importera',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Yer/etkinlik Ekle',
|
||||
'places.addToSelectedDay': 'Açık güne ekle',
|
||||
'places.addPlaceShort': 'Yeni yer',
|
||||
'places.addToDayShort': 'Güne',
|
||||
'places.importFile': 'Dosyayı içe aktar',
|
||||
'places.trackIndicator': 'İz / rota',
|
||||
'places.sidebarDrop': 'İçe aktarmak için bırakın',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Додати місце/активність',
|
||||
'places.addToSelectedDay': 'Додати до відкритого дня',
|
||||
'places.addPlaceShort': 'Нове місце',
|
||||
'places.addToDayShort': 'До дня',
|
||||
'places.importFile': 'Імпортувати файл',
|
||||
'places.trackIndicator': 'Трек / маршрут',
|
||||
'places.sidebarDrop': 'Відпустіть для імпорту',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': 'Thêm địa điểm/Hoạt động',
|
||||
'places.addToSelectedDay': 'Thêm vào ngày đang mở',
|
||||
'places.addPlaceShort': 'Địa điểm mới',
|
||||
'places.addToDayShort': 'Vào ngày',
|
||||
'places.importFile': 'Nhập tập tin',
|
||||
'places.trackIndicator': 'Đường đi / tuyến đường',
|
||||
'places.sidebarDrop': 'Thả để nhập',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': '新增地點/活動',
|
||||
'places.addToSelectedDay': '加入目前開啟的日期',
|
||||
'places.addPlaceShort': '新地點',
|
||||
'places.addToDayShort': '加入當天',
|
||||
'places.importFile': '匯入檔案',
|
||||
'places.trackIndicator': '軌跡 / 路線',
|
||||
'places.sidebarDrop': '拖放以匯入',
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { TranslationStrings } from '../types';
|
||||
|
||||
const places: TranslationStrings = {
|
||||
'places.addPlace': '添加地点/活动',
|
||||
'places.addToSelectedDay': '添加到当前打开的日期',
|
||||
'places.addPlaceShort': '新地点',
|
||||
'places.addToDayShort': '加入当天',
|
||||
'places.importFile': '导入文件',
|
||||
'places.trackIndicator': '轨迹 / 路线',
|
||||
'places.sidebarDrop': '拖放以导入',
|
||||
|
||||
Reference in New Issue
Block a user