goall.ai
web-developmenti18nseortlfarsi

Building Multilingual Websites That Don't Cut Corners: RTL, Persian Fonts, and SEO

goall.ai Team·۴ خرداد ۱۴۰۴·5 دقیقه مطالعه

Most "multilingual" websites are just translated text in a single layout. You can tell within seconds — the typography feels wrong for one language, the layout breaks on RTL text, and the SEO is optimized for exactly one market. We built goall.ai differently, and this post covers how.

Why Multilingual Matters for a B2B Consulting Site

goall.ai serves clients in three linguistic markets: English-speaking (primarily US/EU), Persian-speaking (Iran, Afghanistan, diaspora globally), and German-speaking (DACH region). These aren't markets we aspire to serve — they're markets where our team already has relationships.

Building a site that actually works across all three required decisions at every layer: routing, fonts, layout, and structured data.

The Routing Architecture

We use next-intl with prefix-based routing: every page lives under /en/, /fa/, or /de/. This has a few SEO advantages over subdomain or cookie-based locale detection:

  • Search engines index each locale as a distinct URL
  • hreflang alternates are clean and explicit
  • No JavaScript required to determine the correct language — it's in the URL

Each page generates proper hreflang alternates in its metadata:

alternates: {
  canonical: `https://goall.ai/${locale}/showcase`,
  languages: {
    en: 'https://goall.ai/en/showcase',
    fa: 'https://goall.ai/fa/showcase',
    de: 'https://goall.ai/de/showcase',
  },
}

This tells Google: "these three URLs are the same content in different languages — don't treat them as duplicates."

Handling Right-to-Left (RTL) for Farsi

Persian (Farsi) is written right-to-left. Getting RTL right is more than just dir="rtl" on the <html> element — though that's the starting point.

The dir attribute does the heavy lifting

Setting dir="rtl" on <html> causes browsers to mirror the document direction. Text flows right-to-left, block elements stack correctly, and inline elements reverse. Most of the layout "just works" — but there are gotchas.

Use logical CSS properties, not directional ones

The biggest RTL pitfall in Tailwind CSS is using directional classes like ml-4, pr-6, or text-left. These don't mirror — ml-4 is still a left margin in RTL.

Use logical property equivalents instead:

| Directional | Logical | Meaning | |---|---|---| | ml-* | ms-* | margin-inline-start | | mr-* | me-* | margin-inline-end | | pl-* | ps-* | padding-inline-start | | text-left | text-start | start-aligned text |

We used ms-, me-, ps-, pe-, and text-start/text-end throughout. In LTR (English, German), these behave identically to their directional counterparts. In RTL (Farsi), they automatically mirror.

Choosing a Font for Persian Text

This is the decision most multilingual sites get wrong. They pick a Latin font, assume Unicode fallback handles Persian characters, and call it done. The result: Persian text rendered in a generic system font that looks completely out of place.

Why Latin fonts don't work for Persian

Fonts like Geist, Inter, or Roboto only include Latin glyphs. When the browser encounters Persian characters, it falls back to the OS system font — typically Arial, Times New Roman, or whatever the user has installed. The result is inconsistent rendering and poor readability.

What we use: Noto Sans Arabic

We load Noto Sans Arabic from Google Fonts via next/font/google — but only for the Farsi locale. This is critical: there's no reason to add the font overhead for English or German visitors.

const notoSansArabic = Noto_Sans_Arabic({
  subsets: ['arabic'],
  weight: ['400', '500', '600', '700'],
  variable: '--font-fa-sans',
  display: 'swap',
  preload: false, // only needed when lang=fa
});

The font variable is only added to <html> when locale === 'fa':

<html
  lang={locale}
  dir={dir}
  className={`${GeistSans.variable} ${isFa ? notoSansArabic.variable : ''}`}
>

And in CSS, we override the font stack specifically for the Persian locale:

html[lang='fa'] body,
html[lang='fa'] h1,
html[lang='fa'] p {
  font-family: var(--font-fa-sans), 'Vazirmatn', sans-serif;
}

English and German visitors load zero additional font bytes. Farsi visitors get a properly rendered, purpose-built Persian font.

Why Noto Sans Arabic over Yekan or IranSans? Yekan and IranSans are excellent fonts with high adoption on Iranian sites. For this project, Noto Sans Arabic offers the clearest setup path (available directly in next/font/google) and near-identical visual quality for screen reading. For client projects where brand fidelity demands a specific Persian font, we self-host using next/font/local.

Structured Data for International SEO

hreflang handles the "which page for which language" signal. JSON-LD handles the "what is this site about" signal. Both matter, and most sites only do one.

We added two global schemas to every page:

Organization schema

{
  "@type": "Organization",
  "name": "goall.ai",
  "url": "https://goall.ai",
  "contactPoint": {
    "contactType": "customer service",
    "url": "https://goall.ai/en/contact",
    "availableLanguage": ["English", "Persian", "German"]
  }
}

The availableLanguage field is a direct signal to search engines that this organization operates in multiple languages — useful for local and international knowledge panel generation.

WebSite schema with inLanguage

{
  "@type": "WebSite",
  "url": "https://goall.ai",
  "inLanguage": ["en", "fa", "de"]
}

On pages with list content (like the Showcase), we add an ItemList schema that enumerates the projects — this can generate rich results in Google Search.

What We'd Do Differently at Scale

If you're building a multilingual site serving more than three languages, a few things change:

  1. Font strategy becomes more complex. You might need separate font loading for Arabic script, CJK characters, Cyrillic, etc. Budget for the performance impact.
  2. Translation management needs tooling. We manage three JSON files manually. Past four or five languages, use a proper i18n platform (Localazy, Phrase, Crowdin).
  3. SEO monitoring needs to be per-locale. Search Console treats different language versions as separate properties. Set up monitoring for each.

The Bottom Line

Multilingual web development isn't a checkbox. It's a series of deliberate choices about routing, typography, layout logic, and structured data. Done well, it opens markets. Done poorly, it creates a site that works for one audience and awkwardly tolerates the rest.

If you're building a product that serves multiple language communities, we'd love to talk about the architecture before you write a line of code.

Book a free discovery call →

می‌خواهید این را برای استارتاپ خود اعمال کنید؟

رزرو تماس اکتشافی

مقالات مرتبط

Building Multilingual Websites That Don't Cut Corners: RTL, Persian Fonts, and SEO | goall.ai