- {/* Row 1: Back / Date nav / Today */}
+ {/* Row 1 while searching: the search box with a way back. */}
+ {mobileSearchField ? (
+
+ ) : (
+ /* Row 1: Back / Date nav / Today */
+ )}
{/* Row 2: View switcher pills + calendar toggle */}
@@ -257,6 +279,12 @@ export function CalendarToolbar({
))}
+ {/* Beside the calendar filter: the date row has no room left. */}
+ {onOpenSearch && (
+
+ )}
{calendars && selectedCalendarIds && onToggleVisibility && (
+ {searchField}
+
{moreButton}
diff --git a/hooks/__tests__/use-calendar-event-search.test.tsx b/hooks/__tests__/use-calendar-event-search.test.tsx
new file mode 100644
index 000000000..931d69726
--- /dev/null
+++ b/hooks/__tests__/use-calendar-event-search.test.tsx
@@ -0,0 +1,76 @@
+import { renderHook, waitFor } from '@testing-library/react';
+import { describe, it, expect, vi } from 'vitest';
+import { useCalendarEventSearch } from '../use-calendar-event-search';
+import type { CalendarEvent } from '@/lib/jmap/types';
+import type { IJMAPClient } from '@/lib/jmap/client-interface';
+
+vi.mock('@/lib/timezone', async (importOriginal) => ({
+ ...(await importOriginal()),
+ displayNow: () => new Date(2026, 9, 28, 12, 0),
+}));
+
+function makeEvent(id: string, title: string, start: string, extra: Partial = {}): CalendarEvent {
+ return {
+ id, uid: id, '@type': 'Event', title, start, duration: 'PT1H', showWithoutTime: false,
+ calendarIds: { 'cal-1': true }, ...extra,
+ } as unknown as CalendarEvent;
+}
+
+function clientWith(query: () => Promise) {
+ const queryAllCalendarEvents = vi.fn(query);
+ return { client: { queryAllCalendarEvents } as unknown as IJMAPClient, queryAllCalendarEvents };
+}
+
+const loaded = [
+ makeEvent('standup-1', 'Standup', '2026-10-27T09:00:00', { uid: 'standup' }),
+ makeEvent('standup-2', 'Standup', '2026-10-29T09:00:00', { uid: 'standup' }),
+ makeEvent('standup-3', 'Standup', '2026-10-30T09:00:00', { uid: 'standup' }),
+ makeEvent('review', 'Design review', '2026-10-20T14:00:00', {
+ participants: { p: { '@type': 'Participant', name: 'Grizzelda Pentworth', email: 'griz@example.net' } } as never,
+ }),
+];
+
+describe('useCalendarEventSearch (CAL-16)', () => {
+ it('answers from the loaded events at once, participants included, one row per series', async () => {
+ const { client } = clientWith(() => new Promise(() => {}));
+ const { result } = renderHook(() => useCalendarEventSearch('griz', client, loaded));
+ expect(result.current?.past.map((e) => e.id)).toEqual(['review']);
+ expect(result.current?.searching).toBe(true);
+
+ const series = renderHook(() => useCalendarEventSearch('standup', client, loaded));
+ // The next occurrence to come, not every loaded one
+ expect(series.result.current?.upcoming.map((e) => e.id)).toEqual(['standup-2']);
+ expect(series.result.current?.past).toEqual([]);
+ });
+
+ it('adds what the server finds outside the loaded weeks, upcoming soonest first and past latest first', async () => {
+ const { client, queryAllCalendarEvents } = clientWith(async () => [
+ makeEvent('old', 'Standup retro', '2025-03-01T10:00:00'),
+ makeEvent('older', 'Standup kickoff', '2024-01-10T10:00:00'),
+ makeEvent('later', 'Standup offsite', '2027-02-01T10:00:00'),
+ // the series the loaded weeks already show
+ makeEvent('standup-master', 'Standup', '2026-01-05T09:00:00', { uid: 'standup' }),
+ ]);
+ const { result } = renderHook(() => useCalendarEventSearch('standup', client, loaded));
+ await waitFor(() => expect(result.current?.searching).toBe(false));
+ expect(queryAllCalendarEvents).toHaveBeenCalledWith(
+ { text: 'standup' }, [{ property: 'start', isAscending: false }], 100, expect.anything(),
+ );
+ expect(result.current?.upcoming.map((e) => e.id)).toEqual(['standup-2', 'later']);
+ expect(result.current?.past.map((e) => e.id)).toEqual(['old', 'older']);
+ });
+
+ it('keeps the loaded matches when the server can not search', async () => {
+ const { client } = clientWith(async () => { throw new Error('unsupportedFilter'); });
+ const { result } = renderHook(() => useCalendarEventSearch('review', client, loaded));
+ await waitFor(() => expect(result.current?.searching).toBe(false));
+ expect(result.current?.past.map((e) => e.id)).toEqual(['review']);
+ });
+
+ it('does nothing for an empty query', () => {
+ const { client, queryAllCalendarEvents } = clientWith(async () => []);
+ const { result } = renderHook(() => useCalendarEventSearch(' ', client, loaded));
+ expect(result.current).toBeNull();
+ expect(queryAllCalendarEvents).not.toHaveBeenCalled();
+ });
+});
diff --git a/hooks/use-calendar-event-search.ts b/hooks/use-calendar-event-search.ts
new file mode 100644
index 000000000..9d8122c28
--- /dev/null
+++ b/hooks/use-calendar-event-search.ts
@@ -0,0 +1,107 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+import { matchesTerms, parseSearchQuery } from "@/lib/global-search/query-parser";
+import { calendarFilterFor, eventFields } from "@/lib/global-search/providers/calendar";
+import { getEventEndDate, getEventStartDate } from "@/lib/calendar-utils";
+import { displayNow } from "@/lib/timezone";
+import { debug } from "@/lib/debug";
+import type { CalendarEvent } from "@/lib/jmap/types";
+import type { IJMAPClient } from "@/lib/jmap/client-interface";
+
+const SERVER_LIMIT = 100;
+const DEBOUNCE_MS = 250;
+
+export interface CalendarSearchResults {
+ /** Events still to come (or going on), soonest first. */
+ upcoming: CalendarEvent[];
+ /** Events that are over, latest first. */
+ past: CalendarEvent[];
+ /** Waiting for the server's answer. */
+ searching: boolean;
+}
+
+// One row per event: a series found as loaded occurrences and as the
+// server's base event shows once.
+const seriesKey = (event: CalendarEvent) => `${event.accountId ?? ""} ${event.uid || event.originalId || event.id}`;
+
+/**
+ * Searches the user's events by title, place, description and participants
+ * (CAL-16). The events already loaded answer at once; the server's
+ * CalendarEvent/query `text` filter then adds the rest of the calendar. A
+ * server that can't answer leaves the loaded matches.
+ */
+export function useCalendarEventSearch(
+ query: string,
+ client: IJMAPClient | null,
+ loadedEvents: CalendarEvent[],
+): CalendarSearchResults | null {
+ const parsed = useMemo(() => parseSearchQuery(query), [query]);
+ const active = parsed.terms.length > 0;
+ const text = parsed.text;
+
+ const local = useMemo(
+ () => (active ? loadedEvents.filter((event) => matchesTerms(parsed.terms, eventFields(event))) : []),
+ [active, loadedEvents, parsed],
+ );
+
+ const [remote, setRemote] = useState<{ text: string; events: CalendarEvent[] | null } | null>(null);
+ useEffect(() => {
+ if (!active || !client) return;
+ const controller = new AbortController();
+ const timer = setTimeout(() => {
+ // Inside the chain, so a client that can't query at all lands in catch.
+ Promise.resolve().then(() => client.queryAllCalendarEvents(
+ calendarFilterFor(parsed),
+ [{ property: "start", isAscending: false }],
+ SERVER_LIMIT,
+ { signal: controller.signal },
+ )).then((events) => {
+ if (!controller.signal.aborted) setRemote({ text, events });
+ }).catch((error: unknown) => {
+ if (controller.signal.aborted) return;
+ debug.warn("calendar", "Event search on the server failed; showing loaded events only", error);
+ setRemote({ text, events: null });
+ });
+ }, DEBOUNCE_MS);
+ return () => {
+ clearTimeout(timer);
+ controller.abort();
+ };
+ }, [active, client, parsed, text]);
+
+ return useMemo(() => {
+ if (!active) return null;
+ const answered = remote?.text === text ? remote : null;
+ const now = displayNow().getTime();
+ const ends = (event: CalendarEvent) => getEventEndDate(event).getTime();
+ const starts = (event: CalendarEvent) => getEventStartDate(event).getTime();
+
+ // Of a series' loaded occurrences, the next one to come, else the last.
+ const bySeries = new Map();
+ for (const event of local) {
+ const key = seriesKey(event);
+ const kept = bySeries.get(key);
+ if (!kept) { bySeries.set(key, event); continue; }
+ const keptUpcoming = ends(kept) >= now;
+ const upcoming = ends(event) >= now;
+ if (upcoming && (!keptUpcoming || starts(event) < starts(kept))) bySeries.set(key, event);
+ else if (!upcoming && !keptUpcoming && starts(event) > starts(kept)) bySeries.set(key, event);
+ }
+ for (const event of answered?.events ?? []) {
+ if (typeof event.start !== "string" || !event.start) continue;
+ const key = seriesKey(event);
+ if (!bySeries.has(key)) bySeries.set(key, event);
+ }
+
+ const upcoming: CalendarEvent[] = [];
+ const past: CalendarEvent[] = [];
+ for (const event of bySeries.values()) {
+ if (Number.isNaN(starts(event))) continue;
+ (ends(event) >= now ? upcoming : past).push(event);
+ }
+ upcoming.sort((a, b) => starts(a) - starts(b));
+ past.sort((a, b) => starts(b) - starts(a));
+ return { upcoming, past, searching: !!client && !answered };
+ }, [active, local, remote, text, client]);
+}
diff --git a/lib/__tests__/calendar-shortcuts.test.ts b/lib/__tests__/calendar-shortcuts.test.ts
index 258b0fd00..419a4fe51 100644
--- a/lib/__tests__/calendar-shortcuts.test.ts
+++ b/lib/__tests__/calendar-shortcuts.test.ts
@@ -32,6 +32,9 @@ describe('Calendar keyboard shortcuts', () => {
}
expect(calendarShortcutAction(pressed('t', 'KeyT'))).toBe('today');
expect(calendarShortcutAction(pressed('n', 'KeyN'))).toBe('new');
+ expect(calendarShortcutAction(pressed('/', 'Slash'))).toBe('search');
+ // Shift + / is ?, the shell's help
+ expect(calendarShortcutAction({ ...pressed('?', 'Slash'), shiftKey: true })).toBeNull();
});
it('ignores letters held with a modifier', () => {
diff --git a/lib/calendar-shortcuts.ts b/lib/calendar-shortcuts.ts
index 01c0de843..0416c8f40 100644
--- a/lib/calendar-shortcuts.ts
+++ b/lib/calendar-shortcuts.ts
@@ -11,7 +11,8 @@ export type CalendarShortcutAction =
| "day"
| "agenda"
| "tasks"
- | "new";
+ | "new"
+ | "search";
const LETTERS: Record = {
t: "today",
@@ -31,6 +32,8 @@ export function calendarShortcutAction(
if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return null;
if (event.key === "ArrowLeft") return "prev";
if (event.key === "ArrowRight") return "next";
+ // `/` focuses the search, as in Mail (R8); on any layout, by its key.
+ if (event.code === "Slash" || (!event.code && event.key === "/")) return "search";
const code = event.code;
const letter = code && code.length === 4 && code.startsWith("Key")
? code.charAt(3).toLowerCase()
diff --git a/lib/global-search/providers/calendar.ts b/lib/global-search/providers/calendar.ts
index 2a1df87ba..b02a0527f 100644
--- a/lib/global-search/providers/calendar.ts
+++ b/lib/global-search/providers/calendar.ts
@@ -5,7 +5,8 @@ import { textConditions } from '@/lib/jmap/search-utils';
import type { Calendar, CalendarEvent, CalendarEventFilter } from '@/lib/jmap/types';
import { useCalendarStore } from '@/stores/calendar-store';
-function eventFields(event: CalendarEvent): string[] {
+/** The text an event is found by: title, description, places and participants. */
+export function eventFields(event: CalendarEvent): string[] {
const fields: string[] = [event.title, event.description];
for (const location of Object.values(event.locations ?? {})) if (location?.name) fields.push(location.name);
for (const participant of Object.values(event.participants ?? {})) {
diff --git a/lib/keyboard-shortcuts/calendar.ts b/lib/keyboard-shortcuts/calendar.ts
index eba51dd1e..65f11f619 100644
--- a/lib/keyboard-shortcuts/calendar.ts
+++ b/lib/keyboard-shortcuts/calendar.ts
@@ -29,6 +29,7 @@ export const CALENDAR_SECTIONS: readonly ShortcutSection[] = [
title: "calendar.shortcuts.actions",
entries: [
{ key: "↑ ↓ ← →", description: "calendar.shortcuts.move_in_grid" },
+ { key: "/", description: "calendar.search.title" },
{ key: "n / c", description: "calendar.shortcuts.new_item" },
{ key: "e", description: "calendar.events.edit" },
{ key: "Mod + Enter", description: "calendar.form.save" },
diff --git a/locales/ar/common.json b/locales/ar/common.json
index 76fee66ba..9bd7adbce 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "التنقل بين الأيام والساعات في الشبكة"
},
"other_calendars": "تقويمات أخرى",
- "calendar_options": "خيارات {name}"
+ "calendar_options": "خيارات {name}",
+ "search": {
+ "title": "البحث في الأحداث",
+ "clear": "مسح البحث",
+ "close": "إغلاق البحث",
+ "upcoming": "القادمة",
+ "past": "السابقة",
+ "no_results": "لا توجد أحداث تطابق «{query}»",
+ "no_results_hint": "يبحث في العناوين والأماكن والأوصاف والضيوف.",
+ "repeats": "متكرر",
+ "results": "{count, plural, zero {لم يُعثر على أي حدث} one {تم العثور على حدث واحد} two {تم العثور على حدثين} few {تم العثور على # أحداث} many {تم العثور على # حدثًا} other {تم العثور على # حدث}}"
+ }
},
"calendar_event_notifications": {
"someone": "شخص ما",
diff --git a/locales/ca/common.json b/locales/ca/common.json
index 18d759fa2..b33cd2da5 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Moure's entre dies i hores a la quadrícula"
},
"other_calendars": "Altres calendaris",
- "calendar_options": "Opcions de {name}"
+ "calendar_options": "Opcions de {name}",
+ "search": {
+ "title": "Cerca esdeveniments",
+ "clear": "Esborra la cerca",
+ "close": "Tanca la cerca",
+ "upcoming": "Propers",
+ "past": "Passats",
+ "no_results": "Cap esdeveniment coincideix amb «{query}»",
+ "no_results_hint": "La cerca mira títols, llocs, descripcions i convidats.",
+ "repeats": "Es repeteix",
+ "results": "{count, plural, one {# esdeveniment trobat} other {# esdeveniments trobats}}"
+ }
},
"calendar_event_notifications": {
"someone": "Algú",
diff --git a/locales/cs/common.json b/locales/cs/common.json
index 4f714d184..a1d79990a 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Přesun mezi dny a hodinami v mřížce"
},
"other_calendars": "Další kalendáře",
- "calendar_options": "Možnosti pro {name}"
+ "calendar_options": "Možnosti pro {name}",
+ "search": {
+ "title": "Hledat události",
+ "clear": "Vymazat hledání",
+ "close": "Zavřít hledání",
+ "upcoming": "Nadcházející",
+ "past": "Minulé",
+ "no_results": "Hledanému „{query}“ neodpovídá žádná událost",
+ "no_results_hint": "Hledá se v názvech, místech, popisech a hostech.",
+ "repeats": "Opakuje se",
+ "results": "{count, plural, one {Nalezena # událost} few {Nalezeny # události} many {Nalezeno # události} other {Nalezeno # událostí}}"
+ }
},
"advanced_search": {
"title": "Pokročilé hledání",
diff --git a/locales/da/common.json b/locales/da/common.json
index 303041961..125c534d2 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Gå mellem dage og timer i gitteret"
},
"other_calendars": "Andre kalendere",
- "calendar_options": "Indstillinger for {name}"
+ "calendar_options": "Indstillinger for {name}",
+ "search": {
+ "title": "Søg i begivenheder",
+ "clear": "Ryd søgning",
+ "close": "Luk søgning",
+ "upcoming": "Kommende",
+ "past": "Tidligere",
+ "no_results": "Ingen begivenheder matcher “{query}”",
+ "no_results_hint": "Søgningen ser på titler, steder, beskrivelser og gæster.",
+ "repeats": "Gentages",
+ "results": "{count, plural, one {# begivenhed fundet} other {# begivenheder fundet}}"
+ }
},
"calendar_event_notifications": {
"someone": "Nogen",
diff --git a/locales/de/common.json b/locales/de/common.json
index 0a90f1f52..9f27ddd1c 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Im Raster zwischen Tagen und Stunden wechseln"
},
"other_calendars": "Weitere Kalender",
- "calendar_options": "Optionen für {name}"
+ "calendar_options": "Optionen für {name}",
+ "search": {
+ "title": "Termine durchsuchen",
+ "clear": "Suche löschen",
+ "close": "Suche schließen",
+ "upcoming": "Demnächst",
+ "past": "Vergangen",
+ "no_results": "Keine Termine zu „{query}“",
+ "no_results_hint": "Gesucht wird in Titeln, Orten, Beschreibungen und Gästen.",
+ "repeats": "Wiederholt sich",
+ "results": "{count, plural, one {# Termin gefunden} other {# Termine gefunden}}"
+ }
},
"advanced_search": {
"title": "Erweiterte Suche",
diff --git a/locales/en/common.json b/locales/en/common.json
index d5dc1dc5b..44564b42a 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Move between days and hours in the grid"
},
"other_calendars": "Other calendars",
- "calendar_options": "Options for {name}"
+ "calendar_options": "Options for {name}",
+ "search": {
+ "title": "Search events",
+ "clear": "Clear search",
+ "close": "Close search",
+ "upcoming": "Upcoming",
+ "past": "Past",
+ "no_results": "No events match “{query}”",
+ "no_results_hint": "Search looks at titles, places, descriptions and guests.",
+ "repeats": "Repeats",
+ "results": "{count, plural, one {# event found} other {# events found}}"
+ }
},
"calendar_event_notifications": {
"someone": "Someone",
diff --git a/locales/es/common.json b/locales/es/common.json
index abf92a20d..74e60c8c3 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Moverse entre días y horas en la cuadrícula"
},
"other_calendars": "Otros calendarios",
- "calendar_options": "Opciones de {name}"
+ "calendar_options": "Opciones de {name}",
+ "search": {
+ "title": "Buscar eventos",
+ "clear": "Borrar búsqueda",
+ "close": "Cerrar búsqueda",
+ "upcoming": "Próximos",
+ "past": "Pasados",
+ "no_results": "Ningún evento coincide con «{query}»",
+ "no_results_hint": "La búsqueda mira títulos, lugares, descripciones e invitados.",
+ "repeats": "Se repite",
+ "results": "{count, plural, one {# evento encontrado} other {# eventos encontrados}}"
+ }
},
"advanced_search": {
"title": "Búsqueda avanzada",
diff --git a/locales/et/common.json b/locales/et/common.json
index 95085df2e..e33c15026 100644
--- a/locales/et/common.json
+++ b/locales/et/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Liigu ruudustikus päevade ja tundide vahel"
},
"other_calendars": "Muud kalendrid",
- "calendar_options": "Kalendri {name} valikud"
+ "calendar_options": "Kalendri {name} valikud",
+ "search": {
+ "title": "Otsi sündmusi",
+ "clear": "Tühjenda otsing",
+ "close": "Sulge otsing",
+ "upcoming": "Tulevased",
+ "past": "Möödunud",
+ "no_results": "Päringule „{query}“ ei vasta ükski sündmus",
+ "no_results_hint": "Otsitakse pealkirjadest, kohtadest, kirjeldustest ja külalistest.",
+ "repeats": "Kordub",
+ "results": "{count, plural, one {Leiti # sündmus} other {Leiti # sündmust}}"
+ }
},
"calendar_event_notifications": {
"someone": "Keegi",
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 4a930517c..e09678f4b 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "جابهجایی بین روزها و ساعتها در جدول"
},
"other_calendars": "تقویمهای دیگر",
- "calendar_options": "گزینههای {name}"
+ "calendar_options": "گزینههای {name}",
+ "search": {
+ "title": "جستجوی رویدادها",
+ "clear": "پاک کردن جستجو",
+ "close": "بستن جستجو",
+ "upcoming": "پیش رو",
+ "past": "گذشته",
+ "no_results": "هیچ رویدادی با «{query}» مطابقت ندارد",
+ "no_results_hint": "جستجو در عنوانها، مکانها، توضیحات و مهمانها انجام میشود.",
+ "repeats": "تکرار میشود",
+ "results": "{count, plural, one {# رویداد پیدا شد} other {# رویداد پیدا شد}}"
+ }
},
"calendar_event_notifications": {
"someone": "کسی",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 70eb9cbaf..75b68dc1c 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Se déplacer entre les jours et les heures dans la grille"
},
"other_calendars": "Autres agendas",
- "calendar_options": "Options de {name}"
+ "calendar_options": "Options de {name}",
+ "search": {
+ "title": "Rechercher des événements",
+ "clear": "Effacer la recherche",
+ "close": "Fermer la recherche",
+ "upcoming": "À venir",
+ "past": "Passés",
+ "no_results": "Aucun événement ne correspond à « {query} »",
+ "no_results_hint": "La recherche porte sur les titres, lieux, descriptions et invités.",
+ "repeats": "Se répète",
+ "results": "{count, plural, one {# événement trouvé} other {# événements trouvés}}"
+ }
},
"advanced_search": {
"title": "Recherche avancée",
diff --git a/locales/he/common.json b/locales/he/common.json
index ef20d1f0e..8f633f05c 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -3731,7 +3731,18 @@
"move_in_grid": "מעבר בין ימים ושעות ברשת"
},
"other_calendars": "יומנים אחרים",
- "calendar_options": "אפשרויות עבור {name}"
+ "calendar_options": "אפשרויות עבור {name}",
+ "search": {
+ "title": "חיפוש אירועים",
+ "clear": "ניקוי החיפוש",
+ "close": "סגירת החיפוש",
+ "upcoming": "קרובים",
+ "past": "קודמים",
+ "no_results": "אין אירועים שתואמים ל„{query}”",
+ "no_results_hint": "החיפוש בודק כותרות, מקומות, תיאורים ומוזמנים.",
+ "repeats": "חוזר",
+ "results": "{count, plural, one {נמצא אירוע אחד} two {נמצאו שני אירועים} other {נמצאו # אירועים}}"
+ }
},
"advanced_search": {
"title": "חיפוש מתקדם",
diff --git a/locales/hu/common.json b/locales/hu/common.json
index e5b7118d6..c8ebd5c1a 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Mozgás a napok és órák között a rácsban"
},
"other_calendars": "Egyéb naptárak",
- "calendar_options": "{name} beállításai"
+ "calendar_options": "{name} beállításai",
+ "search": {
+ "title": "Események keresése",
+ "clear": "Keresés törlése",
+ "close": "Keresés bezárása",
+ "upcoming": "Közelgő",
+ "past": "Korábbi",
+ "no_results": "Egy esemény sem felel meg ennek: „{query}”",
+ "no_results_hint": "A keresés a címekben, helyszínekben, leírásokban és vendégekben keres.",
+ "repeats": "Ismétlődik",
+ "results": "{count, plural, one {# esemény található} other {# esemény található}}"
+ }
},
"calendar_event_notifications": {
"someone": "Valaki",
diff --git a/locales/it/common.json b/locales/it/common.json
index 83e296669..2f555dab5 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Spostarsi tra giorni e ore nella griglia"
},
"other_calendars": "Altri calendari",
- "calendar_options": "Opzioni per {name}"
+ "calendar_options": "Opzioni per {name}",
+ "search": {
+ "title": "Cerca eventi",
+ "clear": "Cancella ricerca",
+ "close": "Chiudi ricerca",
+ "upcoming": "Prossimi",
+ "past": "Passati",
+ "no_results": "Nessun evento corrisponde a «{query}»",
+ "no_results_hint": "La ricerca guarda titoli, luoghi, descrizioni e invitati.",
+ "repeats": "Si ripete",
+ "results": "{count, plural, one {# evento trovato} other {# eventi trovati}}"
+ }
},
"advanced_search": {
"title": "Ricerca avanzata",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 1fabfb6bd..85f1e0d97 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "グリッド内で日と時間を移動"
},
"other_calendars": "他のカレンダー",
- "calendar_options": "{name} のオプション"
+ "calendar_options": "{name} のオプション",
+ "search": {
+ "title": "予定を検索",
+ "clear": "検索をクリア",
+ "close": "検索を閉じる",
+ "upcoming": "今後の予定",
+ "past": "過去の予定",
+ "no_results": "「{query}」に一致する予定はありません",
+ "no_results_hint": "タイトル、場所、説明、ゲストを検索します。",
+ "repeats": "繰り返し",
+ "results": "{count, plural, other {# 件の予定が見つかりました}}"
+ }
},
"advanced_search": {
"title": "詳細検索",
diff --git a/locales/ko/common.json b/locales/ko/common.json
index 02c167040..a455aae3b 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "그리드에서 날짜와 시간 사이 이동"
},
"other_calendars": "다른 캘린더",
- "calendar_options": "{name} 옵션"
+ "calendar_options": "{name} 옵션",
+ "search": {
+ "title": "일정 검색",
+ "clear": "검색어 지우기",
+ "close": "검색 닫기",
+ "upcoming": "예정",
+ "past": "지난 일정",
+ "no_results": "‘{query}’와 일치하는 일정이 없습니다",
+ "no_results_hint": "제목, 장소, 설명, 참석자에서 검색합니다.",
+ "repeats": "반복",
+ "results": "{count, plural, other {일정 #개를 찾았습니다}}"
+ }
},
"advanced_search": {
"title": "상세 검색",
diff --git a/locales/lv/common.json b/locales/lv/common.json
index 171450c23..d876b07ed 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Pārvietoties starp dienām un stundām režģī"
},
"other_calendars": "Citi kalendāri",
- "calendar_options": "{name} opcijas"
+ "calendar_options": "{name} opcijas",
+ "search": {
+ "title": "Meklēt notikumus",
+ "clear": "Notīrīt meklēšanu",
+ "close": "Aizvērt meklēšanu",
+ "upcoming": "Gaidāmie",
+ "past": "Pagājušie",
+ "no_results": "Neviens notikums neatbilst “{query}”",
+ "no_results_hint": "Meklē nosaukumos, vietās, aprakstos un viesos.",
+ "repeats": "Atkārtojas",
+ "results": "{count, plural, zero {Atrasti # notikumi} one {Atrasts # notikums} other {Atrasti # notikumi}}"
+ }
},
"advanced_search": {
"title": "Izvērstā meklēšana",
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 243abe693..47e569824 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Хүснэгтэд өдөр, цагийн хооронд шилжих"
},
"other_calendars": "Бусад хуанли",
- "calendar_options": "{name}-ийн тохиргоо"
+ "calendar_options": "{name}-ийн тохиргоо",
+ "search": {
+ "title": "Үйл явдал хайх",
+ "clear": "Хайлтыг арилгах",
+ "close": "Хайлтыг хаах",
+ "upcoming": "Удахгүй болох",
+ "past": "Өнгөрсөн",
+ "no_results": "“{query}”-д тохирох үйл явдал алга",
+ "no_results_hint": "Гарчиг, газар, тайлбар, зочдоос хайна.",
+ "repeats": "Давтагдана",
+ "results": "{count, plural, one {# үйл явдал олдлоо} other {# үйл явдал олдлоо}}"
+ }
},
"calendar_event_notifications": {
"someone": "Хэн нэгэн",
diff --git a/locales/nb/common.json b/locales/nb/common.json
index 9f98b979b..aa87e0470 100644
--- a/locales/nb/common.json
+++ b/locales/nb/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Gå mellom dager og timer i rutenettet"
},
"other_calendars": "Andre kalendere",
- "calendar_options": "Alternativer for {name}"
+ "calendar_options": "Alternativer for {name}",
+ "search": {
+ "title": "Søk i hendelser",
+ "clear": "Tøm søket",
+ "close": "Lukk søket",
+ "upcoming": "Kommende",
+ "past": "Tidligere",
+ "no_results": "Ingen hendelser samsvarer med «{query}»",
+ "no_results_hint": "Søket ser i titler, steder, beskrivelser og gjester.",
+ "repeats": "Gjentas",
+ "results": "{count, plural, one {# hendelse funnet} other {# hendelser funnet}}"
+ }
},
"calendar_event_notifications": {
"someone": "Noen",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index add4370d0..7533c4abe 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Tussen dagen en uren in het raster bewegen"
},
"other_calendars": "Andere agenda's",
- "calendar_options": "Opties voor {name}"
+ "calendar_options": "Opties voor {name}",
+ "search": {
+ "title": "Afspraken zoeken",
+ "clear": "Zoekopdracht wissen",
+ "close": "Zoeken sluiten",
+ "upcoming": "Komend",
+ "past": "Voorbij",
+ "no_results": "Geen afspraken gevonden voor ‘{query}’",
+ "no_results_hint": "Er wordt gezocht in titels, locaties, beschrijvingen en gasten.",
+ "repeats": "Herhaalt",
+ "results": "{count, plural, one {# afspraak gevonden} other {# afspraken gevonden}}"
+ }
},
"advanced_search": {
"title": "Geavanceerd zoeken",
diff --git a/locales/pl/common.json b/locales/pl/common.json
index fa7f52974..4c4a2c35e 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Przechodzenie między dniami i godzinami w siatce"
},
"other_calendars": "Inne kalendarze",
- "calendar_options": "Opcje dla {name}"
+ "calendar_options": "Opcje dla {name}",
+ "search": {
+ "title": "Szukaj wydarzeń",
+ "clear": "Wyczyść wyszukiwanie",
+ "close": "Zamknij wyszukiwanie",
+ "upcoming": "Nadchodzące",
+ "past": "Minione",
+ "no_results": "Brak wydarzeń pasujących do „{query}”",
+ "no_results_hint": "Wyszukiwanie obejmuje tytuły, miejsca, opisy i gości.",
+ "repeats": "Powtarza się",
+ "results": "{count, plural, one {Znaleziono # wydarzenie} few {Znaleziono # wydarzenia} many {Znaleziono # wydarzeń} other {Znaleziono # wydarzenia}}"
+ }
},
"advanced_search": {
"title": "Wyszukiwanie zaawansowane",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index e98e79de5..9985ef334 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Mover entre dias e horas na grelha"
},
"other_calendars": "Outros calendários",
- "calendar_options": "Opções de {name}"
+ "calendar_options": "Opções de {name}",
+ "search": {
+ "title": "Pesquisar eventos",
+ "clear": "Limpar pesquisa",
+ "close": "Fechar pesquisa",
+ "upcoming": "Próximos",
+ "past": "Passados",
+ "no_results": "Nenhum evento corresponde a “{query}”",
+ "no_results_hint": "A pesquisa procura em títulos, locais, descrições e convidados.",
+ "repeats": "Repete-se",
+ "results": "{count, plural, one {# evento encontrado} other {# eventos encontrados}}"
+ }
},
"advanced_search": {
"title": "Pesquisa avançada",
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 1a0677ce5..2846b4fbb 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Deplasare între zile și ore în grilă"
},
"other_calendars": "Alte calendare",
- "calendar_options": "Opțiuni pentru {name}"
+ "calendar_options": "Opțiuni pentru {name}",
+ "search": {
+ "title": "Caută evenimente",
+ "clear": "Șterge căutarea",
+ "close": "Închide căutarea",
+ "upcoming": "Viitoare",
+ "past": "Trecute",
+ "no_results": "Niciun eveniment nu corespunde cu „{query}”",
+ "no_results_hint": "Căutarea se uită la titluri, locuri, descrieri și invitați.",
+ "repeats": "Se repetă",
+ "results": "{count, plural, one {# eveniment găsit} few {# evenimente găsite} other {# de evenimente găsite}}"
+ }
},
"calendar_event_notifications": {
"someone": "Cineva",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index 6d8e0aa80..8ed102a4c 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Перемещение между днями и часами в сетке"
},
"other_calendars": "Другие календари",
- "calendar_options": "Параметры: {name}"
+ "calendar_options": "Параметры: {name}",
+ "search": {
+ "title": "Поиск событий",
+ "clear": "Очистить поиск",
+ "close": "Закрыть поиск",
+ "upcoming": "Предстоящие",
+ "past": "Прошедшие",
+ "no_results": "Нет событий по запросу «{query}»",
+ "no_results_hint": "Поиск идёт по названиям, местам, описаниям и гостям.",
+ "repeats": "Повторяется",
+ "results": "{count, plural, one {Найдено # событие} few {Найдено # события} many {Найдено # событий} other {Найдено # события}}"
+ }
},
"advanced_search": {
"title": "Расширенный поиск",
diff --git a/locales/sk/common.json b/locales/sk/common.json
index 74afce4ed..68684df9e 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Presun medzi dňami a hodinami v mriežke"
},
"other_calendars": "Ďalšie kalendáre",
- "calendar_options": "Možnosti pre {name}"
+ "calendar_options": "Možnosti pre {name}",
+ "search": {
+ "title": "Hľadať udalosti",
+ "clear": "Vymazať hľadanie",
+ "close": "Zavrieť hľadanie",
+ "upcoming": "Nadchádzajúce",
+ "past": "Minulé",
+ "no_results": "Hľadaniu „{query}“ nezodpovedá žiadna udalosť",
+ "no_results_hint": "Hľadá sa v názvoch, miestach, popisoch a hosťoch.",
+ "repeats": "Opakuje sa",
+ "results": "{count, plural, one {Nájdená # udalosť} few {Nájdené # udalosti} many {Nájdenej # udalosti} other {Nájdených # udalostí}}"
+ }
},
"calendar_event_notifications": {
"someone": "Niekto",
diff --git a/locales/th/common.json b/locales/th/common.json
index ab77e9ff3..f6fc2918f 100644
--- a/locales/th/common.json
+++ b/locales/th/common.json
@@ -3142,7 +3142,18 @@
"move_in_grid": "เลื่อนไปมาระหว่างวันและชั่วโมงในตาราง"
},
"other_calendars": "ปฏิทินอื่นๆ",
- "calendar_options": "ตัวเลือกสำหรับ {name}"
+ "calendar_options": "ตัวเลือกสำหรับ {name}",
+ "search": {
+ "title": "ค้นหากิจกรรม",
+ "clear": "ล้างการค้นหา",
+ "close": "ปิดการค้นหา",
+ "upcoming": "ที่กำลังจะมาถึง",
+ "past": "ที่ผ่านมา",
+ "no_results": "ไม่มีกิจกรรมที่ตรงกับ “{query}”",
+ "no_results_hint": "ค้นหาจากชื่อ สถานที่ คำอธิบาย และแขก",
+ "repeats": "เกิดซ้ำ",
+ "results": "{count, plural, other {พบ # กิจกรรม}}"
+ }
},
"settings": {
"title": "การตั้งค่า",
diff --git a/locales/tr/common.json b/locales/tr/common.json
index dff1b6133..5518dcf19 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Izgarada günler ve saatler arasında gezin"
},
"other_calendars": "Diğer takvimler",
- "calendar_options": "{name} seçenekleri"
+ "calendar_options": "{name} seçenekleri",
+ "search": {
+ "title": "Etkinlik ara",
+ "clear": "Aramayı temizle",
+ "close": "Aramayı kapat",
+ "upcoming": "Yaklaşan",
+ "past": "Geçmiş",
+ "no_results": "“{query}” ile eşleşen etkinlik yok",
+ "no_results_hint": "Arama başlıklarda, yerlerde, açıklamalarda ve davetlilerde yapılır.",
+ "repeats": "Tekrarlanıyor",
+ "results": "{count, plural, one {# etkinlik bulundu} other {# etkinlik bulundu}}"
+ }
},
"calendar_event_notifications": {
"someone": "Birisi",
diff --git a/locales/uk/common.json b/locales/uk/common.json
index 12eb47ff2..129392404 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "Переміщення між днями й годинами в сітці"
},
"other_calendars": "Інші календарі",
- "calendar_options": "Параметри: {name}"
+ "calendar_options": "Параметри: {name}",
+ "search": {
+ "title": "Пошук подій",
+ "clear": "Очистити пошук",
+ "close": "Закрити пошук",
+ "upcoming": "Майбутні",
+ "past": "Минулі",
+ "no_results": "Немає подій за запитом «{query}»",
+ "no_results_hint": "Пошук іде за назвами, місцями, описами й гостями.",
+ "repeats": "Повторюється",
+ "results": "{count, plural, one {Знайдено # подію} few {Знайдено # події} many {Знайдено # подій} other {Знайдено # події}}"
+ }
},
"advanced_search": {
"title": "Розширений пошук",
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
index b6260294d..8f661dec8 100644
--- a/locales/zh-TW/common.json
+++ b/locales/zh-TW/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "在格線中按天與小時移動"
},
"other_calendars": "其他日曆",
- "calendar_options": "{name} 的選項"
+ "calendar_options": "{name} 的選項",
+ "search": {
+ "title": "搜尋活動",
+ "clear": "清除搜尋",
+ "close": "關閉搜尋",
+ "upcoming": "即將到來",
+ "past": "已過去",
+ "no_results": "沒有符合「{query}」的活動",
+ "no_results_hint": "搜尋範圍包括標題、地點、說明和邀請對象。",
+ "repeats": "重複",
+ "results": "{count, plural, other {找到 # 個活動}}"
+ }
},
"calendar_event_notifications": {
"someone": "有人",
diff --git a/locales/zh/common.json b/locales/zh/common.json
index f4019600e..390e791c7 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -3813,7 +3813,18 @@
"move_in_grid": "在网格中按天和小时移动"
},
"other_calendars": "其他日历",
- "calendar_options": "{name} 的选项"
+ "calendar_options": "{name} 的选项",
+ "search": {
+ "title": "搜索活动",
+ "clear": "清除搜索",
+ "close": "关闭搜索",
+ "upcoming": "即将到来",
+ "past": "已过去",
+ "no_results": "没有与“{query}”匹配的活动",
+ "no_results_hint": "搜索范围包括标题、地点、描述和邀请对象。",
+ "repeats": "重复",
+ "results": "{count, plural, other {找到 # 个活动}}"
+ }
},
"advanced_search": {
"title": "高级搜索",