Skip to content

Examples

Below are practical examples for common scenarios.

"use client";
import React from "react";
import { useTranslatedToast } from "~/lib/toasts";
export default function SaveButton() {
const { toast } = useTranslatedToast();
function onSaved() {
toast.success({
key: "notifications.saved",
type: "regular",
data: { name: "Alice" },
});
}
return <button onClick={onSaved}>Save</button>;
}

Notes:

  • The wrapper will call the configured translator under the hood and forward the resolved string (or node) to Sonner.
  • data is optional and maps to translation interpolation.

If your application keeps notifications under a dedicated namespace (e.g. notifications), you can pass notificationsRoot when calling the useTranslatedToast hook. This prefix will be prepended to all message keys passed to the toast methods.

const { toast } = useTranslatedToast({ notificationsRoot: "notifications" });
toast.info({ key: "updateAvailable", type: "regular" });
// actual key used by translator: 'notifications.updateAvailable'

This is handy to avoid repeating the notifications. prefix across your codebase.

next-intl supports several output modes. The wrapper forwards through the type field:

  • type: 'rich' — use rich tag functions (translator returns React nodes).
  • type: 'markup' — returns markup string (useful if Sonner supports HTML strings).
  • type: 'raw' — returns raw string without interpolation.

Example — rich:

toast.custom((id) => (
<div>
<h4>{translator("notifications.titleWithRich")}</h4>
<div>
{translator.rich("notifications.richDescription", {
bold: (chunks) => <strong>{chunks}</strong>,
})}
</div>
</div>
));

But in most cases you will use the typed TranslatedMessage descriptor instead:

toast.info({
key: "notifications.richDescription",
type: "rich",
data: { user: "Bob" },
});

The wrapper supports Sonner’s promise accepting translated descriptors for loading, success, error and description.

const fetchData = async () => {
// some async op
};
toast.promise(fetchData(), {
loading: { key: "notifications.loading", type: "regular" },
success: async (data) => ({
message: {
key: "notifications.successSaved",
type: "regular",
data: { count: data.count },
},
}),
error: (err) => ({
message: { key: "notifications.failed", type: "regular" },
}),
});

The wrapper cleanly handles both static objects and functions that return objects, and translates messages returned from async resolvers.

If you want full control over markup, use toast.custom. You can still provide a translated description in the data argument for Sonner:

toast.custom(
(id) => (
<div className="my-toast">
<h3>{translator("notifications.customTitle")}</h3>
</div>
),
{
description: { key: "notifications.customDescription", type: "markup" },
}
);

The wrapper will translate description before passing it.