Osy#betaRun it on Osyrin: osyrin.com

Reference / I18n

Right to left — an app that reads in Arabic with no change to the app

pl: · mr: · left: · textAlign: TextAlign.Right · flowDirection: FlowDirection.LeftToRight · mirror: Mirror.InRightToLeft · <svg data-mirror="rtl">

A language written right to left (Arabic, Hebrew, Persian, Urdu) mirrors the whole page: every horizontal side a style prop names is a START or END side, a slide moves towards the end, an arrow points onwards, and data inside Arabic text keeps its own order. Nothing in the app is written for it. `FlowDirection.LeftToRight` keeps the geometry of the one element that must not mirror; `Mirror.InRightToLeft` turns a photograph composed for one side.

preview3 examples compiled by CIi18nlanguagertlui

Summary#

When the reader's language is written right to left — an app that has locales/ar.osy and an Arabic reader — the page is <html dir="rtl"> and the whole layout mirrors: the navigation that starts the line at the left in English starts it at the right, a drawer that slides in from its edge slides in from the other one, a "next" chevron points left. The language's direction is the culture data's own fact, so locales/he.osy or locales/fa.osy works the same with nothing added. An app does not change; it was already written in starts and ends.

Signature#

Box(pl: 4, mr: 2, borderLW: 1, left: 0);           // START and END sides — the left and right of an English page
Text(price, textAlign: TextAlign.Right);              // the END of the line: the left of an Arabic one
Box(translateX: "-100%");                              // towards the start: mirrored in a right-to-left page
Box(flowDirection: FlowDirection.LeftToRight) { … }    // THE ESCAPE: this element keeps left-to-right geometry
Image(picture: Textures.Bottle, mirror: Mirror.InRightToLeft);   // a photograph composed for one side turns with the page
// icons/next.svg:  <svg data-mirror="rtl" viewBox="0 0 24 24">…</svg>   — an icon that points ONWARDS

Description#

Every horizontal side is a start or an end side. Pl/Ml/BorderLW/Left are the side a line starts on and Pr/Mr/BorderRW/Right the side it ends on — exactly as WPF's Thickness.Left is under FlowDirection. They lower to CSS's logical properties (padding-inline-start, margin-inline-end, border-inline-start-width, inset-inline-start), Px/Mx/ScrollPaddingX to the -inline pair, and TextAlign.Left/Right to start/end. In an English page that is the left and right they always meant; in an Arabic one the layout mirrors. Rows and grids already follow the direction, so a Row reads right to left by itself.

A slide mirrors. TranslateX is multiplied by the page's mirror factor, so a drawer that slides in from the start edge, a line that leaves towards the end, an entrance that rises from the side all move the other way in Arabic — in an animation as well as on an element. TranslateY, Rotate and Scale do not change.

The one escape: FlowDirection.LeftToRight. Some geometry must not mirror: a chart's plot drawn in left-to-right coordinates, a timeline, a phone number, a code sample, a map. Give the element that holds it flowDirection: FlowDirection.LeftToRight (WPF's FlowDirection, spelled the same) — inside it, start is the left again, slides and directional icons un-mirror, and its text is isolated from the Arabic around it. The chart kit does exactly this around its plot; its title and legend follow the page. FlowDirection.RightToLeft is the other way round, for a right-to-left island in a left-to-right page.

A photograph composed for one side. A full-bleed photograph whose empty half carries the words set on it (a bottle at the right, the list in the dark at its left) must turn when the words move to the other side, or in Arabic they land on the bottle. mirror: Mirror.InRightToLeft draws it mirrored in a right-to-left page. A photograph with writing in it, a face, a product or a logo stays as it is (Mirror.Never, the default).

An icon that points somewhere. An icon file whose root says data-mirror="rtl" is drawn mirrored wherever it is used in a right-to-left page (and unmirrored inside a FlowDirection.LeftToRight element) — see icons. The platform's arrows, chevrons, send, sign-in/sign-out, cart, external-link and message glyphs carry it; play, clock, check and brand marks do not.

Data keeps its own order. Inside Arabic text a price, an order number, an email address or an English product name must still read left to right — unprotected, "120,00 kr" reads "kr 120,00" and "#1042" reads "1042#". So in a right-to-left page: every hole of a translated sentence is an isolate (U+2068 … U+2069, <bdi> as text), a symbol written against a hole (#{number}) is inside it, a text that shows only data is isolated whole, and a numbered reference inside Arabic text is one token. The app's own translated words are never wrapped. What a reader TYPES takes its direction from what they type (an email field runs left to right). A test names text as the reader sees it — the isolates are invisible to Assert.Visible and Ui.Click.

Dates and money in Arabic. An Arabic page formats its numbers, prices and dates in ar-AE, a culture of the platform's default pack: Arabic month and day names, the Gregorian calendar, and Latin digits — 2 أكتوبر 2026, 9:12 ص, 1,234.50. The browser is handed the server's own plan for it rather than asking the browser's Intl, so a date the server wrote (a mail, a server render) and one the browser wrote read alike, digit for digit. A price the app writes with its own symbol ("560 kr") keeps that symbol; only the separators come from the culture.

A heading face for Arabic. A Latin display face rarely carries Arabic, and a heading in Arabic then falls back to whatever the reader's system has — a different voice from the rest of the design. Name an Arabic face after the Latin one in the theme's font token, ship both with osy font add, and each heading takes the face of its script, glyph by glyph: Display = "\"Newsreader\", \"Amiri\", ui-serif, serif"; keeps a Latin product name inside an Arabic heading in Newsreader and the Arabic words in Amiri.

A mail is mirrored too. A mail written in Arabic (Which language a reader gets — and switching it — its language =>) is a right-to-left document: dir="rtl", its start and end sides on the right and left, a receipt's line on the right and its amount on the left, each line ordering its own data. Mail clients read physical sides far more reliably than logical CSS, so the mail renderer writes them physically for the mail's language.

Examples#

[Composable]
[AllowAnonymous]
component ReceiptLine(string item, string price) {
  render {
    Row(gap: 3, justify: Justify.Between, pl: 4, pr: 2, borderLW: 2, border: Colors.Border) {
      Text(item);
      Text(price, textAlign: TextAlign.Right, fontVariant: FontVariant.TabularNums);
    }
  }
}
[Composable]
[AllowAnonymous]
component Sparkline(double[] points, string caption) {
  render {
    Stack(gap: 2) {
      Text(caption);
      Box(flowDirection: FlowDirection.LeftToRight, h: 40, position: Position.Relative) {
        foreach (var i in Enumerable.Range(0, points.Length)) {
          Box(position: Position.Absolute, left: $"{i * 100 / points.Length}%", bottom: "0", w: 4, h: $"{points[i]}%",
              bg: Colors.Primary);
        }
      }
    }
  }
}
animation SlideIn { Duration = "240ms"; from { TranslateX = "-100%"; } to { TranslateX = "0"; } }

[Composable]
[AllowAnonymous]
component Panel(bool open) {
  render {
    if (open) {
      Stack(position: Position.Fixed, top: 0, bottom: 0, left: 0, w: "320px", bg: Colors.Surface, animation: SlideIn) {
        Box(h: "200px", bgImage: "linear-gradient(#222, #555)", mirror: Mirror.InRightToLeft);
        Text("Your bag");
      }
    }
  }
}

See also#

Related

Which language a reader gets — and switching it

A reader's language is, in order: their explicit choice (a `/sv/` address prefix or a language switch), their own…

A language's words — locales/<language>.osy

A project's translations are checked-in source: one `locales/<language>.osy` per language, a `translations sv { }`…

style props

Inside a `variants` block, each `Name = value` is a style prop from a fixed vocabulary the renderer maps to CSS — paint…

icons

Drop `.svg` files into `model/icons/` and render them with `Icon(Icons.Search)`. The name is checked at compile time…

animation — keyframe motion, on entry, on exit, in a loop, or as it scrolls into view

An `animation` block declares reusable keyframe motion — an entrance, a shimmer, a pulse, a flash that decays. Its…

[Email] — mail rendered from data

An `[Email]` component is a mail template. It is rendered on the server — from any function, a workflow step included —…