diff --git a/components/calendar/__tests__/calendar-search-placeholder-ux-pim.test.tsx b/components/calendar/__tests__/calendar-search-placeholder-ux-pim.test.tsx
new file mode 100644
index 000000000..09b877bfa
--- /dev/null
+++ b/components/calendar/__tests__/calendar-search-placeholder-ux-pim.test.tsx
@@ -0,0 +1,53 @@
+import { render, screen, cleanup } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
+import { createRef } from 'react';
+import { CalendarSearchField } from '../calendar-search';
+
+// pim-21: at 150 % in German the search box read "Termine durchs". A field
+// too narrow for "Search events" now says "Search"; its name stays whole.
+
+/** The field is `width` px wide inside; text measures 10px per character. */
+function stubWidth(width: number) {
+ vi.stubGlobal('ResizeObserver', class implements ResizeObserver {
+ constructor(private readonly callback: ResizeObserverCallback) {}
+ observe(target: Element) {
+ this.callback([{ target, contentRect: { width } } as unknown as ResizeObserverEntry], this);
+ }
+ unobserve() {}
+ disconnect() {}
+ });
+ vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({
+ font: '',
+ measureText: (text: string) => ({ width: text.length * 10 }),
+ } as unknown as CanvasRenderingContext2D);
+}
+
+afterEach(() => {
+ cleanup();
+ vi.unstubAllGlobals();
+ vi.restoreAllMocks();
+});
+
+describe('CalendarSearchField placeholder', () => {
+ it('says the short word in a narrow field', () => {
+ // "search.title" is 12 characters: 120px does not fit in 96.
+ stubWidth(96);
+ render();
+ const input = screen.getByRole('searchbox');
+ expect(input.getAttribute('placeholder')).toBe('search_short');
+ expect(input.getAttribute('aria-label')).toBe('search.title');
+ });
+
+ it('keeps the whole phrase where it fits', () => {
+ stubWidth(200);
+ render();
+ expect(screen.getByRole('searchbox').getAttribute('placeholder')).toBe('search.title');
+ });
+
+ it('still hands the input to the page', () => {
+ stubWidth(200);
+ const ref = createRef();
+ render();
+ expect(ref.current).toBe(screen.getByRole('searchbox'));
+ });
+});
diff --git a/components/calendar/calendar-search.tsx b/components/calendar/calendar-search.tsx
index 306d427ab..44c1d168b 100644
--- a/components/calendar/calendar-search.tsx
+++ b/components/calendar/calendar-search.tsx
@@ -1,6 +1,6 @@
"use client";
-import { forwardRef, useMemo, useRef, type KeyboardEvent } from "react";
+import { forwardRef, useCallback, useMemo, useRef, type KeyboardEvent } from "react";
import { useTranslations } from "next-intl";
import { format } from "date-fns";
import { Search, MapPin, Repeat } from "@/components/icons";
@@ -18,6 +18,7 @@ import type { Calendar, CalendarEvent } from "@/lib/jmap/types";
import type { CalendarSearchResults as Results } from "@/hooks/use-calendar-event-search";
import { getEventColor } from "./event-card";
import { useCvdColor } from "@/hooks/use-cvd-color";
+import { useShortenedText } from "@/hooks/use-shortened-text";
const RESULT_SELECTOR = "[data-search-result]";
@@ -34,6 +35,15 @@ interface CalendarSearchFieldProps {
export const CalendarSearchField = forwardRef(
function CalendarSearchField({ value, onChange, onDismiss, autoFocus, className }, ref) {
const t = useTranslations("calendar");
+ const tGrid = useTranslations("ux_pim_grid");
+ // A narrow field says "Search" rather than cut "Search events" (pim-21:
+ // "Termine durchs" at 150 % in German). The name stays the full one.
+ const [measureRef, placeholder] = useShortenedText([t("search.title"), tGrid("search_short")]);
+ const inputRef = useCallback((node: HTMLInputElement | null) => {
+ measureRef(node);
+ if (typeof ref === "function") ref(node);
+ else if (ref) ref.current = node;
+ }, [measureRef, ref]);
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
// Escape clears the search first, then leaves the field (R8).
@@ -49,7 +59,7 @@ export const CalendarSearchField = forwardRef
onChange(e.target.value)}
@@ -57,7 +67,7 @@ export const CalendarSearchField = forwardRef onChange("")}
clearLabel={t("search.clear")}
shortcut="/"
- placeholder={t("search.title")}
+ placeholder={placeholder}
aria-label={t("search.title")}
autoComplete="off"
data-calendar-search
diff --git a/locales/ar/common.json b/locales/ar/common.json
index f8800991e..387b49d32 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "المؤسسة"
},
+ "ux_pim_grid": {
+ "search_short": "بحث",
+ "reply": "رد",
+ "reply_to_invitation": "الرد على الدعوة"
+ },
"vacation_calendar": {
"label": "حجز تقويمي",
"description": "يضيف حدثًا مشغولًا لمدة غيابك، حتى لا يحجز أحد اجتماعات معك خلالها.",
diff --git a/locales/ca/common.json b/locales/ca/common.json
index 62c75d062..18d1b645a 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Organització"
},
+ "ux_pim_grid": {
+ "search_short": "Cerca",
+ "reply": "Respon",
+ "reply_to_invitation": "Respon a la invitació"
+ },
"vacation_calendar": {
"label": "Bloca el meu calendari",
"description": "Afegeix un esdeveniment ocupat mentre sou fora, perquè ningú us convoqui a reunions aleshores.",
diff --git a/locales/cs/common.json b/locales/cs/common.json
index cd765731a..36c2ef2d2 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organizace"
},
+ "ux_pim_grid": {
+ "search_short": "Hledat",
+ "reply": "Odpovědět",
+ "reply_to_invitation": "Odpovědět na pozvánku"
+ },
"vacation_calendar": {
"label": "Zablokovat můj kalendář",
"description": "Přidá obsazenou událost na dobu vaší nepřítomnosti, aby vám tehdy nikdo neplánoval schůzky.",
diff --git a/locales/da/common.json b/locales/da/common.json
index 68c3ace47..00147ee7e 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Organisation"
},
+ "ux_pim_grid": {
+ "search_short": "Søg",
+ "reply": "Svar",
+ "reply_to_invitation": "Svar på invitationen"
+ },
"vacation_calendar": {
"label": "Bloker min kalender",
"description": "Tilføjer en optaget begivenhed, mens du er væk, så ingen booker møder med dig imens.",
diff --git a/locales/de/common.json b/locales/de/common.json
index 6acbe0328..4c186bf61 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organisation"
},
+ "ux_pim_grid": {
+ "search_short": "Suchen",
+ "reply": "Antworten",
+ "reply_to_invitation": "Einladung beantworten"
+ },
"vacation_calendar": {
"label": "Meinen Kalender blockieren",
"description": "Trägt für Ihre Abwesenheit einen belegten Termin ein, damit Ihnen niemand Besprechungen in dieser Zeit einträgt.",
diff --git a/locales/en/common.json b/locales/en/common.json
index 9fe28226c..cf9e21b43 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Organisation"
},
+ "ux_pim_grid": {
+ "search_short": "Search",
+ "reply": "Reply",
+ "reply_to_invitation": "Answer the invitation"
+ },
"vacation_calendar": {
"label": "Block my calendar",
"description": "Adds a busy event for the time you’re away, so nobody books meetings with you then.",
diff --git a/locales/es/common.json b/locales/es/common.json
index e1f5a5ebe..30753a99c 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organización"
},
+ "ux_pim_grid": {
+ "search_short": "Buscar",
+ "reply": "Responder",
+ "reply_to_invitation": "Responder a la invitación"
+ },
"vacation_calendar": {
"label": "Bloquear mi calendario",
"description": "Añade un evento ocupado mientras estás fuera, para que nadie te convoque a reuniones entonces.",
diff --git a/locales/et/common.json b/locales/et/common.json
index f77713d19..a2a08af3d 100644
--- a/locales/et/common.json
+++ b/locales/et/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Organisatsioon"
},
+ "ux_pim_grid": {
+ "search_short": "Otsi",
+ "reply": "Vasta",
+ "reply_to_invitation": "Vasta kutsele"
+ },
"vacation_calendar": {
"label": "Blokeeri minu kalender",
"description": "Lisab äraoleku ajaks hõivatud sündmuse, et keegi sel ajal sulle koosolekuid ei paneks.",
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 36f5febbd..9b39e60dd 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "سازمان"
},
+ "ux_pim_grid": {
+ "search_short": "جستجو",
+ "reply": "پاسخ",
+ "reply_to_invitation": "پاسخ به دعوت"
+ },
"vacation_calendar": {
"label": "مسدود کردن تقویم من",
"description": "برای مدت غیبت شما یک رویداد مشغول اضافه میکند تا کسی در آن زمان جلسه نگذارد.",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 96ad6b352..ec5279991 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organisation"
},
+ "ux_pim_grid": {
+ "search_short": "Rechercher",
+ "reply": "Répondre",
+ "reply_to_invitation": "Répondre à l’invitation"
+ },
"vacation_calendar": {
"label": "Bloquer mon calendrier",
"description": "Ajoute un événement occupé pendant votre absence, pour que personne ne vous invite à une réunion à ce moment-là.",
diff --git a/locales/he/common.json b/locales/he/common.json
index 8190e738d..1ab4a6238 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -5551,6 +5551,11 @@
"company_calendars": {
"section": "ארגון"
},
+ "ux_pim_grid": {
+ "search_short": "חיפוש",
+ "reply": "מענה",
+ "reply_to_invitation": "מענה להזמנה"
+ },
"vacation_calendar": {
"label": "חסום את היומן שלי",
"description": "מוסיף אירוע תפוס לזמן ההיעדרות, כדי שאף אחד לא יקבע איתך פגישות אז.",
diff --git a/locales/hu/common.json b/locales/hu/common.json
index 9d7abd079..914c17671 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Szervezet"
},
+ "ux_pim_grid": {
+ "search_short": "Keresés",
+ "reply": "Válasz",
+ "reply_to_invitation": "Válasz a meghívásra"
+ },
"vacation_calendar": {
"label": "Naptáram foglalása",
"description": "Foglalt eseményt tesz a távollét idejére, hogy addig senki ne szervezzen Önnel megbeszélést.",
diff --git a/locales/it/common.json b/locales/it/common.json
index c230dd475..621a7cf13 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organizzazione"
},
+ "ux_pim_grid": {
+ "search_short": "Cerca",
+ "reply": "Rispondi",
+ "reply_to_invitation": "Rispondi all’invito"
+ },
"vacation_calendar": {
"label": "Blocca il mio calendario",
"description": "Aggiunge un evento occupato per il periodo di assenza, così nessuno ti fissa riunioni in quei giorni.",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 859b086d6..a0daf6e38 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "組織"
},
+ "ux_pim_grid": {
+ "search_short": "検索",
+ "reply": "返信",
+ "reply_to_invitation": "招待に返信"
+ },
"vacation_calendar": {
"label": "カレンダーをブロック",
"description": "不在の期間に予定ありの予定を追加し、その間に会議を入れられないようにします。",
diff --git a/locales/ko/common.json b/locales/ko/common.json
index b26122750..ca882fba2 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "조직"
},
+ "ux_pim_grid": {
+ "search_short": "검색",
+ "reply": "응답",
+ "reply_to_invitation": "초대에 응답"
+ },
"vacation_calendar": {
"label": "내 캘린더 차단",
"description": "부재 기간에 바쁨 일정을 추가해 그동안 회의가 잡히지 않게 합니다.",
diff --git a/locales/lv/common.json b/locales/lv/common.json
index 6474a45eb..a94e68744 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organizācija"
},
+ "ux_pim_grid": {
+ "search_short": "Meklēt",
+ "reply": "Atbildēt",
+ "reply_to_invitation": "Atbildēt uz ielūgumu"
+ },
"vacation_calendar": {
"label": "Bloķēt manu kalendāru",
"description": "Pievieno aizņemtu notikumu prombūtnes laikam, lai tad neviens neplānotu ar jums sapulces.",
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 49f47d310..ce0cd5602 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Байгууллага"
},
+ "ux_pim_grid": {
+ "search_short": "Хайх",
+ "reply": "Хариулах",
+ "reply_to_invitation": "Урилгад хариулах"
+ },
"vacation_calendar": {
"label": "Миний хуанлийг хаах",
"description": "Таныг байхгүй үед завгүй арга хэмжээ нэмж, тэр үед хэн ч уулзалт товлохгүй болгоно.",
diff --git a/locales/nb/common.json b/locales/nb/common.json
index 6c54c14c6..d1ed21f2d 100644
--- a/locales/nb/common.json
+++ b/locales/nb/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Organisasjon"
},
+ "ux_pim_grid": {
+ "search_short": "Søk",
+ "reply": "Svar",
+ "reply_to_invitation": "Svar på invitasjonen"
+ },
"vacation_calendar": {
"label": "Blokker kalenderen min",
"description": "Legger inn en opptatt-hendelse mens du er borte, så ingen booker møter med deg da.",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index caa6592d6..4ed0f4dba 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organisatie"
},
+ "ux_pim_grid": {
+ "search_short": "Zoeken",
+ "reply": "Reageren",
+ "reply_to_invitation": "Op uitnodiging reageren"
+ },
"vacation_calendar": {
"label": "Mijn agenda blokkeren",
"description": "Zet een bezette afspraak in je agenda voor de tijd dat je weg bent, zodat niemand dan vergaderingen met je plant.",
diff --git a/locales/pl/common.json b/locales/pl/common.json
index a42440af6..633c4f8e6 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organizacja"
},
+ "ux_pim_grid": {
+ "search_short": "Szukaj",
+ "reply": "Odpowiedz",
+ "reply_to_invitation": "Odpowiedz na zaproszenie"
+ },
"vacation_calendar": {
"label": "Zablokuj mój kalendarz",
"description": "Dodaje zajęte wydarzenie na czas Twojej nieobecności, żeby nikt nie umawiał wtedy z Tobą spotkań.",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index bd73fa852..a14a69a17 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Organização"
},
+ "ux_pim_grid": {
+ "search_short": "Pesquisar",
+ "reply": "Responder",
+ "reply_to_invitation": "Responder ao convite"
+ },
"vacation_calendar": {
"label": "Bloquear o meu calendário",
"description": "Adiciona um evento ocupado durante a sua ausência, para que ninguém marque reuniões consigo nessa altura.",
diff --git a/locales/ro/common.json b/locales/ro/common.json
index ad9938c1a..dc5b51fd1 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Organizație"
},
+ "ux_pim_grid": {
+ "search_short": "Caută",
+ "reply": "Răspunde",
+ "reply_to_invitation": "Răspunde la invitație"
+ },
"vacation_calendar": {
"label": "Blochează-mi calendarul",
"description": "Adaugă un eveniment ocupat pe durata absenței, ca nimeni să nu vă programeze ședințe atunci.",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index e32676136..f580285c7 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Организация"
},
+ "ux_pim_grid": {
+ "search_short": "Поиск",
+ "reply": "Ответить",
+ "reply_to_invitation": "Ответить на приглашение"
+ },
"vacation_calendar": {
"label": "Занять мой календарь",
"description": "Добавляет событие «занят» на время отсутствия, чтобы вам не назначали встречи.",
diff --git a/locales/sk/common.json b/locales/sk/common.json
index 3da1ed6c4..0ae0751b2 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Organizácia"
},
+ "ux_pim_grid": {
+ "search_short": "Hľadať",
+ "reply": "Odpovedať",
+ "reply_to_invitation": "Odpovedať na pozvánku"
+ },
"vacation_calendar": {
"label": "Zablokovať môj kalendár",
"description": "Pridá obsadenú udalosť na čas vašej neprítomnosti, aby vám vtedy nikto neplánoval stretnutia.",
diff --git a/locales/th/common.json b/locales/th/common.json
index d8fbce3f9..4cb41ecbd 100644
--- a/locales/th/common.json
+++ b/locales/th/common.json
@@ -2736,6 +2736,11 @@
"company_calendars": {
"section": "องค์กร"
},
+ "ux_pim_grid": {
+ "search_short": "ค้นหา",
+ "reply": "ตอบกลับ",
+ "reply_to_invitation": "ตอบกลับคำเชิญ"
+ },
"vacation_calendar": {
"label": "บล็อกปฏิทินของฉัน",
"description": "เพิ่มกิจกรรมไม่ว่างในช่วงที่คุณไม่อยู่ เพื่อไม่ให้ใครนัดประชุมกับคุณในเวลานั้น",
diff --git a/locales/tr/common.json b/locales/tr/common.json
index 59e4b73db..2c534597e 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "Kuruluş"
},
+ "ux_pim_grid": {
+ "search_short": "Ara",
+ "reply": "Yanıtla",
+ "reply_to_invitation": "Davete yanıt ver"
+ },
"vacation_calendar": {
"label": "Takvimimi kapat",
"description": "Uzakta olduğunuz süre için meşgul bir etkinlik ekler, böylece o sırada kimse sizinle toplantı ayarlamaz.",
diff --git a/locales/uk/common.json b/locales/uk/common.json
index be354b49c..078b69b26 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "Організація"
},
+ "ux_pim_grid": {
+ "search_short": "Пошук",
+ "reply": "Відповісти",
+ "reply_to_invitation": "Відповісти на запрошення"
+ },
"vacation_calendar": {
"label": "Заблокувати мій календар",
"description": "Додає подію «зайнято» на час вашої відсутності, щоб вам тоді не призначали зустрічей.",
diff --git a/locales/vi/common.json b/locales/vi/common.json
index fe1be93e4..6c5474904 100644
--- a/locales/vi/common.json
+++ b/locales/vi/common.json
@@ -5153,6 +5153,11 @@
"company_calendars": {
"section": "Tổ chức"
},
+ "ux_pim_grid": {
+ "search_short": "Tìm",
+ "reply": "Trả lời",
+ "reply_to_invitation": "Trả lời lời mời"
+ },
"vacation_calendar": {
"label": "Chặn lịch của tôi",
"description": "Thêm một sự kiện bận trong thời gian bạn vắng mặt để không ai đặt cuộc họp với bạn lúc đó.",
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
index 205e145f6..19437aba7 100644
--- a/locales/zh-TW/common.json
+++ b/locales/zh-TW/common.json
@@ -5141,6 +5141,11 @@
"company_calendars": {
"section": "組織"
},
+ "ux_pim_grid": {
+ "search_short": "搜尋",
+ "reply": "回覆",
+ "reply_to_invitation": "回覆邀請"
+ },
"vacation_calendar": {
"label": "佔用我的行事曆",
"description": "在你外出期間新增一個忙碌活動,這樣別人不會在這段時間約你開會。",
diff --git a/locales/zh/common.json b/locales/zh/common.json
index 0e601ed20..7e0fff333 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -5732,6 +5732,11 @@
"company_calendars": {
"section": "组织"
},
+ "ux_pim_grid": {
+ "search_short": "搜索",
+ "reply": "回复",
+ "reply_to_invitation": "回复邀请"
+ },
"vacation_calendar": {
"label": "占用我的日历",
"description": "在你外出期间添加一个忙碌活动,这样别人不会在这段时间约你开会。",