Labels & announcements

Every text Cloudlet writes itself, where to localize it, and a complete translation.

On this page

Cloudlet ships English texts for its controls, for what screen readers hear and for the few titles it writes itself. Each group is replaced in its own place, without an i18n dependency. Your notifications' own content (titles, descriptions, action labels) is yours: pass it already translated to show and track.

WhatWhereType
Names of controls and regions<Toaster labels>CloudletLabels
What screen readers hear<Toaster announcements>AnnouncementFormatters
Titles the controller writes when your content is missing or failedcreateCloudlet({ messages })FallbackMessages
Text direction<Toaster dir>'ltr' | 'rtl'
Notification titles, descriptions, action labelsyour show and track callsstring

Each takes a partial object: pass only what you change, and the rest keeps its English default. A function that throws falls back to the default text and reports one formatter-error diagnostic per function.

CloudletLabels

Read on every render of the Toaster.

PropTypeDefaultDescription
regionstring'Notifications'Accessible name of the notifications region.
islandstring'Current notifications'Accessible name of the opened Island, a carousel region.
carouselstring'carousel'How screen readers name the opened Island's role (aria-roledescription). Translate it with the rest.
slidestring'slide'How screen readers name each slide's role in the Island (aria-roledescription).
closestring'Close notification'Name of the close button.
retrystring'Retry'Text of the retry button of a failed operation.
cancelstring'Request cancellation'Text of the cancel button of a pending operation.
cancellationRequestedstring'Cancellation requested'Text of that button once cancellation was requested.
acknowledgeOverflowstring'Acknowledge missed notifications'Name of the button that resets the missed count.
collapsestring'Press Escape to collapse'Hidden hint in the Island that Escape collapses it.
stackSummary(title: string, count: number, missed: number) => string'Saved, 3 notifications, 2 missed'Name of the collapsed pill: the top title, how many notifications the stack holds and how many were missed. The default leaves out a count of 1 and a missed count of 0.
page(index: number, total: number) => string'2 of 5'Name of the Island's current slide; index starts at 1.
overflowCount(count: number) => string'4 missed notifications'The missed-notifications line in the Island.

AnnouncementFormatters

Cloudlet speaks through two permanent live regions, polite and assertive, chosen per notification by announcementPriority; 'off' silences a notification's automatic text but not its controls. Announcements within 300 ms are spoken together. Progress updates, reflows and seeing a notification again are never announced. The formatters are read when an announcement is made, so the latest props apply.

PropTypeDefaultDescription
notification(notification: NotificationSnapshot) => stringtitle and descriptionA notification shown for the first time. The default speaks the title, then the text description or the announcement of a rich one.
queued(count: number) => string'2 notifications queued'Notifications that arrived behind the presented one, counted together.
occurrences(notification: NotificationSnapshot) => string'Saved. 3 occurrences'A deduplicated notification repeated; read notification.occurrences.
overflow(count: number) => string'4 missed notifications'Notifications rejected because the stack was full.
operation(notification: NotificationSnapshot, phase) => stringtitle and descriptionA tracked operation changed phase: 'started', 'fulfilled', 'rejected', 'cancelled' or 'cancel-requested'. Each phase of an attempt is spoken once.
actionError(notification: NotificationSnapshot, message: string) => string'Upload failed. Action failed'A command action failed; message is its errorMessage or messages.actionError.
actionPending(notification: NotificationSnapshot) => string'Upload failed. Retry. In progress'Optional. A command action is running: title, action label, state.
resumed(count: number) => string'3 current notifications'Optional. The user returned to a hidden tab: the current count, without replaying what happened meanwhile.

actionPending and resumed are optional in the type itself, so an object written for an older version that lacks them stays complete. Delivery to a live region does not prove a screen reader spoke it: keep important results in your main interface too.

Controller messages

Titles the controller writes itself, when a mapping function fails, a cancellation has no content of its own, or a rich slot cannot render. They are part of the notification, so they are set where the controller is created: see messages on createCloudlet for the six fields and their defaults.

A complete translation

Typing the objects as the full CloudletLabels, Required<AnnouncementFormatters> and FallbackMessages, instead of their partial forms, makes TypeScript point out a text you forgot.

notifications-de.tsx
import type { ReactNode } from 'react';
import {
  CloudletProvider,
  createCloudlet,
  Toaster,
  type AnnouncementFormatters,
  type CloudletLabels,
  type FallbackMessages,
  type NotificationSnapshot,
} from '@cloudlet/toast';

const count = (value: number, one: string, other: string) => `${value} ${value === 1 ? one : other}`;

const labels: CloudletLabels = {
  region: 'Benachrichtigungen',
  island: 'Aktuelle Benachrichtigungen',
  carousel: 'Karussell',
  slide: 'Folie',
  close: 'Benachrichtigung schließen',
  retry: 'Erneut versuchen',
  cancel: 'Abbruch anfordern',
  cancellationRequested: 'Abbruch angefordert',
  acknowledgeOverflow: 'Verpasste Benachrichtigungen bestätigen',
  collapse: 'Mit Escape einklappen',
  stackSummary: (title, total, missed) => [
    title,
    ...(total > 1 ? [count(total, 'Benachrichtigung', 'Benachrichtigungen')] : []),
    ...(missed > 0 ? [`${missed} verpasst`] : []),
  ].join(', '),
  page: (index, total) => `${index} von ${total}`,
  overflowCount: (missed) => count(missed, 'verpasste Benachrichtigung', 'verpasste Benachrichtigungen'),
};

/** Title, then the description: a text, or the spoken alternative of a rich one. */
function spoken({ content }: NotificationSnapshot<ReactNode>) {
  const description = content.description?.kind === 'rich' ? content.description.announcement : content.description?.text;
  return description ? `${content.title}. ${description}` : content.title;
}

const announcements: Required<AnnouncementFormatters> = {
  notification: spoken,
  queued: (queued) => `${count(queued, 'weitere Benachrichtigung', 'weitere Benachrichtigungen')}`,
  occurrences: (notification) => `${notification.content.title}. ${notification.occurrences}-mal`,
  overflow: (missed) => count(missed, 'verpasste Benachrichtigung', 'verpasste Benachrichtigungen'),
  operation: spoken,
  actionError: (notification, message) => `${notification.content.title}. ${message}`,
  actionPending: (notification) => `${notification.content.title}. ${notification.content.action?.label ?? ''}. Läuft`,
  resumed: (current) => count(current, 'aktuelle Benachrichtigung', 'aktuelle Benachrichtigungen'),
};

const messages: FallbackMessages = {
  operationSuccess: 'Erledigt',
  operationError: 'Etwas ist schiefgelaufen',
  operationCancelled: 'Abgebrochen',
  cancellationRequested: 'Abbruch angefordert',
  actionError: 'Aktion fehlgeschlagen',
  renderError: 'Inhalt nicht verfügbar',
};

const cloudlet = createCloudlet({ messages });

export function Notifications({ children }: { children: ReactNode }) {
  return (
    <CloudletProvider controller={cloudlet}>
      {children}
      <Toaster labels={labels} announcements={announcements} dir="ltr" />
    </CloudletProvider>
  );
}

Keep labels and announcements outside the component, or memoize them, so a render does not create new functions for nothing. For a right-to-left language, pass dir="rtl" as well: direction is not inferred from the texts.