Skip to content

How It Works

mmaher88 edited this page Mar 9, 2026 · 1 revision

How It Works

Architecture Overview

Laren is structured as a 4-layer stack. Only the outermost layer (Engine) depends on Fcitx5 — everything else is a standalone C++ library.

graph TB
    subgraph "Fcitx5 Process"
        E["🔌 Engine Layer<br/><i>laren_engine + laren_state</i><br/>Fcitx5 addon, key events, UI"]
    end

    subgraph "Laren Core (standalone C++)"
        C["⚙️ Core Layer<br/><i>transliterator + rule_engine + ranker</i><br/>Transliteration pipeline"]
        D["📖 Dictionary Layer<br/><i>dictionary + trie</i><br/>375k Arabic words"]
        U["🔧 Util Layer<br/><i>unicode + mmap_file</i><br/>UTF-8/UTF-32, memory mapping"]
    end

    E --> C
    C --> D
    D --> U

    style E fill:#fff3e0,stroke:#e65100
    style C fill:#e3f2fd,stroke:#1565c0
    style D fill:#e8f5e9,stroke:#2e7d32
    style U fill:#f3e5f5,stroke:#6a1b9a
Loading

Transliteration Pipeline

When you type a character, it flows through this pipeline:

flowchart TD
    A["⌨️ Key Press"] --> B["Buffer: 'salam'"]
    B --> C["1. Normalize<br/><i>lowercase, collapse repeats</i>"]
    C --> D["2. DFS Expand<br/><i>generate all possible Arabic forms</i>"]
    D --> E{"3. Dictionary Filter"}
    E -->|"Match found"| F["📖 Dictionary Candidates<br/><i>ranked by frequency</i>"]
    E -->|"No match"| G["🔤 Skeletal Fallback<br/><i>top expansions as-is</i>"]
    F --> H["4. Rank & Sort"]
    G --> H
    H --> I["5. History Boost<br/><i>user's preferred choice → top</i>"]
    I --> J["📋 Candidate List<br/><i>up to 50 results, paginated</i>"]

    style A fill:#e1f5fe
    style J fill:#c8e6c9
    style F fill:#fff9c4
    style G fill:#ffecb3
Loading

DFS Expansion — The Heart of Transliteration

The Rule Engine uses depth-first search to generate every possible Arabic interpretation of the input. Each Latin character can map to multiple Arabic letters.

Example: "salam"

graph TD
    S["s"] -->|"س (seen)"| A1["a"]
    S -->|"ص (sad)"| A2["a"]

    A1 -->|"SKIP<br/><i>short vowel</i>"| L1["l → ل"]
    A1 -->|"ا (alef)"| L2["l → ل"]

    A2 -->|"SKIP"| L3["l → ل"]
    A2 -->|"ا (alef)"| L4["l → ل"]

    L1 --> A3["a → SKIP"] --> M1["m → م"]
    L2 --> A4["a → SKIP"] --> M2["m → م"]

    M1 --> R1["سلم"]
    M2 --> R2["سالم"]

    style R1 fill:#c8e6c9
    style R2 fill:#c8e6c9
    style S fill:#e1f5fe
Loading

The tree branches at every character. With max_expansions = 4096, Laren explores thousands of paths, then filters through the dictionary to find real Arabic words.

Short Vowel SKIP

Arabic script doesn't write short vowels. The SKIP sentinel (value 0) represents this:

"salam" with SKIP vowels → سلام (correct Arabic)
"salam" with explicit alef → سالاام (over-vowelized, not a real word)
graph LR
    subgraph "SKIP path (correct)"
        A1["s→س"] --> A2["a→∅"] --> A3["l→ل"] --> A4["a→ا"] --> A5["m→م"]
        A5 --> R1["سلام ✅"]
    end

    subgraph "Explicit path (wrong)"
        B1["s→س"] --> B2["a→ا"] --> B3["l→ل"] --> B4["a→ا"] --> B5["m→م"]
        B5 --> R2["سالام ❌"]
    end

    style R1 fill:#c8e6c9
    style R2 fill:#ffcdd2
Loading

Fcitx5 Integration

sequenceDiagram
    participant App as Application
    participant KWin as KWin / Compositor
    participant Fcitx as Fcitx5
    participant Laren as Laren Engine

    App->>KWin: Key press event
    KWin->>Fcitx: Forward to input method
    Fcitx->>Laren: processKeyEvent()

    alt Printable character
        Laren->>Laren: Append to buffer
        Laren->>Laren: transliterate(buffer)
        Laren-->>Fcitx: Update candidate list
        Fcitx-->>App: Show preedit + candidates
    else Space / Enter / Number
        Laren->>Laren: Commit selected candidate
        Laren->>Laren: Save to history
        Laren-->>Fcitx: commitString(arabic + " ")
        Fcitx-->>App: Insert Arabic text
    else Escape
        Laren->>Laren: Clear buffer
        Laren-->>Fcitx: Reset input panel
    end
Loading

Module Dependencies

graph LR
    subgraph "No external deps"
        util["util/<br/>unicode.cpp<br/>mmap_file.cpp"]
    end

    subgraph "No external deps"
        dict["dict/<br/>dictionary.cpp<br/>trie.cpp"]
    end

    subgraph "No external deps"
        core["core/<br/>transliterator.cpp<br/>rule_engine.cpp<br/>ranker.cpp"]
    end

    subgraph "Fcitx5 dependency"
        engine["engine/<br/>laren_engine.cpp<br/>laren_state.cpp"]
    end

    engine --> core --> dict --> util

    style engine fill:#fff3e0
    style core fill:#e3f2fd
    style dict fill:#e8f5e9
    style util fill:#f3e5f5
Loading

Only engine/ links against Fcitx5. The core transliteration logic can be used independently.

Clone this wiki locally