<?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: Abo-Elmakarem Shohoud</title>
    <description>The latest articles on DEV Community by Abo-Elmakarem Shohoud (@karem505).</description>
    <link>https://dev.to/karem505</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3931967%2Fbeb67e16-773c-49f0-af6c-e64b9ddc7fc2.jpeg</url>
      <title>DEV Community: Abo-Elmakarem Shohoud</title>
      <link>https://dev.to/karem505</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karem505"/>
    <language>en</language>
    <item>
      <title>A Realistic Digital Transformation Roadmap for SMEs in Egypt and the Gulf</title>
      <dc:creator>Abo-Elmakarem Shohoud</dc:creator>
      <pubDate>Mon, 01 Jun 2026 13:25:51 +0000</pubDate>
      <link>https://dev.to/karem505/a-realistic-digital-transformation-roadmap-for-smes-in-egypt-and-the-gulf-1o17</link>
      <guid>https://dev.to/karem505/a-realistic-digital-transformation-roadmap-for-smes-in-egypt-and-the-gulf-1o17</guid>
      <description>&lt;p&gt;A realistic digital transformation for an SME in Egypt or the Gulf does not start with buying a big system. It starts by mapping how your business actually works, picking one painful manual process, automating it end to end, proving the gain, then expanding in phases. Transformation is about redesigning how work flows — not just moving spreadsheets online. Here is the phased roadmap I use with small and mid-size companies across Egypt, the UAE, and Saudi Arabia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Digitization vs Digital Transformation
&lt;/h2&gt;

&lt;p&gt;These get confused constantly, and the difference decides whether you waste money.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Digitization&lt;/strong&gt; converts existing manual steps to digital ones — scanning paper, moving a process into a spreadsheet or a basic form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digital transformation&lt;/strong&gt; rethinks the workflow itself so work moves faster, errors drop, and you get real-time visibility — usually replacing scattered spreadsheets and disconnected tools with one connected system, and adding AI where it genuinely helps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only digitize, you've made the same slow process digital. Transformation is the part that pays off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1 — Map How the Work Really Happens
&lt;/h2&gt;

&lt;p&gt;Before any tool or budget, document reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How each core process actually runs today — who does what, in which tool, with what hand-offs.&lt;/li&gt;
&lt;li&gt;Where work waits, gets re-typed, or breaks (the classic spreadsheet-emailed-back-and-forth pattern).&lt;/li&gt;
&lt;li&gt;Which steps are repetitive, error-prone, and time-consuming.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This diagnostic is a small, fixed engagement on its own — and it's the single most valuable step, because it tells you exactly where automation will pay off and where it won't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2 — Pick One High-Value Process First
&lt;/h2&gt;

&lt;p&gt;Do not try to transform everything at once. That's how SME projects stall. Choose &lt;strong&gt;one&lt;/strong&gt; process that is painful, repetitive, and measurable. Common first wins for Gulf and Egyptian SMEs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replacing a &lt;strong&gt;spreadsheet-based&lt;/strong&gt; quoting, inventory, or approval process with a connected system.&lt;/li&gt;
&lt;li&gt;Automating &lt;strong&gt;invoicing and follow-ups&lt;/strong&gt; so nothing falls through the cracks.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;customer intake or request&lt;/strong&gt; workflow that's currently run over WhatsApp and email.&lt;/li&gt;
&lt;li&gt;Reporting that someone rebuilds by hand every month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A contained first project delivers a visible result fast, builds trust with the team, and funds the next phase with proven gains rather than promises.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Not sure which process to start with?&lt;/strong&gt; That's exactly what the diagnostic phase answers. &lt;a href="https://aboelmakarem.pro/digital-transformation" rel="noopener noreferrer"&gt;See the digital transformation service →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Phase 3 — Replace Spreadsheets With Connected Software
&lt;/h2&gt;

&lt;p&gt;For most SMEs, the biggest single leap is moving off spreadsheets onto a proper system. Spreadsheets break silently: no audit trail, version chaos, broken formulas, and no real-time visibility for managers.&lt;/p&gt;

&lt;p&gt;Replacing them means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One source of truth&lt;/strong&gt; instead of five conflicting files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated hand-offs&lt;/strong&gt; — the next person is notified, not chased.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; so bad data can't enter in the first place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time dashboards&lt;/strong&gt; so managers see status without asking.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where a hands-on consultant who can actually build matters: the same person who maps the process also ships the system that replaces it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 4 — Add AI Where It Genuinely Helps
&lt;/h2&gt;

&lt;p&gt;AI turns digital transformation from simple digitization into a system that can read, summarize, and decide. But add it where it creates real value, not for the sake of it. Practical, honest uses for an SME:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document handling&lt;/strong&gt; — extracting data from invoices, contracts, and forms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drafting and replies&lt;/strong&gt; — first-draft customer responses, reports, and summaries, in Arabic and English.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data work&lt;/strong&gt; — cleaning, categorizing, and summarizing records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recurring questions&lt;/strong&gt; — an internal assistant over your own policies and documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI is most powerful once your processes are connected, because it has clean data to work with. That's why it comes after — not before — Phases 2 and 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 5 — Roll Out in Phases, With Change Management
&lt;/h2&gt;

&lt;p&gt;The fastest way to kill a transformation is a big-bang launch that disrupts operations. Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Roll out &lt;strong&gt;one team or one process at a time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Train the people&lt;/strong&gt; who will run it — adoption fails when staff aren't brought along. (This is where &lt;a href="https://aboelmakarem.pro/ai-training" rel="noopener noreferrer"&gt;AI training for your team&lt;/a&gt; and transformation reinforce each other.)&lt;/li&gt;
&lt;li&gt;Keep the old process as a fallback until the new one is proven.&lt;/li&gt;
&lt;li&gt;Measure, fix, then expand to the next process.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why an Independent Consultant Beats an Agency for SMEs
&lt;/h2&gt;

&lt;p&gt;For an SME, a large agency adds account managers, layers, and overhead. An independent consultant gives you senior, hands-on attention and direct accountability — the same person plans the work &lt;em&gt;and&lt;/em&gt; helps build and ship it. For companies that want practical results fast without a multi-year budget, that's usually faster and far more cost-effective. The work is scoped in phases, so you fund one measurable outcome at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Much Does It Cost?
&lt;/h2&gt;

&lt;p&gt;There's no honest single price — it depends entirely on scope. A diagnostic and roadmap is a small fixed engagement. Automating one process or replacing a spreadsheet is a contained project. A full operations overhaul runs in phases. Working in stages means you commit to one measurable outcome at a time instead of a large upfront budget — which is exactly what makes transformation viable for an SME.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ready to start with one process and prove the gain?&lt;/strong&gt; &lt;a href="https://aboelmakarem.pro/digital-transformation" rel="noopener noreferrer"&gt;Talk to an independent digital transformation consultant →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How long does a digital transformation take for an SME?&lt;/strong&gt;&lt;br&gt;
A focused first project — automating one process or replacing a spreadsheet — typically runs in weeks, not years. A broader transformation continues in phases, each delivering a measurable result, so you're never waiting a year to see value. The phased approach is what keeps SME projects from stalling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I hire an agency or an independent consultant?&lt;/strong&gt;&lt;br&gt;
For most SMEs, an independent consultant who can both advise and build is faster and more cost-effective. You get senior attention and direct accountability without agency overhead and account-management layers. The same person who maps your process helps ship the system that replaces it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need to replace my ERP to transform?&lt;/strong&gt;&lt;br&gt;
Often no. Transformation usually starts by automating painful processes and replacing spreadsheets around your existing systems. Legacy or ERP modernization is done in phases, without disrupting operations — you modernize what's holding you back, not everything at once.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>business</category>
      <category>productivity</category>
      <category>startup</category>
    </item>
    <item>
      <title>How to Train Your Employees and Executives on AI to Accelerate Work: A Practical Roadmap</title>
      <dc:creator>Abo-Elmakarem Shohoud</dc:creator>
      <pubDate>Mon, 01 Jun 2026 13:22:43 +0000</pubDate>
      <link>https://dev.to/karem505/how-to-train-your-employees-and-executives-on-ai-to-accelerate-work-a-practical-roadmap-531e</link>
      <guid>https://dev.to/karem505/how-to-train-your-employees-and-executives-on-ai-to-accelerate-work-a-practical-roadmap-531e</guid>
      <description>&lt;p&gt;The fastest way to train your team on AI is to skip generic courses and run hands-on, role-specific sessions on your company's real tasks: train employees on daily workflows (writing, reporting, customer replies, automating repetitive work) with ChatGPT and Claude, train executives separately on AI strategy and decision-making, and follow up so the habit sticks. Below is the exact roadmap I use with companies across Egypt, the UAE, and Saudi Arabia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Generic AI Courses Fail Your Team
&lt;/h2&gt;

&lt;p&gt;Most online AI courses are self-paced, generic, and theoretical. Your marketing lead finishes a 6-hour video and still doesn't know how to use AI on &lt;em&gt;their&lt;/em&gt; monthly report. The skill that actually moves the needle — prompt engineering on real work — only develops through guided practice on the tasks people do every day.&lt;/p&gt;

&lt;p&gt;Two things make corporate AI training work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is built around your team's actual workflows&lt;/strong&gt;, not a generic curriculum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It splits employees and executives&lt;/strong&gt;, because they need fundamentally different things from AI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Map the Time-Wasting Tasks First
&lt;/h2&gt;

&lt;p&gt;Before any training, list the repetitive, time-consuming tasks each department actually does. Common high-value targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Marketing&lt;/strong&gt;: drafting posts, ad copy, content briefs, repurposing one article into ten formats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sales&lt;/strong&gt;: writing follow-up emails, summarizing calls, tailoring proposals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HR&lt;/strong&gt;: screening CVs, drafting job descriptions, policy documents, interview questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finance &amp;amp; Operations&lt;/strong&gt;: summarizing reports, cleaning data, drafting SOPs, answering recurring questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer support&lt;/strong&gt;: first-draft replies, knowledge-base articles, tone adjustment across Arabic and English.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tasks become the live exercises in the workshop. People learn AI by automating something they were going to do anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Train Employees: Hands-On With Real Tools
&lt;/h2&gt;

&lt;p&gt;The employee track is workflow-first and non-technical — there is no coding. The core skill is &lt;strong&gt;prompt engineering&lt;/strong&gt;: writing clear instructions that get accurate, useful results from tools like ChatGPT and Claude.&lt;/p&gt;

&lt;p&gt;A practical employee session covers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;How the tools actually work&lt;/strong&gt; (and where they get things wrong) so people trust but verify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt engineering fundamentals&lt;/strong&gt; — context, role, examples, constraints, iteration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live practice&lt;/strong&gt; — each person applies AI to one of their own real tasks during the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A reusable prompt library&lt;/strong&gt; the team keeps and grows after the workshop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data and confidentiality basics&lt;/strong&gt; — what is safe to paste, and what is not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end, a non-technical employee should be able to take a task that took an hour and do a strong first draft in minutes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want this for your team?&lt;/strong&gt; I deliver hands-on AI training for employees, on-site or live online, in Arabic or English. &lt;a href="https://aboelmakarem.pro/ai-training" rel="noopener noreferrer"&gt;See the AI training service →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3 — Train Executives Separately: Strategy, Not Buttons
&lt;/h2&gt;

&lt;p&gt;Executives don't need to become power users of every tool. They need to make good decisions about AI. The leadership track focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spotting high-value use cases&lt;/strong&gt; in their own function and across the company.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluating AI initiatives&lt;/strong&gt; — what's worth funding, what's hype.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Governance&lt;/strong&gt; — data handling, acceptable use, where humans must stay in the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A working personal prompt set&lt;/strong&gt; for research, drafting communications, and preparing for meetings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When leaders understand AI well enough to set direction — and use it themselves for a few real tasks — adoption across the rest of the company accelerates dramatically. Teams copy what they see their managers actually doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Run It In-House and Tailored
&lt;/h2&gt;

&lt;p&gt;In-house (on your premises) or live-online private training beats any open course because the examples come from &lt;em&gt;your&lt;/em&gt; business. For teams in Egypt and the Gulf, Arabic-language delivery removes the biggest adoption blocker: staff practise prompts in the language they actually work in, in both Arabic and English.&lt;/p&gt;

&lt;p&gt;Format options that work in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Half-day workshop (3–4 hours)&lt;/strong&gt; — fast momentum on 2–3 high-value tasks per team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-session program over several weeks&lt;/strong&gt; — deeper adoption, with practice between sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right length depends on team size and current skill level — scope it before you book, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Follow Up, or It Won't Stick
&lt;/h2&gt;

&lt;p&gt;The single biggest reason AI training fails is no follow-up. After the session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give each team &lt;strong&gt;one task to fully move onto AI&lt;/strong&gt; within two weeks.&lt;/li&gt;
&lt;li&gt;Hold a short check-in to fix what's not working and share wins.&lt;/li&gt;
&lt;li&gt;Keep the &lt;strong&gt;prompt library&lt;/strong&gt; alive — assign an owner per department.&lt;/li&gt;
&lt;li&gt;Connect it to your broader &lt;a href="https://aboelmakarem.pro/digital-transformation" rel="noopener noreferrer"&gt;digital transformation roadmap&lt;/a&gt; so AI adoption reinforces process change instead of fighting it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Measure Whether It Worked
&lt;/h2&gt;

&lt;p&gt;You don't need invented statistics. Measure honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time-on-task&lt;/strong&gt; before and after, for the 2–3 workflows you targeted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume&lt;/strong&gt; — how many drafts, replies, or reports the AI now handles in first pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adoption&lt;/strong&gt; — what share of the team uses AI on a real task weekly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a marketing draft now takes 15 minutes instead of an hour, that's your ROI — visible, specific, and yours to verify.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ready to upskill your employees and executives?&lt;/strong&gt; &lt;a href="https://aboelmakarem.pro/ai-training" rel="noopener noreferrer"&gt;Book an AI training session for your team →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Should I train employees or executives first?&lt;/strong&gt;&lt;br&gt;
Run them as one coordinated program rather than choosing. Executives set direction and signal that AI is a priority; employees deliver the day-to-day time savings. Training both at once means the whole organization adopts AI consistently instead of in disconnected pockets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do non-technical staff really need prompt engineering?&lt;/strong&gt;&lt;br&gt;
Yes. Prompt engineering is just writing clear instructions — no coding involved. It's the single skill that separates frustrating, unreliable AI use from fast, accurate results, and it's the core of every employee-track session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long before we see results?&lt;/strong&gt;&lt;br&gt;
A half-day workshop produces visible wins immediately on 2–3 targeted tasks. Lasting, organization-wide adoption takes a few weeks of practice and follow-up — which is why a coordinated program plus a short check-in beats a one-off session.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>business</category>
      <category>career</category>
    </item>
    <item>
      <title>Building bilingual Arabic-first SaaS with Next.js 14: a production recipe</title>
      <dc:creator>Abo-Elmakarem Shohoud</dc:creator>
      <pubDate>Thu, 14 May 2026 20:51:04 +0000</pubDate>
      <link>https://dev.to/karem505/building-bilingual-arabic-first-saas-with-nextjs-14-a-production-recipe-27lo</link>
      <guid>https://dev.to/karem505/building-bilingual-arabic-first-saas-with-nextjs-14-a-production-recipe-27lo</guid>
      <description>&lt;p&gt;description: "Most 'Arabic support' is dir=rtl and a font swap. This is the architecture for actually shipping a bilingual product — direction, typography, content storage, and the SSR trick that keeps Arabic queries indexable."&lt;br&gt;
tags: nextjs, arabic, i18n, typescript&lt;/p&gt;
&lt;h2&gt;
  
  
  canonical_url: &lt;a href="https://aboelmakarem.pro/blog:" rel="noopener noreferrer"&gt;https://aboelmakarem.pro/blog:&lt;/a&gt; 
&lt;/h2&gt;

&lt;p&gt;Most "Arabic support" I see in production is &lt;code&gt;dir="rtl"&lt;/code&gt; on the body and a Google-Fonts swap to Cairo. That is not bilingual. That is translated. Buttons sit on the wrong side, numbers leak left-to-right inside right-to-left sentences, the input cursor jumps to the wrong corner when you type a URL, and Googlebot indexes none of your Arabic content because the language toggle runs in the client.&lt;/p&gt;

&lt;p&gt;I have shipped three Arabic-first SaaS products from the same Next.js 14 architecture: Tornix.ai (AI project management with a Critical Path Method engine and Primavera P6 import/export), Oravex.app (Odoo 18 with an Arabic-fluent natural-language layer), and Costra.ailigent.ai (AI construction cost estimation, Arabic-RTL throughout). Each one taught me that "bilingual" is not one problem. It is five, and they have to be solved in the right order.&lt;/p&gt;
&lt;h2&gt;
  
  
  The five layers of bilingual
&lt;/h2&gt;

&lt;p&gt;When people ask me to "add Arabic support" to an existing app, the answer is almost always: rewrite the layout assumptions. Bilingual is structural, not decorative. There are five layers, and skipping one of them creates a visible bug that takes ten times longer to fix later than to design correctly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Direction.&lt;/strong&gt; The DOM's &lt;code&gt;dir&lt;/code&gt; attribute, the document's writing mode, and every layout assumption that follows from it. &lt;code&gt;ml-4&lt;/code&gt; becomes a bug. &lt;code&gt;flex-row&lt;/code&gt; becomes a bug. Anything with a hardcoded &lt;code&gt;left&lt;/code&gt; or &lt;code&gt;right&lt;/code&gt; becomes a bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text rendering.&lt;/strong&gt; Bidirectional text (Arabic with embedded English brand names, URLs, code identifiers, numbers) needs the Unicode Bidirectional Algorithm to do the right thing. Most of the time the browser handles it, but only if you don't fight it with CSS overrides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typography.&lt;/strong&gt; Latin and Arabic have different x-heights, different optical sizes, different stroke contrasts. A font that looks balanced in English will look thin in Arabic. You need a pair, not a fallback chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content storage.&lt;/strong&gt; Where do Arabic and English strings live? In the same row with two columns, or in a translations table, or in JSON? The choice ripples through every API response, every search query, every sitemap entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO and discovery.&lt;/strong&gt; Arabic queries are a separate index. Search Console treats &lt;code&gt;/?lang=ar&lt;/code&gt; as a distinct page, hreflang has to be exactly right, and if your language toggle is client-side, Googlebot never sees the Arabic content at all unless you give it a server-rendered shadow copy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each layer is independent, and each one has a "right" answer in Next.js 14 that is not obvious from the docs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 1: direction and language context
&lt;/h2&gt;

&lt;p&gt;Skip &lt;code&gt;next-intl&lt;/code&gt; or &lt;code&gt;next-i18next&lt;/code&gt; for the first cut. They are good libraries, but they solve a translations-dictionary problem, and what you have on day one is a direction-and-document problem. Build a tiny context first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/LanguageContext.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ltr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rtl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LanguageContextValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Language&lt;/span&gt;
  &lt;span class="nx"&gt;setLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
  &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Direction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LanguageContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LanguageContextValue&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LanguageProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLanguageState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Language&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lang&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lang&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Language&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;param&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt;
    &lt;span class="nf"&gt;setLanguageState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rtl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ltr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lang&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rtl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ltr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LanguageContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;setLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setLanguageState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;LanguageContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LanguageContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;useLanguage must be used inside LanguageProvider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things matter here. First, &lt;code&gt;document.documentElement.dir&lt;/code&gt; flips the entire CSS logical-properties cascade in one assignment, which is why the next layer can be expressed in pure logical CSS. Second, the provider lives at the root in &lt;code&gt;app/layout.tsx&lt;/code&gt;, not inside the blog or dashboard. Bilingual is a document-level concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Tailwind without the &lt;code&gt;ltr:&lt;/code&gt; and &lt;code&gt;rtl:&lt;/code&gt; prefix soup
&lt;/h2&gt;

&lt;p&gt;Tailwind ships RTL variants you can opt into. Don't use them. Or rather, don't reach for them first. Modern CSS has logical properties that already do the right thing: &lt;code&gt;margin-inline-start&lt;/code&gt;, &lt;code&gt;padding-inline-end&lt;/code&gt;, &lt;code&gt;border-inline-start&lt;/code&gt;. Tailwind 3 exposes them as &lt;code&gt;ms-*&lt;/code&gt;, &lt;code&gt;me-*&lt;/code&gt;, &lt;code&gt;ps-*&lt;/code&gt;, &lt;code&gt;pe-*&lt;/code&gt;, &lt;code&gt;border-s&lt;/code&gt;, &lt;code&gt;border-e&lt;/code&gt;. Use those, and 90% of your layout flips for free when &lt;code&gt;dir&lt;/code&gt; changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tailwindcss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app/**/*.{ts,tsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/**/*.{ts,tsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;mono&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--font-jetbrains)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;monospace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;arabic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--font-plex-arabic)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system-ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sans-serif&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule I follow: any spacing or border that means "from the edge of the text" uses the logical variant. &lt;code&gt;ms-4&lt;/code&gt; replaces &lt;code&gt;ml-4&lt;/code&gt;. &lt;code&gt;pe-6&lt;/code&gt; replaces &lt;code&gt;pr-6&lt;/code&gt;. Positional things that mean literal screen edges (a fixed sidebar at the right of the viewport in both languages) stay physical. About 95% of utility classes in a typical layout are the first kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: typography is a pairing, not a fallback
&lt;/h2&gt;

&lt;p&gt;IBM Plex Sans Arabic is the best free Arabic typeface in production right now. It has eight weights, generous counters, a stroke contrast that holds up at 14px, and it pairs cleanly with monospace Latin like JetBrains Mono. The mistake is treating it as a fallback (&lt;code&gt;font-family: 'Inter', 'IBM Plex Arabic'&lt;/code&gt;). Browsers will use Inter for the Latin glyphs and Plex Arabic for the Arabic glyphs in the same paragraph, which produces a visible vertical-rhythm mismatch. Pick the font explicitly per language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/fonts.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JetBrains_Mono&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IBM_Plex_Sans_Arabic&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/font/google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jetbrains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JetBrains_Mono&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;subsets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;800&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--font-jetbrains&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plexArabic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IBM_Plex_Sans_Arabic&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;subsets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arabic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--font-plex-arabic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;IBM Plex Sans Arabic adds about 85kb gzipped per weight you ship. Be honest about which weights you actually use. I ship four (300, 400, 500, 700) because anything heavier than 700 looks like a fax in Arabic, and 200 is illegible below 18px. Then on the body element apply the family based on language: &lt;code&gt;className={language === 'ar' ? 'font-arabic' : 'font-mono'}&lt;/code&gt;. The two families never mix inside the same block, which is what keeps the rhythm consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: content storage as dual columns, not a translations table
&lt;/h2&gt;

&lt;p&gt;For a SaaS with a content surface (blog posts, product descriptions, FAQ), the choice between dual columns (&lt;code&gt;title_en&lt;/code&gt;, &lt;code&gt;title_ar&lt;/code&gt;) and a translations table keyed by &lt;code&gt;post_id&lt;/code&gt; + &lt;code&gt;locale&lt;/code&gt; decides how painful your queries become. I have built both. Dual columns win for two-language products. The translations table is correct for five or more languages, but it forces a join on every read and Postgres full-text search becomes a configuration project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- supabase/schema.sql (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title_en&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title_ar&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;excerpt_en&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;excerpt_ar&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content_en&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content_ar&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;search_vector_en&lt;/span&gt; &lt;span class="n"&gt;tsvector&lt;/span&gt; &lt;span class="k"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title_en&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="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_en&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="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;search_vector_ar&lt;/span&gt; &lt;span class="n"&gt;tsvector&lt;/span&gt; &lt;span class="k"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'arabic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title_ar&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="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_ar&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="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The serving function is a one-liner: &lt;code&gt;const title = lang === 'ar' ? post.title_ar : post.title_en&lt;/code&gt;. No joins, no fallback dance, and the two search vectors mean Arabic and English queries hit the right tokenizer. Postgres has a built-in &lt;code&gt;arabic&lt;/code&gt; text search configuration since version 11. Most people don't know this and end up writing trigram search instead, which is fine for a prototype and wrong for a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 5: hreflang the way Next.js doesn't document
&lt;/h2&gt;

&lt;p&gt;This is where I see the most production sites get it wrong, and it is the bug that costs you the most in lost traffic. The Next.js Metadata API has an &lt;code&gt;alternates.languages&lt;/code&gt; option that looks like exactly what you want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// what you reach for first — and what silently breaks&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar-EG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/?lang=ar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&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;Next strips the &lt;code&gt;?lang=ar&lt;/code&gt; query string when it renders the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag. The hreflang ends up pointing to &lt;code&gt;/&lt;/code&gt;, which is identical to the English canonical, so Google merges the two and your Arabic URL never gets indexed. Inject the link tag raw in the layout's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hrefLang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://aboelmakarem.pro/"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hrefLang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://aboelmakarem.pro/"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hrefLang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ar"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://aboelmakarem.pro/?lang=ar"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hrefLang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ar-EG"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://aboelmakarem.pro/?lang=ar"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hrefLang&lt;/span&gt;&lt;span class="p"&gt;=&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="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://aboelmakarem.pro/"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;In &lt;code&gt;app/sitemap.ts&lt;/code&gt;, include the Arabic URL as a distinct entry. Submit to Google Search Console and to Bing via IndexNow. About two weeks later, the Arabic URLs start appearing in coverage reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden Arabic SEO block
&lt;/h2&gt;

&lt;p&gt;This one is genuinely novel and I have not seen it written up anywhere, so it is worth its own section. Here is the failure mode: your homepage uses the &lt;code&gt;LanguageProvider&lt;/code&gt; from layer 1 to toggle between English and Arabic. The toggle works in the browser. You ship. Two months later, you check Search Console and your Arabic impressions are zero, because Googlebot rendered the page with the default state (English) and never saw a single Arabic character.&lt;/p&gt;

&lt;p&gt;You have two options. Option A: server-side detect the locale (from a cookie, the &lt;code&gt;Accept-Language&lt;/code&gt; header, or the URL) and render the matching language on the server. This is correct, but it doubles the rendering matrix and breaks if your CMS expects one canonical URL.&lt;/p&gt;

&lt;p&gt;Option B: render both languages on the server, hide the non-active one visually, and let the client-side toggle swap visibility. This is what I do on aboelmakarem.pro. The Arabic content sits in the DOM at request time, fully visible to crawlers, and a single CSS class moves it out of the visual flow for sighted English users.&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="c"&gt;/* globals.css */&lt;/span&gt;
&lt;span class="nc"&gt;.sr-only-seo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;The block contains the Arabic name (ابوالمكارم شهود), the service descriptions, the Arabic project names, contact information — everything Googlebot needs to match an Arabic query. It is not cloaking. The content is genuinely on the page, available to screen readers in the same way as visually-hidden labels are, and identical in meaning to the English content above it. After adding this block, Arabic impressions on the site went from approximately zero to consistent weekly traffic on the name query "ابوالمكارم شهود" within three weeks. The technique works for any client-rendered language toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three production callouts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Webfonts: subset your Arabic.&lt;/strong&gt; The full Arabic-script Unicode range covers Arabic, Persian, Urdu, Pashto, and several historical scripts. If you are shipping a product for Egyptian, Gulf, or Levantine Arabic speakers, you only need the Arabic block plus the presentation forms. Next.js's &lt;code&gt;subsets: ['arabic']&lt;/code&gt; parameter handles this, but verify the actual file size in Network tab. If your &lt;code&gt;.woff2&lt;/code&gt; is over 60kb per weight, the subset is not actually being applied and you are shipping Persian glyphs to no one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Number rendering: mixed-direction is a pit.&lt;/strong&gt; Arabic-Indic digits versus Western Arabic digits (&lt;code&gt;٠١٢٣&lt;/code&gt; versus &lt;code&gt;0123&lt;/code&gt;) is a content decision, not a technical one. Banks and government use Arabic-Indic. SaaS dashboards almost universally use Western. Pick one per surface and stay consistent. The bug to watch for is bidirectional reordering: a string like "Tornix v2.4" inside an Arabic paragraph will reverse to "2.4 Tornix v" unless you wrap the Latin run in &lt;code&gt;&amp;lt;bdi&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;span dir="ltr"&amp;gt;&lt;/code&gt;. Test with a real Arabic paragraph, not just a label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Form inputs: text-align: start, not left.&lt;/strong&gt; Every &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; in your design system should use &lt;code&gt;text-align: start&lt;/code&gt; so that the cursor begins at the leading edge for both languages. Same for placeholders. Same for error messages. The one exception is numeric inputs (price, quantity, year), which should keep &lt;code&gt;text-align: end&lt;/code&gt; so the trailing digit aligns with the field's edge. If your design system has fifty input components and they all use &lt;code&gt;text-align: left&lt;/code&gt;, this is a one-day refactor that fixes a hundred small bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this enables
&lt;/h2&gt;

&lt;p&gt;Three production SaaS — Tornix.ai, Oravex.app, Costra.ailigent.ai — ship from one bilingual architecture and one shared component library. Adding a new product means a new project repo, the same &lt;code&gt;LanguageContext&lt;/code&gt;, the same dual-column schema, the same hreflang block, the same hidden-SEO pattern. The cost of bilingual on day one is high. The cost of bilingual on day three hundred is approximately zero. That is the whole argument for designing this in from the start: the layers compound, and the only painful version is the retrofit.&lt;/p&gt;

&lt;p&gt;If you are starting a new product for any market where Arabic readers will pay you money — Egypt, Saudi Arabia, the UAE, Jordan, Morocco — build it bilingual on day one. The architecture above is enough to get the first version out the door, and the five layers give you a checklist to verify nothing is missing before you ship.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://aboelmakarem.pro" rel="noopener noreferrer"&gt;Abo-Elmakarem Shohoud&lt;/a&gt; — Full-Stack Developer at Ailigent, building bilingual AI SaaS from Cairo. Three production products in the wild: &lt;a href="https://tornix.ai" rel="noopener noreferrer"&gt;Tornix.ai&lt;/a&gt;, &lt;a href="https://oravex.app" rel="noopener noreferrer"&gt;Oravex.app&lt;/a&gt;, &lt;a href="https://costra.ailigent.ai" rel="noopener noreferrer"&gt;Costra.ailigent.ai&lt;/a&gt;. Find me on &lt;a href="https://github.com/karem505" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/abo-el-makarem-shohoud-745367244" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or &lt;a href="https://twitter.com/karem_shohud" rel="noopener noreferrer"&gt;Twitter/X&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>nextjs</category>
      <category>saas</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
