<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hatem Gheta</title>
    <description>The latest articles on DEV Community by Hatem Gheta (@hatemgheta).</description>
    <link>https://dev.to/hatemgheta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4232319%2Fcc96b976-a1c5-4995-b363-e205e527345a.jpeg</url>
      <title>DEV Community: Hatem Gheta</title>
      <link>https://dev.to/hatemgheta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hatemgheta"/>
    <language>en</language>
    <item>
      <title>Bilingual SEO for an Arabic-first website: what actually mattered</title>
      <dc:creator>Hatem Gheta</dc:creator>
      <pubDate>Sat, 07 Nov 2026 15:05:20 +0000</pubDate>
      <link>https://dev.to/hatemgheta/bilingual-seo-for-an-arabic-first-website-what-actually-mattered-39nj</link>
      <guid>https://dev.to/hatemgheta/bilingual-seo-for-an-arabic-first-website-what-actually-mattered-39nj</guid>
      <description>&lt;p&gt;I recently shipped a bilingual (Arabic + English) profile page on our company site, &lt;a href="https://basmaos.com/en/hatem-gheta" rel="noopener noreferrer"&gt;basmaos.com&lt;/a&gt;. Arabic is the default language, English lives under &lt;code&gt;/en&lt;/code&gt;, and the whole site is a small PHP app with no framework.&lt;/p&gt;

&lt;p&gt;Most bilingual SEO guides assume English first and treat right-to-left as an afterthought. Here is what actually mattered when Arabic comes first, including two caching surprises I didn't expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pick one URL scheme and keep it
&lt;/h2&gt;

&lt;p&gt;Our site already served Arabic at the root and English under &lt;code&gt;/en&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://basmaos.com/hatem-gheta      ← Arabic (default)
https://basmaos.com/en/hatem-gheta   ← English
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was tempting to add &lt;code&gt;/ar/...&lt;/code&gt; for symmetry. I didn't, because two URLs for the same Arabic page means duplicate content and split signals. Instead, &lt;code&gt;/ar/...&lt;/code&gt; is a single permanent redirect to the root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'/ar'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;str_starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/ar/'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$target&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$target&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rtrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'?'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;People still guess &lt;code&gt;/ar/&lt;/code&gt; when they share links, so the redirect catches them in one hop, with no chains and no loops. Trailing slashes get the same treatment: one &lt;code&gt;301&lt;/code&gt; to the canonical form.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. hreflang: every page lists every language, including itself
&lt;/h2&gt;

&lt;p&gt;Each language version carries the full set, plus &lt;code&gt;x-default&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://basmaos.com/en/hatem-gheta"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"ar"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://basmaos.com/hatem-gheta"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://basmaos.com/en/hatem-gheta"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"x-default"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://basmaos.com/hatem-gheta"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules I check in tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The canonical points at itself.&lt;/strong&gt; Never point the English page's canonical at the Arabic page; that tells Google to drop the English one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The links are reciprocal.&lt;/strong&gt; If the Arabic page lists the English one, the English page must list the Arabic one back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;x-default&lt;/code&gt; is the Arabic version&lt;/strong&gt;, because that is what someone with no language preference should land on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sitemap repeats the same alternates with &lt;code&gt;xhtml:link&lt;/code&gt;, so Google sees one consistent story everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. RTL is more than &lt;code&gt;dir="rtl"&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ar"&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"rtl"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That attribute is the easy part. The issues show up in the details:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use logical CSS properties.&lt;/strong&gt; &lt;code&gt;margin-left&lt;/code&gt; is wrong in one of your two languages. &lt;code&gt;margin-inline-start&lt;/code&gt;, &lt;code&gt;padding-inline-start&lt;/code&gt; and &lt;code&gt;inset-inline-end&lt;/code&gt; flip automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.product-points&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;padding-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.product-points&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;inset-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Isolate mixed-direction text.&lt;/strong&gt; An Arabic page that shows an English name, or an English page that shows an Arabic one, needs both &lt;code&gt;lang&lt;/code&gt; and &lt;code&gt;dir&lt;/code&gt; on that element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
  Hatem Gheta
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ar"&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"rtl"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;حاتم غيته&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that, punctuation and brand names such as "BASMA OS" end up in the wrong place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch alignment inside grids.&lt;/strong&gt; My first version put the Arabic name at the far edge of the English hero, because an RTL span inside an LTR grid aligned to its own start. &lt;code&gt;justify-self: start&lt;/code&gt; fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arrows and icons need flipping.&lt;/strong&gt; An arrow that means "go" points right in English and left in Arabic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.arrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;scaleX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Structured data: one person, two pages
&lt;/h2&gt;

&lt;p&gt;The page describes a real person, so it uses &lt;code&gt;ProfilePage&lt;/code&gt; with a &lt;code&gt;Person&lt;/code&gt; as &lt;code&gt;mainEntity&lt;/code&gt;. The important decision was the &lt;code&gt;@id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://basmaos.com/hatem-gheta#person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hatem Gheta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alternateName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"حاتم غيته"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://basmaos.com/hatem-gheta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"worksFor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://basmaos.com/#organization"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://www.linkedin.com/in/hatemgheta/"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The same &lt;code&gt;@id&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt; on both language pages.&lt;/strong&gt; It is one person, not two. My first version set &lt;code&gt;url&lt;/code&gt; to the current language's page, which quietly told search engines there were two different &lt;code&gt;url&lt;/code&gt;s for the same entity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The name in the other script goes in &lt;code&gt;alternateName&lt;/code&gt;.&lt;/strong&gt; On the Arabic page the order is reversed: Arabic &lt;code&gt;name&lt;/code&gt;, English &lt;code&gt;alternateName&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sameAs&lt;/code&gt; lists only profiles that are definitely mine.&lt;/strong&gt; It is tempting to add every link you have. Don't: an unrelated or closed profile there does more harm than good.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything in the JSON-LD is also visible on the page.&lt;/strong&gt; Structured data that says things the page doesn't is a fast way to lose trust.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Accessibility caught a brand-color bug
&lt;/h2&gt;

&lt;p&gt;Running axe-core on both languages flagged our brand gold, &lt;code&gt;#b8913f&lt;/code&gt;, as text on a light background: a contrast ratio of 2.7:1, well below the 4.5:1 that WCAG AA requires. It was already used across the whole site, not just the new page.&lt;/p&gt;

&lt;p&gt;The fix was a separate token used only for text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--gold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;#b8913f&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;       &lt;span class="c"&gt;/* buttons, borders, lines */&lt;/span&gt;
&lt;span class="nt"&gt;--gold-text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;#86671&lt;/span&gt;&lt;span class="nt"&gt;f&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* text on light backgrounds: 4.85:1 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buttons and decoration kept the original color; only text changed. Visually you barely notice, and the audit went from failing to 0 violations.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The surprise: LinkedIn caches preview images by URL
&lt;/h2&gt;

&lt;p&gt;After the launch I changed the title on the page and regenerated the Open Graph image. The page updated. LinkedIn's Post Inspector picked up the new title… and kept showing the &lt;strong&gt;old image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Debugging this took a detour, because the image hash from the live URL didn't match the file on disk either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file on server:  4666015a…  (79,405 bytes)
served via CDN:  fd7df16f…  (78,080 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CDN header said &lt;code&gt;cache-status: MISS&lt;/code&gt;, and a cache-busting query string returned the same bytes. So it wasn't serving a stale copy: the CDN re-compresses images on the fly. That explained the hash mismatch, but not LinkedIn.&lt;/p&gt;

&lt;p&gt;The real cause: &lt;strong&gt;LinkedIn caches the preview image by its URL&lt;/strong&gt;, and re-scraping the page doesn't necessarily re-fetch an image whose URL hasn't changed. The fix was simply a new file name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- &amp;lt;meta property="og:image" content="https://basmaos.com/basmaos-assets/hatem-gheta-og.jpg"&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ &amp;lt;meta property="og:image" content="https://basmaos.com/basmaos-assets/hatem-gheta-og-2.jpg"&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One more inspection and the new card appeared. Lesson: &lt;strong&gt;version your OG image file names&lt;/strong&gt;, the same way you version CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Arabic text in images needs real shaping
&lt;/h2&gt;

&lt;p&gt;The Open Graph card has the name in both scripts. Many image libraries draw Arabic letters disconnected and left-to-right. I rendered the card as HTML in headless Chromium and took a screenshot, which gives correct Arabic shaping for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Test the boring parts
&lt;/h2&gt;

&lt;p&gt;The whole site is checked by a plain PHP test script that calls the router directly, with no web server. For this page it verifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; for both languages; &lt;code&gt;301&lt;/code&gt; for &lt;code&gt;/ar/...&lt;/code&gt; and trailing slashes&lt;/li&gt;
&lt;li&gt;one &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, one &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, one meta description per page&lt;/li&gt;
&lt;li&gt;a self-referencing canonical and the complete hreflang set&lt;/li&gt;
&lt;li&gt;the page appears in the sitemap with alternates&lt;/li&gt;
&lt;li&gt;the JSON-LD parses, the Person &lt;code&gt;@id&lt;/code&gt; is identical in both languages, and &lt;code&gt;sameAs&lt;/code&gt; contains only the expected profile&lt;/li&gt;
&lt;li&gt;no &lt;code&gt;noindex&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of it is clever, but each check is something I have seen go wrong on a real site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;Deployed, crawlable, in the sitemap, and submitted through Search Console. Indexing is up to Google, and I'll update this post once both pages are indexed.&lt;/p&gt;

&lt;p&gt;If you are building for Arabic, Hebrew, Persian or Urdu audiences, I'd like to hear what tripped you up. The RTL details are where I learned the most.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>i18n</category>
      <category>php</category>
    </item>
  </channel>
</rss>
