Notifications text

Use this page to change the text of a notification, whether a built-in one or one your app publishes. The text is translated with the rest of the UI, so it follows the user's language. Where notifications appear and how they look is on Displaying notifications. What screen readers do with them is on Notifications accessibility.

Translating notifications

The Notification component chooses the text of each notification as it renders it, by the notification's type:

  1. A type stream-chat publishes, such as api:attachment:upload:failed, renders a translation key through t(). The SDK maps every type stream-chat defines.
  2. A few types the SDK publishes itself, api:poll:end:success, api:poll:end:failed, browser:audio:playback:error and channel:jumpToFirstUnread:failed, are mapped the same way.
  3. Any other type renders notification.message as it is.

The SDK's components translate most of their notifications before publishing them: "Message pinned" is t("message.pinned.text", "Message pinned"). So there are three ways to change the text:

  • A notification from an SDK component: override the key it was published with. When a message action fails with an error that has a message, the notification shows that message instead.
  • A notification from stream-chat, or one of the four SDK types above: override the key its type is mapped to, listed in Keys by type.
  • Text that depends on the notification: render your own Notification.
import { Streami18n } from "stream-chat-react-native";

export const streami18n = new Streami18n({
  translationsForLanguage: {
    // Published by the Pin action, translated at the call site.
    "message.pinned.text": "Pinned",
    // Published by stream-chat: an upload that failed.
    "notifications.attachmentUploadFailed.error":
      "That file could not be uploaded",
    "notifications.attachmentUploadFailed.withReason.error":
      "That file could not be uploaded: {{reason}}",
  },
});

Pass the instance to Chat and OverlayProvider as i18nInstance. Localization covers dictionaries, other languages and reading the catalog of keys from your editor.

Keys by type

A type stream-chat publishes, with the key it renders:

Type Key English
validation:attachment:file:missing notifications.attachmentFileMissing.error File is required for upload attachment
validation:attachment:id:missing notifications.attachmentIdMissing.error Local upload attachment missing local id
validation:attachment:upload:blocked notifications.attachmentUploadBlocked.error Attachment upload blocked due to {{reason}}
api:attachment:upload:failed notifications.attachmentUploadFailed.error, or notifications.attachmentUploadFailed.withReason.error Error uploading attachment
validation:attachment:upload:in-progress notifications.attachmentUploadInProgress.error Wait until all attachments have uploaded
validation:command:disabled notifications.commandUnavailable.error, .whileEditing.error or .whileReplying.error Command not available
validation:command:not-ready notifications.commandNotReady.error Command not ready to be sent
api:location:create:failed notifications.locationShareFailed.error Failed to share location
api:message:jump:failed notifications.messageJumpFailed.error Failed to jump to the message
api:message:jumpToLatest:failed notifications.messageJumpToLatestFailed.error Failed to jump to the latest message
api:message:send:failed common.sendMessageFailed.error Send message request failed
api:message:update:failed common.editMessageFailed.error Edit message request failed
validation:poll:castVote:limit notifications.voteLimitReached.error Reached the vote limit. Remove an existing vote first.
api:poll:create:failed notifications.pollCreateFailed.error, or notifications.pollCreateFailed.withReason.error Failed to create the poll

Four types the SDK publishes:

Type Key English
api:poll:end:success notifications.pollEnded.text Poll ended
api:poll:end:failed poll.endVote.error, or notifications.pollEndFailed.withReason.error Failed to end the poll
browser:audio:playback:error the message the player published: audioPlayer.playbackFailed.error, .formatUnsupported.error or .seekUnsupported.error Failed to play the recording
channel:jumpToFirstUnread:failed channel.jumpToFirstUnreadFailed.error Failed to jump to the first unread message

The keys with withReason are used when the notification carries a string in metadata.reason, which they put in {{reason}} in lowercase. An upload that was blocked fills {{reason}} from three keys of its own: notifications.attachmentUploadBlocked.reason.sizeLimit.text for a size_limit reason, .unknownError.text for a reason that isn't a string, and .unsupportedFileType.text for any other. A disabled command uses .whileEditing.error for the reason editing, and .whileReplying.error for quoted_message. When browser:audio:playback:error carries no message, it renders notifications.recordingPlaybackFailed.error, "Error reproducing the recording".

Text that depends on the notification

The SDK has no registry of translators. To change the text beyond what the keys allow, replace the Notification component and pass the default one a notification with the text you want. This one names the author of a pinned message:

import type { PropsWithChildren } from "react";
import {
  asDynamicKey,
  Notification,
  useTranslationContext,
  WithComponents,
} from "stream-chat-react-native";
import type {
  ComponentOverrides,
  NotificationProps,
} from "stream-chat-react-native";

const PIN_SUCCESS = "api:message:pin:success";

const PinNotification = (props: NotificationProps) => {
  const { t } = useTranslationContext();
  const { notification } = props;
  if (notification.type !== PIN_SUCCESS) return <Notification {...props} />;

  const message = notification.origin.context?.message as
    { user?: { name?: string } } | undefined;
  const text = t(
    asDynamicKey("myApp.notification.messagePinned"),
    "Pinned a message from {{author}}",
    { author: message?.user?.name ?? "someone" },
  );

  return (
    <Notification
      {...props}
      notification={{ ...notification, message: text }}
    />
  );
};

const overrides: ComponentOverrides = { Notification: PinNotification };

export const WithPinNotification = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);

This works for the types the SDK doesn't map. A type from the tables above renders its key instead of the message the notification carries, except browser:audio:playback:error, which renders its message when it has one. A replacement Notification has to render the text itself for those types. Declare overrides at module scope: WithComponents reads it once.

Text of notifications you publish

A notification of your own renders its message as you publish it, so translate it first. Keys of your own take part in the dictionaries like any other, and t reads them through asDynamicKey, as Localization shows:

import { Pressable, Text } from "react-native";
import {
  asDynamicKey,
  useNotificationApi,
  useTranslationContext,
} from "stream-chat-react-native";

export const FavoriteButton = ({
  onFavorite,
}: {
  onFavorite: () => Promise<void>;
}) => {
  const { addNotification } = useNotificationApi();
  const { t } = useTranslationContext();

  const favorite = async () => {
    try {
      await onFavorite();
      addNotification({
        message: t(
          asDynamicKey("myApp.favorite.success"),
          "Added to favorites",
        ),
        options: { severity: "success", type: "app:channel:favorite:success" },
        origin: { emitter: "FavoriteButton" },
      });
    } catch (error) {
      addNotification({
        message: t(
          asDynamicKey("myApp.favorite.failed"),
          "Could not add to favorites",
        ),
        options: {
          originalError: error instanceof Error ? error : undefined,
          severity: "error",
          type: "app:channel:favorite:failed",
        },
        origin: { emitter: "FavoriteButton" },
      });
    }
  };

  return (
    <Pressable accessibilityRole="button" onPress={favorite}>
      <Text>Favorite</Text>
    </Pressable>
  );
};

Use a type of your own, in the domain:entity:operation:result form. A type from the tables above renders its mapped key instead of the message, except browser:audio:playback:error, which renders its message when it has one. Publishing, severity and durations are on Publishing notifications, and routing a notification to another part of the screen is on Routing notifications.

Text the SDK's components don't translate

The SDK's components publish notifications of their own, such as when a message action succeeds or a channel can't be left. Notifications lists every type, with its emitter and when it's published, and What the SDK publishes lists them by component.

Most messages are translated with t() before they're published. A failed message action whose error carries a message is the exception: the notification shows that message, which is the error's own text and isn't translated.

Uploads, sending, jumps in the message paginator, commands and poll votes are published by stream-chat: see Publishing notifications.

Used in