Skip to content
Learn Agentic AI10 min read0 views

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.

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.

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.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

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.

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.

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

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.