---
title: "Building RTL-Compatible Agent Interfaces: Arabic, Hebrew, and Persian Support"
description: "Implement right-to-left text support, bidirectional content handling, and UI mirroring for AI agent interfaces serving Arabic, Hebrew, and Persian-speaking users."
canonical: https://callsphere.ai/blog/building-rtl-compatible-agent-interfaces-arabic-hebrew-persian
category: "Learn Agentic AI"
tags: ["RTL Support", "Bidirectional Text", "Arabic UI", "AI Interfaces", "Accessibility"]
author: "CallSphere Team"
published: 2026-03-17T00:00:00.000Z
updated: 2026-05-06T01:02:43.217Z
---

# Building RTL-Compatible Agent Interfaces: Arabic, Hebrew, and Persian Support

> Implement right-to-left text support, bidirectional content handling, and UI mirroring for AI agent interfaces serving Arabic, Hebrew, and Persian-speaking users.

## The RTL Challenge in AI Interfaces

Right-to-left (RTL) language support goes far beyond flipping text direction. When an AI agent serves Arabic, Hebrew, or Persian users, the entire interface layout must mirror: navigation moves to the right, progress indicators reverse, chat bubbles swap sides, and mixed-direction content (code snippets, URLs, numbers within Arabic text) must render correctly without garbling.

For AI agents specifically, the challenge intensifies because agent responses often mix RTL text with LTR elements — code blocks, technical terms, URLs, and mathematical expressions all flow left-to-right even within an Arabic response.

## Detecting RTL Requirements

Determine directionality from the language code and apply it to the response context.

```mermaid
flowchart LR
    INPUT(["User intent"])
    PARSE["Parse plus
classify"]
    PLAN["Plan and tool
selection"]
    AGENT["Agent loop
LLM plus tools"]
    GUARD{"Guardrails
and policy"}
    EXEC["Execute and
verify result"]
    OBS[("Trace and metrics")]
    OUT(["Outcome plus
next action"])
    INPUT --> PARSE --> PLAN --> AGENT --> GUARD
    GUARD -->|Pass| EXEC --> OUT
    GUARD -->|Fail| AGENT
    AGENT --> OBS
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
```

```python
from dataclasses import dataclass
from typing import Set

RTL_LANGUAGES: Set[str] = {"ar", "he", "fa", "ur", "ps", "sd", "yi", "dv"}

@dataclass
class DirectionalityContext:
    language: str
    is_rtl: bool
    base_direction: str  # "rtl" or "ltr"
    alignment: str       # "right" or "left"

    @classmethod
    def from_language(cls, lang_code: str) -> "DirectionalityContext":
        lang = lang_code.split("-")[0].split("_")[0].lower()
        is_rtl = lang in RTL_LANGUAGES
        return cls(
            language=lang,
            is_rtl=is_rtl,
            base_direction="rtl" if is_rtl else "ltr",
            alignment="right" if is_rtl else "left",
        )

# Usage
ctx = DirectionalityContext.from_language("ar_SA")
print(ctx.base_direction)  # "rtl"
print(ctx.alignment)       # "right"
```

## Handling Bidirectional Text in Agent Responses

Agent responses often contain embedded LTR content within RTL text. Use Unicode bidirectional control characters to prevent display corruption.

```python
import re

# Unicode Bidi control characters
LRI = "\u2066"  # Left-to-Right Isolate
RLI = "\u2067"  # Right-to-Left Isolate
PDI = "\u2069"  # Pop Directional Isolate
LRM = "\u200E"  # Left-to-Right Mark
RLM = "\u200F"  # Right-to-Left Mark

class BidiTextProcessor:
    """Process bidirectional text for correct display."""

    def wrap_ltr_in_rtl(self, text: str) -> str:
        """Wrap LTR segments (code, URLs, numbers) in isolation markers within RTL text."""
        # Isolate URLs
        text = re.sub(
            r"(https?://\S+)",
            lambda m: f"{LRI}{m.group(1)}{PDI}",
            text,
        )
        # Isolate code in single backticks
        text = re.sub(
            r"`([^`]+)`",
            lambda m: f"`{LRI}{m.group(1)}{PDI}`",
            text,
        )
        # Isolate standalone numbers with units
        text = re.sub(
            r"(\d+[\w%$]+)",
            lambda m: f"{LRI}{m.group(1)}{PDI}",
            text,
        )
        return text

    def prepare_code_block(self, code: str, surrounding_dir: str) -> str:
        """Ensure code blocks always render LTR regardless of surrounding direction."""
        if surrounding_dir == "rtl":
            return f"{LRI}{code}{PDI}"
        return code

    def fix_punctuation(self, text: str, direction: str) -> str:
        """Ensure punctuation appears on the correct side for the text direction."""
        if direction == "rtl":
            # Arabic/Hebrew punctuation should be at the logical end
            text = text.replace(f".{LRI}", f"{LRI}.")
        return text
```

## Backend Response Formatting for RTL

When the agent generates responses, annotate them with directionality metadata so the frontend can render correctly.

```python
from typing import List
from dataclasses import dataclass, field

@dataclass
class FormattedSegment:
    text: str
    direction: str  # "rtl", "ltr", or "auto"
    segment_type: str  # "text", "code", "url", "number"

@dataclass
class DirectionalResponse:
    base_direction: str
    segments: List[FormattedSegment] = field(default_factory=list)

class RTLResponseFormatter:
    def __init__(self, bidi: BidiTextProcessor):
        self.bidi = bidi

    def format_response(self, text: str, lang: str) -> DirectionalResponse:
        ctx = DirectionalityContext.from_language(lang)
        response = DirectionalResponse(base_direction=ctx.base_direction)

        # Split response into segments by code fence delimiters
        fence = "~" * 3
        parts = re.split(rf"({fence}\w*\n[\s\S]*?{fence})", text)
        for part in parts:
            if part.startswith(fence):
                response.segments.append(
                    FormattedSegment(text=part, direction="ltr", segment_type="code")
                )
            elif ctx.is_rtl:
                processed = self.bidi.wrap_ltr_in_rtl(part)
                response.segments.append(
                    FormattedSegment(text=processed, direction="rtl", segment_type="text")
                )
            else:
                response.segments.append(
                    FormattedSegment(text=part, direction="ltr", segment_type="text")
                )
        return response
```

## UI Mirroring Metadata

Send layout hints to the frontend so the chat interface mirrors correctly for RTL users.

```python
def generate_layout_hints(direction: str) -> dict:
    """Generate CSS/layout hints for the frontend."""
    if direction == "rtl":
        return {
            "dir": "rtl",
            "text_align": "right",
            "user_bubble_side": "left",   # Mirrored from LTR default
            "agent_bubble_side": "right",
            "input_icon_position": "left",
            "scrollbar_side": "left",
            "nav_direction": "row-reverse",
            "font_family": "'Noto Sans Arabic', 'Segoe UI', sans-serif",
        }
    return {
        "dir": "ltr",
        "text_align": "left",
        "user_bubble_side": "right",
        "agent_bubble_side": "left",
        "input_icon_position": "right",
        "scrollbar_side": "right",
        "nav_direction": "row",
        "font_family": "'Inter', 'Segoe UI', sans-serif",
    }
```

## Input Handling for RTL

Agent input fields must handle mixed-direction typing. When a user types Arabic text and then inserts an English technical term, the cursor behavior and text flow must remain predictable.

```python
class RTLInputValidator:
    """Validate and normalize RTL input before processing."""

    def normalize_input(self, text: str, expected_dir: str) -> str:
        """Normalize Unicode and strip problematic bidi overrides from user input."""
        import unicodedata
        # Normalize to NFC form
        text = unicodedata.normalize("NFC", text)
        # Remove potentially malicious bidi override characters
        dangerous = {"\u202A", "\u202B", "\u202C", "\u202D", "\u202E"}
        for char in dangerous:
            text = text.replace(char, "")
        return text.strip()

    def detect_mixed_direction(self, text: str) -> bool:
        """Check if text contains both RTL and LTR scripts."""
        has_rtl = bool(re.search(r"[\u0600-\u06FF\u0590-\u05FF\u0750-\u077F]", text))
        has_ltr = bool(re.search(r"[a-zA-Z]", text))
        return has_rtl and has_ltr
```

## FAQ

### Do I need separate UI builds for RTL and LTR?

No. Modern CSS with logical properties (`margin-inline-start` instead of `margin-left`) and the `dir` HTML attribute handle mirroring automatically. Build one responsive interface that adapts based on the direction attribute. This is significantly easier to maintain than separate builds.

### How do I handle RTL text in agent logs and debugging?

Logs should store raw Unicode text without bidi formatting characters. Add the language code and direction as structured metadata fields alongside the log entry. This keeps logs machine-readable while preserving full content. Bidi rendering should only happen at the display layer.

### What fonts should I use for RTL languages?

Use the Noto font family (Google Noto Sans Arabic, Noto Sans Hebrew) as a reliable cross-platform choice. Specify RTL fonts first in your CSS font stack with LTR fonts as fallback. Ensure the font supports all diacritical marks — Arabic text without proper tashkeel rendering looks broken to native speakers.

---

#RTLSupport #BidirectionalText #ArabicUI #AIInterfaces #Accessibility #AgenticAI #LearnAI #AIEngineering

---

Source: https://callsphere.ai/blog/building-rtl-compatible-agent-interfaces-arabic-hebrew-persian
