OfficeCLI i18n and RTL Support in Word Documents: A Technical Deep Dive

OfficeCLI provides built-in, first-class internationalization (i18n) and right-to-left (RTL) language support for Word documents through a multi-layered property cascade that operates independently of Microsoft Word.

OfficeCLI enables AI agents and automation scripts to create, modify, and query Word documents in any language or script. Unlike simple wrappers around COM objects, the iOfficeAI/OfficeCLI repository implements native RTL handling directly within its core engine, ensuring reliable document generation for Arabic, Hebrew, CJK, and other complex scripts.

How OfficeCLI Implements i18n and RTL Support

The architecture follows a three-layer model (Read → DOM → Raw XML) with RTL logic integrated at the DOM layer. This ensures that both high-level view commands and low-level raw-set operations see consistent direction information across the entire document tree.

Document-Wide Defaults

Global RTL settings and language tags are stored in the document's default properties. In WordHandler.Set.DocDefaults.cs, the engine reads and writes <w:bidi> elements within the docDefaults section, handling properties such as docdefaults.rtl and docdefaults.direction.

When you create a document with a locale flag, the CLI automatically sets these defaults. For example, using --locale ar-SA triggers the logic at line 116 in WordHandler.Set.DocDefaults.cs to enable RTL document-wide and insert the appropriate BCP-47 language tag.

Style and Paragraph-Level Cascade

Direction inheritance flows from document defaults through sections, tables, and styles down to individual paragraphs. The ResolveEffectiveParagraphStyleProperties method in WordHandler.StyleList.cs (lines 885-938) merges <w:bidi> values from style paragraph properties, table cells, section defaults, and numbering levels.

This method emits both effective.direction and effective.rtl for downstream consumers, ensuring that any element inheriting from a style receives the correct text direction. The WordHandler.StyleList.cs file (lines 98-106) specifically lifts <w:bidi> attributes from style definitions into an effective run-level RightToLeftText flag.

Run-Level Property Resolution

Individual runs may explicitly set RTL via <w:rtl> elements or inherit direction from their parent paragraph. The ResolveEffectiveRunPropertiesCore method in WordHandler.StyleList.cs (lines 77-88) merges run properties, table-style runs, and paragraph bidi attributes into a single RightToLeftText object.

The method records provenance information in effective.rtl, allowing you to trace whether a run's direction was set explicitly, inherited from a style, or derived from document defaults.

Selector and Query Support

The CLI exposes RTL state through selectors for querying and filtering. In WordHandler.Selector.cs (lines 207-214), the rtl key maps to the run's RightToLeftText value, enabling queries such as paragraph[rtl=true] or run[rtl=true].

The set command accepts multiple value formats for RTL properties, including rtl, righttoleft, true, or 1, parsed through WordHandler.Set.cs.

Language Tags and Per-Script Font Slots

OfficeCLI respects OpenXML's complex script handling through dedicated font slots and language attributes:

  • Per-script font slots: font.eastAsia for CJK scripts, font.cs for complex scripts (Arabic/Hebrew), font.hAnsi for high ANSI, and font.ascii for basic Latin.
  • BCP-47 language tags: Exposed as lang.latin, lang.ea (East Asian), and lang.cs (complex script).
  • RTL-specific layout features: Including rtlGutter, pgBorders, and locale-aware page numbering.

The MergeRunProperties method in WordHandler.StyleList.cs (lines 93-106) handles the inheritance and merging rules for these per-slot properties, ensuring that mixed-script documents render correctly.

Practical Code Examples

Create an RTL Document Automatically

officecli create report.docx --locale ar-SA

This command writes the appropriate BCP-47 tag and sets docDefaults.rtl=true automatically through the document defaults handler.

Set Paragraph Direction Manually

officecli set report.docx '/body/p[1]' --prop direction=rtl

This updates <w:pPr><w:bidi/> on the target paragraph. The effective run-level RTL flag is emitted for all child runs through the cascade logic in StyleList.cs.

Verify Effective Direction on a Run

officecli get report.docx '/body/p[1]/r[1]' --json

Expected output:

{
  "tag": "run",
  "path": "/body/p[1]/r[1]",
  "attributes": {
    "text": "مرحبا",
    "effective.rtl": true,
    "effective.direction": "rtl"
  }
}

Query All RTL Runs in a Document

officecli query report.docx "run[rtl=true]" --json

This returns every run whose effective RTL flag is true, regardless of whether the direction originated from a style, section, table, or direct formatting.

Apply Per-Script Font Slots

officecli set report.docx '/body/p[2]/r[1]' \
  --prop font.eastAsia=宋体 \
  --prop font.cs=Times New Roman \
  --prop font.ascii=Calibri

The merge respects the per-slot inheritance rules implemented in MergeRunProperties.

Summary

  • OfficeCLI implements native i18n and RTL support through WordHandler.Set.DocDefaults.cs and WordHandler.StyleList.cs, not through external Word automation.
  • Direction cascades from docDefaults → section → table → style → paragraph → run, tracked via effective.rtl and effective.direction properties.
  • Selectors and commands support RTL queries using rtl=true and accept multiple value formats (rtl, righttoleft, 1) for setting direction.
  • Per-script font handling uses dedicated slots (eastAsia, cs, hAnsi, ascii) with BCP-47 language tags exposed as lang.latin, lang.ea, and lang.cs.
  • Provenance tracking in ResolveEffectiveRunPropertiesCore allows tracing whether RTL state came from explicit formatting, styles, or document defaults.

Frequently Asked Questions

How does OfficeCLI handle mixed-direction documents containing both LTR and RTL text?

OfficeCLI resolves effective direction at the run level using ResolveEffectiveRunPropertiesCore in WordHandler.StyleList.cs. Each run maintains its own RightToLeftText property, allowing individual Arabic or Hebrew runs to display RTL within English paragraphs, or vice versa. The selector run[rtl=true] filters for specifically RTL content regardless of the surrounding paragraph direction.

Can OfficeCLI set document-wide RTL defaults without modifying every paragraph?

Yes. Use the create command with --locale (e.g., ar-SA or he-IL) or manually set docdefaults.rtl=true via the set command targeting document defaults. In WordHandler.Set.DocDefaults.cs, this writes the <w:bidi> element to the document's docDefaults section, causing all content to inherit RTL direction unless explicitly overridden at the paragraph or run level.

What BCP-47 language tags does OfficeCLI support for font selection?

OfficeCLI supports the full range of BCP-47 tags exposed through OpenXML's language properties: lang.latin for Western scripts, lang.ea (East Asian) for CJK languages, and lang.cs (complex script) for Arabic, Hebrew, Thai, and similar scripts. These map to corresponding font slots (font.ascii, font.eastAsia, font.cs) that are merged according to the rules in MergeRunProperties within WordHandler.StyleList.cs.

How do I query only the paragraphs that contain RTL text in a large document?

Use the query command with the RTL selector: officecli query document.docx "paragraph[rtl=true]" --json. This leverages the selector logic in WordHandler.Selector.cs (lines 207-214), which evaluates the effective RTL flag computed through the style cascade. This flags paragraphs where the resolved direction is RTL, regardless of whether it was set via direct formatting, style inheritance, or document defaults.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →