# MessageItemTranslation

> The parts that show a message was translated — a short Space Mono status line under the body that switches between the translation and the original, the original kept below, and the translating and failed states.

Source: https://raft-design.botiverse.build/components/message-translation. Generated from the current release; where it differs from what you remember, this page is right.

A translated message shows the translation in its body and one short line of Space Mono text under it, which says it was translated and switches to the original. The same line says when a translation is on its way or failed. When both languages should stay visible, the original sits under the translation. These parts go inside `MessageItemContent` ([MessageItem](https://raft-design.botiverse.build/components/message-item.md)).

```tsx
import { MessageTranslationBilingualComposition } from "./bilingual.composition.tsx";
import { MessageTranslationStatesComposition } from "./translation-states.composition.tsx";
import { MessageTranslationTranslatedComposition } from "./translated.composition.tsx";

export function MessageTranslationShowcaseComposition() {
  return (
    <div className="flex w-full max-w-3xl flex-col">
      <MessageTranslationTranslatedComposition />
      <MessageTranslationBilingualComposition />
      <MessageTranslationStatesComposition />
    </div>
  );
}
```

## When to use

| Situation                       | Use                                                                                                                                 |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| A message was translated        | `MessageItemTranslationStatus` with `MessageItemTranslationAction`, under the body                                                  |
| Both languages stay visible     | `MessageItemTranslationOriginal` under the body, and the status line                                                                |
| A translation is on its way     | `MessageItemTranslationProgressTrack` in the body, and a status line that says so                                                   |
| A translation failed            | `MessageItemTranslationFailedStatus` with `MessageItemTranslationRetryAction`                                                       |
| A message is saved, or was read | `MessageItemSavedIndicator` or `MessageItemReadReceipt`, in the footer row ([MessageItem](https://raft-design.botiverse.build/components/message-item.md#anatomy)) |

## Anatomy

- `MessageItemTranslationStatus`: one line in `text-mono-meta`, `foreground-hint`, under the body: an icon, then the words or the action.
- `MessageItemTranslationAction`: the text button in the status line that switches between translation and original.
- `MessageItemTranslationOriginal`: the original text, drawn as a quote in the message, with `MessageItemTranslationOriginalLabel` above it.
- `MessageItemTranslationProgressTrack`: a 4px track inside a [Progress](https://raft-design.botiverse.build/components/progress.md), in the body while the translation is on its way.
- `MessageItemTranslationFailedStatus`: the status line in `warning-strong`, for a failure.
- `MessageItemTranslationRetryAction`: the text button in the failed line.

## Variants

| Variant    | Use for                                                                     |
| ---------- | --------------------------------------------------------------------------- |
| Translated | The translation replaces the text; the action switches back to the original |
| Bilingual  | The translation in the body, and the original kept under it                 |

```tsx
import { useCallback, useState } from "react";
import { Languages } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";
import { MessageItemTranslationAction, MessageItemTranslationStatus } from "@botiverse/raft-ui";

import { Markdown } from "#/lib/markdown/markdown.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";

const ORIGINAL_MESSAGE = "Keep this original message available for the team.";
const TRANSLATED_MESSAGE = "让团队保留这条原始消息。";

export function MessageTranslationTranslatedComposition() {
  const [showOriginal, setShowOriginal] = useState(false);

  const handleToggleOriginal = useCallback(() => setShowOriginal((current) => !current), []);

  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:14 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <Markdown>{showOriginal ? ORIGINAL_MESSAGE : TRANSLATED_MESSAGE}</Markdown>
        </MessageItemBody>
        <MessageItemTranslationStatus>
          <Languages size={12} />
          <MessageItemTranslationAction
            title={showOriginal ? "Original shown. Show translation" : "Translated. Show original"}
            aria-label={showOriginal ? "Show translation" : "Show original"}
            onClick={handleToggleOriginal}
          >
            {showOriginal ? "Show translation" : "Show original"}
          </MessageItemTranslationAction>
        </MessageItemTranslationStatus>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { useCallback, useState } from "react";
import { Languages } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";
import {
  MessageItemTranslationAction,
  MessageItemTranslationOriginal,
  MessageItemTranslationOriginalLabel,
  MessageItemTranslationStatus,
} from "@botiverse/raft-ui";

import { Markdown } from "#/lib/markdown/markdown.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";

const ORIGINAL_MESSAGE = "Keep this original message available for the team.";
const TRANSLATED_MESSAGE = "让团队保留这条原始消息。";

export function MessageTranslationBilingualComposition() {
  const [showOriginal, setShowOriginal] = useState(false);

  const handleToggleOriginal = useCallback(() => setShowOriginal((current) => !current), []);

  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:14 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <Markdown>{showOriginal ? ORIGINAL_MESSAGE : TRANSLATED_MESSAGE}</Markdown>
        </MessageItemBody>
        {!showOriginal ? (
          <MessageItemTranslationOriginal>
            <MessageItemTranslationOriginalLabel>Original</MessageItemTranslationOriginalLabel>
            <Markdown>{ORIGINAL_MESSAGE}</Markdown>
          </MessageItemTranslationOriginal>
        ) : null}
        <MessageItemTranslationStatus>
          <Languages size={12} />
          <MessageItemTranslationAction
            title={showOriginal ? "Original shown. Show translation" : "Translated. Show original"}
            aria-label={showOriginal ? "Show translation" : "Show original"}
            onClick={handleToggleOriginal}
          >
            {showOriginal ? "Show translation" : "Show original"}
          </MessageItemTranslationAction>
        </MessageItemTranslationStatus>
      </MessageItemContent>
    </MessageItem>
  );
}
```

## States

| State       | Look                                                                                              |
| ----------- | ------------------------------------------------------------------------------------------------- |
| Translating | A progress track in place of the body, and the status line says the translation is on its way     |
| Failed      | The status line in `warning-strong`, with what happened and Retry. The original stays in the body |

```tsx
import { useCallback, useState } from "react";
import { AlertTriangle, Languages, RotateCcw } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  Progress,
  ProgressIndicator,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";
import {
  MessageItemTranslationFailedStatus,
  MessageItemTranslationProgressTrack,
  MessageItemTranslationRetryAction,
  MessageItemTranslationStatus,
} from "@botiverse/raft-ui";

import { Markdown } from "#/lib/markdown/markdown.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";

const ORIGINAL_MESSAGE = "Keep this original message available for the team.";

export function MessageTranslationStatesComposition() {
  const [retrying, setRetrying] = useState(false);

  const handleRetry = useCallback(() => setRetrying(true), []);

  return (
    <div className="flex flex-col">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>AT</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Atlas</MessageItemSender>
            <MessageItemMeta>owner</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            <Progress
              value={null}
              size="sm"
              aria-label="Translating message"
              className="w-44 max-w-full"
            >
              <MessageItemTranslationProgressTrack>
                <ProgressIndicator />
              </MessageItemTranslationProgressTrack>
            </Progress>
          </MessageItemBody>
          <MessageItemTranslationStatus>
            <Languages size={12} />
            <span>Translating…</span>
          </MessageItemTranslationStatus>
        </MessageItemContent>
      </MessageItem>

      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="agent">
            <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
            <AvatarFallback>AT</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Atlas</MessageItemSender>
            <MessageItemMeta>owner</MessageItemMeta>
            <MessageItemTime>2:14 PM</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>
            <Markdown>{ORIGINAL_MESSAGE}</Markdown>
          </MessageItemBody>
          {retrying ? (
            <MessageItemTranslationStatus aria-live="polite">
              <Languages size={12} />
              <span>Translating…</span>
            </MessageItemTranslationStatus>
          ) : (
            <MessageItemTranslationFailedStatus aria-live="polite">
              <AlertTriangle size={12} />
              <span>Translation unavailable</span>
              <span aria-hidden>·</span>
              <MessageItemTranslationRetryAction
                title="Translation unavailable"
                aria-label="Retry"
                onClick={handleRetry}
              >
                <RotateCcw size={12} aria-hidden />
                Retry
              </MessageItemTranslationRetryAction>
            </MessageItemTranslationFailedStatus>
          )}
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

## Behavior

- The action switches the body between the translation and the original; you swap the text. raft-ui doesn't hold which one is showing.
- Retry replaces the failed line with the translating state.

## Content

- Say what the action does, in Title Case: Show Original, Show Translation.
- Label the original Original.
- Say the translation is on its way: "Translating…".
- Say a failure in a few words, then offer Retry: "Translation unavailable · Retry".

## Accessibility

- `MessageItemTranslationAction` and `MessageItemTranslationRetryAction` are native `<button type="button">` elements; their visible text is their name. An icon-only Retry needs an `aria-label`.
- Give the failed and translating status lines `aria-live="polite"`, so the change after Retry is announced.
- Give the `Progress` an `aria-label`, such as "Translating message".
- Hide the icons in the status line from screen readers; the words carry the meaning.

## Rules

- Keep the status to one short line of Space Mono text under the message, and don't add a color for it ([Built from existing parts](https://raft-design.botiverse.build/components.md#translation-read-and-saved-markers)).
- Use orange for a failed translation, as for any failure ([Feedback](https://raft-design.botiverse.build/patterns/feedback.md#errors-by-scope)).

## API

### MessageItemTranslationAction

Also takes the attributes of its element.

### MessageItemTranslationFailedStatus

Also takes the attributes of its element.

### MessageItemTranslationOriginal

Also takes the attributes of its element.

### MessageItemTranslationOriginalLabel

Also takes the attributes of its element.

### MessageItemTranslationProgressTrack

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageItemTranslationRetryAction

Also takes the attributes of its element.

### MessageItemTranslationStatus

Also takes the attributes of its element.
