feat(places): an empty search offers adding the place by hand (#2472)

This commit is contained in:
Maurice
2026-10-01 13:47:04 +02:00
parent d61d8ca9d7
commit f4d81ea42a
30 changed files with 132 additions and 4 deletions
@@ -9,6 +9,8 @@ import { readWeek, weekdayNames, writeWeek } from './placeHours'
import { useTranslation } from '../../i18n'
interface Props {
/** Anchor for the "add by hand" jump from an empty search. */
id?: string
phone: string
email: string
/** The stored hours text (JSON), empty when the place has none of its own. */
@@ -23,7 +25,7 @@ interface Props {
* behind one row until somebody wants them, so a place without any does not
* carry a seven-row grid.
*/
export function PlaceContactFields({ phone, email, openingHours, onChange }: Props) {
export function PlaceContactFields({ id: anchorId, phone, email, openingHours, onChange }: Props) {
const { t, locale } = useTranslation()
const id = useId()
const week = readWeek(openingHours)
@@ -38,7 +40,7 @@ export function PlaceContactFields({ phone, email, openingHours, onChange }: Pro
const clearHours = () => { onChange('opening_hours', ''); setEditing(false) }
return (
<div className="flex flex-col gap-4">
<div id={anchorId} className="flex flex-col gap-4">
<div className={GRID_2}>
<EditorField label={t('places.formPhone')} htmlFor={`${id}-phone`}>
<div className="relative">
@@ -55,6 +55,21 @@ beforeEach(() => {
});
describe('PlaceFormModal', () => {
it('FE-COMP-PLACEFORM-2472: an empty search offers adding the place by hand and takes the query as the name', async () => {
const user = userEvent.setup();
server.use(http.post('/api/maps/search', () => HttpResponse.json({ places: [], source: 'osm' })));
render(<PlaceFormModal {...defaultProps} />);
const search = screen.getByPlaceholderText(/Search places/i);
await user.type(search, 'Tiny Garage Cafe');
await user.click(screen.getByRole('button', { name: 'Search' }));
expect(await screen.findByText('Nothing found for “Tiny Garage Cafe”')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Add by hand' }));
expect(screen.queryByText('Nothing found for “Tiny Garage Cafe”')).not.toBeInTheDocument();
// The search field keeps the query and the name field now holds it too.
expect(screen.getAllByDisplayValue('Tiny Garage Cafe')).toHaveLength(2);
expect(screen.getByLabelText('Phone')).toBeInTheDocument();
});
it('FE-COMP-PLACEFORM-001: renders modal when isOpen is true', () => {
render(<PlaceFormModal {...defaultProps} />);
expect(document.body).toBeInTheDocument();
@@ -12,7 +12,7 @@ import CollectionPicker from '../Collections/CollectionPicker'
import PlaceDetailsColumn, { type PlaceDetailsSelection } from './PlaceDetailsColumn'
import { useToast } from '../shared/Toast'
import { Tooltip } from '../shared/Tooltip'
import { Search, Paperclip, X, AlertTriangle, Loader2, Plus, RotateCcw, MapPin, Navigation, LocateFixed } from 'lucide-react'
import { Search, Paperclip, X, AlertTriangle, Loader2, Plus, RotateCcw, MapPin, Navigation, LocateFixed, PencilLine } from 'lucide-react'
import { useTranslation } from '../../i18n'
import CustomTimePicker from '../shared/CustomTimePicker'
import { PlaceContactFields } from './PlaceContactFields'
@@ -164,6 +164,9 @@ function usePlaceFormModal(props: PlaceFormModalProps) {
return id != null ? String(id) : ''
}
const [pendingFiles, setPendingFiles] = useState([])
// The query the last full search found nothing for (#2472): the cue to add the
// place by hand instead of a silent empty list.
const [emptySearch, setEmptySearch] = useState<string | null>(null)
/**
* The leg of the drive the traveller picked, or empty while the projection's own
* answer stands. Empty rather than seeded, because there is nothing to project onto
@@ -451,6 +454,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) {
searchMetaRef.current = { query: trimmed, source: result.source || 'unknown' }
setNearbyList(false)
setMapsResults(result.places || [])
setEmptySearch((result.places || []).length === 0 ? trimmed : null)
setSearchSource(result.source || '')
} catch (err: unknown) {
if (epoch !== searchEpochRef.current) return
@@ -818,6 +822,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) {
setShowNewCategory,
isSaving,
pendingFiles,
emptySearch, setEmptySearch,
fileRef,
acSuggestions,
setAcSuggestions,
@@ -1037,7 +1042,7 @@ export default function PlaceFormModal(props: PlaceFormModalProps) {
// in from the map already has what the search would give.
autoFocus={!place && !prefillCoords}
value={mapsSearch}
onChange={e => setMapsSearch(e.target.value)}
onChange={e => { setMapsSearch(e.target.value); S.setEmptySearch(null) }}
onKeyDown={handleSearchKeyDown}
onBlur={() => setTimeout(() => setAcSuggestions([]), 150)}
onFocus={() => {
@@ -1103,6 +1108,30 @@ export default function PlaceFormModal(props: PlaceFormModalProps) {
</div>
</EditorField>
{/* Nothing found (#2472): say so, and offer the hand-made way in, taking
the query as the name when there is none yet. */}
{S.emptySearch && mapsResults.length === 0 && !isSearchingMaps && (
<div className="flex items-center gap-3 rounded-[12px] border border-dashed border-edge bg-surface-card px-3 py-2.5">
<span className="grid h-8 w-8 flex-none place-items-center rounded-full bg-surface-tertiary text-content-muted">
<PencilLine size={15} strokeWidth={2} />
</span>
<span className="min-w-0 flex-1">
<span className="block truncate font-semibold text-content" style={fs(12.5, 'body')}>{t('places.searchNothing', { query: S.emptySearch })}</span>
<span className="block text-content-muted" style={fs(11.5)}>{t('places.searchNothingHint')}</span>
</span>
<button type="button"
onClick={() => {
if (!form.name.trim()) handleChange('name', S.emptySearch!)
S.setEmptySearch(null)
document.getElementById(`${fieldId}-contact`)?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
}}
className="flex-none rounded-full bg-accent px-3 py-1.5 font-semibold text-accent-text hover:opacity-90"
style={fs(12, 'body')}>
{t('places.addByHand')}
</button>
</div>
)}
{/* Search results (populated after full search) */}
{mapsResults.length > 0 && (
<div className="max-h-40 overflow-y-auto rounded-[10px] border border-edge-faint bg-surface-card p-1">
@@ -1285,6 +1314,7 @@ export default function PlaceFormModal(props: PlaceFormModalProps) {
{/* Phone, e-mail and the place's own hours, for what the search did not
know or a place nobody has listed (#2472). */}
<PlaceContactFields
id={`${fieldId}-contact`}
phone={form.phone ?? ''}
email={form.email ?? ''}
openingHours={form.opening_hours ?? ''}
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'يغلق',
'places.hoursCopyFirst': 'استخدم ساعات {day} لكل الأيام',
'places.hoursRemove': 'إزالة ساعات العمل',
'places.searchNothing': 'لم يتم العثور على شيء لـ «{query}»',
'places.searchNothingHint': 'أضفه بنفسك: للعنوان والهاتف والبريد وساعات العمل حقول بالأسفل.',
'places.addByHand': 'أضف يدويًا',
'places.formNotes': 'ملاحظات',
'places.formNotesPlaceholder': 'ملاحظات شخصية...',
'places.assignmentNotes': 'ملاحظات لهذا اليوم',
+3
View File
@@ -112,6 +112,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'bağlanır',
'places.hoursCopyFirst': '{day} saatlarını bütün günlərə tətbiq et',
'places.hoursRemove': 'İş saatlarını sil',
'places.searchNothing': '“{query}” üçün heç nə tapılmadı',
'places.searchNothingHint': 'Özünüz əlavə edin: ünvan, telefon, e-poçt və iş saatları üçün aşağıda sahə var.',
'places.addByHand': 'Əllə əlavə et',
'places.formNotes': 'Qeydlər',
'places.formNotesPlaceholder': 'Şəxsi qeydlər...',
'places.assignmentNotes': 'Bu gün üçün qeydlər',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'fecha',
'places.hoursCopyFirst': 'Usar o horário de {day} em todos os dias',
'places.hoursRemove': 'Remover horário de funcionamento',
'places.searchNothing': 'Nada encontrado para “{query}”',
'places.searchNothingHint': 'Adicione você mesmo: endereço, telefone, e-mail e horário têm campos abaixo.',
'places.addByHand': 'Adicionar manualmente',
'places.formNotes': 'Notas',
'places.formNotesPlaceholder': 'Notas pessoais...',
'places.assignmentNotes': 'Notas para este dia',
+3
View File
@@ -101,6 +101,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'tanca',
'places.hoursCopyFirst': "Fes servir l'horari de {day} per a tots els dies",
'places.hoursRemove': "Elimina l'horari",
'places.searchNothing': "No s'ha trobat res per a «{query}»",
'places.searchNothingHint': 'Afegeix-lo tu: adreça, telèfon, correu i horari tenen un camp a sota.',
'places.addByHand': 'Afegeix a mà',
'places.formNotes': 'Notes',
'places.formNotesPlaceholder': 'Notes personals...',
'places.assignmentNotes': 'Notes per a aquest dia',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'zavírá',
'places.hoursCopyFirst': 'Použít časy z {day} pro všechny dny',
'places.hoursRemove': 'Odebrat otevírací dobu',
'places.searchNothing': 'Pro „{query}“ nebylo nic nalezeno',
'places.searchNothingHint': 'Přidejte ho sami: adresa, telefon, e-mail i otevírací doba mají pole níže.',
'places.addByHand': 'Přidat ručně',
'places.formNotes': 'Poznámky',
'places.formNotesPlaceholder': 'Osobní poznámky...',
'places.assignmentNotes': 'Poznámky pro tento den',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'schließt',
'places.hoursCopyFirst': 'Zeiten vom {day} für alle Tage übernehmen',
'places.hoursRemove': 'Öffnungszeiten entfernen',
'places.searchNothing': 'Nichts gefunden für „{query}“',
'places.searchNothingHint': 'Leg ihn selbst an: Adresse, Telefon, E-Mail und Öffnungszeiten haben unten ein Feld.',
'places.addByHand': 'Selbst anlegen',
'places.formNotes': 'Notizen',
'places.formNotesPlaceholder': 'Persönliche Notizen...',
'places.assignmentNotes': 'Notizen für diesen Tag',
+3
View File
@@ -106,6 +106,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'closes',
'places.hoursCopyFirst': "Use {day}'s hours for every day",
'places.hoursRemove': 'Remove opening hours',
'places.searchNothing': 'Nothing found for “{query}”',
'places.searchNothingHint': 'Add it yourself: address, phone, e-mail and opening hours all have a field below.',
'places.addByHand': 'Add by hand',
'places.formNotes': 'Notes',
'places.formNotesPlaceholder': 'Personal notes...',
'places.assignmentNotes': 'Notes for this day',
+3
View File
@@ -103,6 +103,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'cierra',
'places.hoursCopyFirst': 'Usar el horario del {day} para todos los días',
'places.hoursRemove': 'Quitar horario',
'places.searchNothing': 'No se encontró nada para «{query}»',
'places.searchNothingHint': 'Añádelo tú: dirección, teléfono, correo y horario tienen un campo abajo.',
'places.addByHand': 'Añadir a mano',
'places.formNotes': 'Notas',
'places.formNotesPlaceholder': 'Notas personales...',
'places.assignmentNotes': 'Notas para este día',
+3
View File
@@ -103,6 +103,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'suletakse',
'places.hoursCopyFirst': 'Kasuta päeva {day} aegu kõigil päevadel',
'places.hoursRemove': 'Eemalda lahtiolekuajad',
'places.searchNothing': 'Otsingule „{query}“ ei leitud midagi',
'places.searchNothingHint': 'Lisa see ise: aadressil, telefonil, e-postil ja lahtiolekuaegadel on all väli.',
'places.addByHand': 'Lisa käsitsi',
'places.formNotes': 'Märkmed',
'places.formNotesPlaceholder': 'Isiklikud märkmed...',
'places.assignmentNotes': 'Selle päeva märkmed',
+3
View File
@@ -103,6 +103,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'ferme',
'places.hoursCopyFirst': 'Appliquer les horaires du {day} à tous les jours',
'places.hoursRemove': 'Supprimer les horaires',
'places.searchNothing': 'Aucun résultat pour « {query} »',
'places.searchNothingHint': 'Ajoutez-le vous-même : adresse, téléphone, e-mail et horaires ont un champ plus bas.',
'places.addByHand': 'Ajouter à la main',
'places.formNotes': 'Notes',
'places.formNotesPlaceholder': 'Notes personnelles…',
'places.assignmentNotes': 'Notes pour ce jour',
+3
View File
@@ -103,6 +103,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'κλείνει',
'places.hoursCopyFirst': 'Χρήση του ωραρίου της ημέρας {day} για όλες τις ημέρες',
'places.hoursRemove': 'Αφαίρεση ωραρίου',
'places.searchNothing': 'Δεν βρέθηκε τίποτα για «{query}»',
'places.searchNothingHint': 'Προσθέστε το μόνοι σας: διεύθυνση, τηλέφωνο, email και ωράριο έχουν πεδίο παρακάτω.',
'places.addByHand': 'Χειροκίνητη προσθήκη',
'places.formNotes': 'Σημειώσεις',
'places.formNotesPlaceholder': 'Προσωπικές σημειώσεις...',
'places.assignmentNotes': 'Σημειώσεις για αυτή την ημέρα',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'zár',
'places.hoursCopyFirst': 'A(z) {day} nyitvatartása minden napra',
'places.hoursRemove': 'Nyitvatartás törlése',
'places.searchNothing': 'Nincs találat erre: „{query}”',
'places.searchNothingHint': 'Add hozzá magad: a cím, telefon, e-mail és nyitvatartás lent kitölthető.',
'places.addByHand': 'Kézi hozzáadás',
'places.formNotes': 'Jegyzetek',
'places.formNotesPlaceholder': 'Személyes jegyzetek...',
'places.assignmentNotes': 'Jegyzetek erre a napra',
+3
View File
@@ -101,6 +101,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'tutup',
'places.hoursCopyFirst': 'Pakai jam {day} untuk semua hari',
'places.hoursRemove': 'Hapus jam buka',
'places.searchNothing': 'Tidak ada hasil untuk “{query}”',
'places.searchNothingHint': 'Tambahkan sendiri: alamat, telepon, email, dan jam buka ada di bawah.',
'places.addByHand': 'Tambah manual',
'places.formNotes': 'Catatan',
'places.formNotesPlaceholder': 'Catatan pribadi...',
'places.assignmentNotes': 'Catatan untuk hari ini',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'chiude',
'places.hoursCopyFirst': 'Usa gli orari di {day} per tutti i giorni',
'places.hoursRemove': 'Rimuovi orari di apertura',
'places.searchNothing': 'Nessun risultato per «{query}»',
'places.searchNothingHint': 'Aggiungilo tu: indirizzo, telefono, e-mail e orari hanno un campo qui sotto.',
'places.addByHand': 'Aggiungi a mano',
'places.formNotes': 'Note',
'places.formNotesPlaceholder': 'Note personali...',
'places.assignmentNotes': 'Note per questo giorno',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': '閉店',
'places.hoursCopyFirst': '{day}の時間を全曜日に適用',
'places.hoursRemove': '営業時間を削除',
'places.searchNothing': '「{query}」は見つかりませんでした',
'places.searchNothingHint': '自分で追加できます。住所・電話・メール・営業時間の欄が下にあります。',
'places.addByHand': '手動で追加',
'places.formNotes': 'メモ',
'places.formNotesPlaceholder': '個人的なメモ…',
'places.assignmentNotes': 'この日のメモ',
+3
View File
@@ -101,6 +101,9 @@ const places: TranslationStrings = {
'places.hoursCloses': '마감',
'places.hoursCopyFirst': '{day} 시간을 모든 요일에 적용',
'places.hoursRemove': '영업시간 삭제',
'places.searchNothing': '“{query}”에 대한 결과가 없습니다',
'places.searchNothingHint': '직접 추가하세요. 주소, 전화, 이메일, 영업시간 입력란이 아래에 있습니다.',
'places.addByHand': '직접 추가',
'places.formNotes': '메모',
'places.formNotesPlaceholder': '개인 메모...',
'places.assignmentNotes': '이 날의 메모',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'sluit',
'places.hoursCopyFirst': 'Tijden van {day} voor alle dagen gebruiken',
'places.hoursRemove': 'Openingstijden verwijderen',
'places.searchNothing': 'Niets gevonden voor ‘{query}’',
'places.searchNothingHint': 'Voeg het zelf toe: adres, telefoon, e-mail en openingstijden hebben hieronder een veld.',
'places.addByHand': 'Zelf toevoegen',
'places.formNotes': 'Notities',
'places.formNotesPlaceholder': 'Persoonlijke notities...',
'places.assignmentNotes': 'Notities voor deze dag',
+3
View File
@@ -92,6 +92,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'zamknięcie',
'places.hoursCopyFirst': 'Użyj godzin z dnia {day} dla wszystkich dni',
'places.hoursRemove': 'Usuń godziny otwarcia',
'places.searchNothing': 'Nic nie znaleziono dla „{query}”',
'places.searchNothingHint': 'Dodaj samodzielnie: adres, telefon, e-mail i godziny otwarcia mają pola poniżej.',
'places.addByHand': 'Dodaj ręcznie',
'places.formNotes': 'Notatki',
'places.formNotesPlaceholder': 'Osobiste notatki...',
'places.assignmentNotes': 'Notatki na ten dzień',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'закрывается',
'places.hoursCopyFirst': 'Применить часы дня «{day}» ко всем дням',
'places.hoursRemove': 'Удалить часы работы',
'places.searchNothing': 'По запросу «{query}» ничего не найдено',
'places.searchNothingHint': 'Добавьте сами: для адреса, телефона, почты и часов работы есть поля ниже.',
'places.addByHand': 'Добавить вручную',
'places.formNotes': 'Заметки',
'places.formNotesPlaceholder': 'Личные заметки...',
'places.assignmentNotes': 'Заметки на этот день',
+3
View File
@@ -103,6 +103,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'zatvára',
'places.hoursCopyFirst': 'Použiť časy z {day} pre všetky dni',
'places.hoursRemove': 'Odstrániť otváracie hodiny',
'places.searchNothing': 'Pre „{query}“ sa nič nenašlo',
'places.searchNothingHint': 'Pridajte ho sami: adresa, telefón, e-mail aj otváracie hodiny majú pole nižšie.',
'places.addByHand': 'Pridať ručne',
'places.formNotes': 'Poznámky',
'places.formNotesPlaceholder': 'Osobné poznámky...',
'places.assignmentNotes': 'Poznámky pre tento deň',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'stänger',
'places.hoursCopyFirst': 'Använd tiderna för {day} alla dagar',
'places.hoursRemove': 'Ta bort öppettider',
'places.searchNothing': 'Inget hittades för ”{query}”',
'places.searchNothingHint': 'Lägg till den själv: adress, telefon, e-post och öppettider har fält nedanför.',
'places.addByHand': 'Lägg till själv',
'places.formNotes': 'Noteringar',
'places.formNotesPlaceholder': 'Personliga anteckningar...',
'places.assignmentNotes': 'Anteckningar för denna dag',
+3
View File
@@ -96,6 +96,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'ปิด',
'places.hoursCopyFirst': 'ใช้เวลาของวัน{day}กับทุกวัน',
'places.hoursRemove': 'ลบเวลาทำการ',
'places.searchNothing': 'ไม่พบผลลัพธ์สำหรับ “{query}”',
'places.searchNothingHint': 'เพิ่มเองได้: ที่อยู่ โทรศัพท์ อีเมล และเวลาทำการมีช่องให้กรอกด้านล่าง',
'places.addByHand': 'เพิ่มเอง',
'places.formNotes': 'หมายเหตุ',
'places.formNotesPlaceholder': 'บันทึกส่วนตัว...',
'places.assignmentNotes': 'หมายเหตุสำหรับวันนี้',
+3
View File
@@ -104,6 +104,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'kapanış',
'places.hoursCopyFirst': '{day} saatlerini tüm günlere uygula',
'places.hoursRemove': 'Çalışma saatlerini kaldır',
'places.searchNothing': '“{query}” için sonuç bulunamadı',
'places.searchNothingHint': 'Kendin ekle: adres, telefon, e-posta ve çalışma saatleri için aşağıda alan var.',
'places.addByHand': 'Elle ekle',
'places.formNotes': 'Notlar',
'places.formNotesPlaceholder': 'Kişisel notlar...',
'places.assignmentNotes': 'Bu gün için notlar',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'зачиняється',
'places.hoursCopyFirst': 'Застосувати години дня «{day}» до всіх днів',
'places.hoursRemove': 'Видалити години роботи',
'places.searchNothing': 'За запитом «{query}» нічого не знайдено',
'places.searchNothingHint': 'Додайте самі: для адреси, телефону, пошти й годин роботи є поля нижче.',
'places.addByHand': 'Додати вручну',
'places.formNotes': 'Нотатки',
'places.formNotesPlaceholder': 'Особисті нотатки...',
'places.assignmentNotes': 'Нотатки на цей день',
+3
View File
@@ -102,6 +102,9 @@ const places: TranslationStrings = {
'places.hoursCloses': 'đóng cửa',
'places.hoursCopyFirst': 'Dùng giờ của {day} cho mọi ngày',
'places.hoursRemove': 'Xóa giờ mở cửa',
'places.searchNothing': 'Không tìm thấy “{query}”',
'places.searchNothingHint': 'Tự thêm: địa chỉ, điện thoại, email và giờ mở cửa đều có ô bên dưới.',
'places.addByHand': 'Thêm thủ công',
'places.formNotes': 'Ghi chú',
'places.formNotesPlaceholder': 'Ghi chú cá nhân...',
'places.assignmentNotes': 'Ghi chú cho ngày này',
+3
View File
@@ -101,6 +101,9 @@ const places: TranslationStrings = {
'places.hoursCloses': '關門',
'places.hoursCopyFirst': '將{day}的時間套用到每一天',
'places.hoursRemove': '移除營業時間',
'places.searchNothing': '找不到「{query}」',
'places.searchNothingHint': '自行新增:下方有地址、電話、電子郵件和營業時間的欄位。',
'places.addByHand': '手動新增',
'places.formNotes': '備註',
'places.formNotesPlaceholder': '個人備註...',
'places.assignmentNotes': '當天備註',
+3
View File
@@ -101,6 +101,9 @@ const places: TranslationStrings = {
'places.hoursCloses': '关门',
'places.hoursCopyFirst': '将{day}的时间用于每一天',
'places.hoursRemove': '移除营业时间',
'places.searchNothing': '未找到“{query}”',
'places.searchNothingHint': '自己添加:下方有地址、电话、电子邮件和营业时间的输入框。',
'places.addByHand': '手动添加',
'places.formNotes': '备注',
'places.formNotesPlaceholder': '个人备注...',
'places.assignmentNotes': '当天备注',