Languages and RTL
Translate your theme with useTranslation, locale files, and direction-safe layout.
Read this when you add text to your theme, or support both Arabic and English.
Salla stores are multilingual, and Arabic is right-to-left (RTL). The engine handles language and direction for you. Your job is to put text in translation files and write layout that works both ways.
The language comes from the URL
Every route starts with the optional {-$locale} segment (see Routes and pages).
For /en/cart, the locale is en. On a store with several languages, a URL without the segment redirects to /ar/....
From the locale it also picks the direction: ar, fa, he and ur are RTL, everything else is LTR.
The root route writes both onto the page, so the first server render already has the right direction:
import { getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';
function RootComponent() {
const ctx = getTwilightContext();
return (
<html lang={ctx.locale} dir={ctx.dir} suppressHydrationWarning>
{/* … */}
</html>
);
}Translation files
Your theme's strings live in locales/ar.json and locales/en.json. Keys are flat strings with dots, not nested objects:
{
"pages.cart.total": "Total",
"pages.cart.empty_cart": "Your cart is empty",
"pages.products.add_to_cart": "Add to Cart",
"common.actions.save": "Save"
}vite.config.ts points the plugin at the folder (twilightReact({ localesDir: './locales' })), and the root route hands
the result to the provider:
import themeTranslations from 'virtual:twilight/theme-translations';
<TwilightProvider translations={themeTranslations} layout={ThemeLayout}>
<Outlet />
</TwilightProvider>Without that translations prop none of your files load. Add every key to every language file.
The translation keys recipe covers the steps.
The store's own translations from Salla are looked up first, and your theme files are the fallback. If a key you added seems ignored, the same key probably exists on the Salla side.
Use a translation
useTranslation returns t plus the language facts:
import { useTranslation } from '@salla.sa/twilight-theme-engine/i18n';
function AddToCart() {
const { t, locale, isRTL } = useTranslation();
return <button>{t('pages.products.add_to_cart', 'Add to Cart')}</button>;
}The second argument is the fallback text, shown when the key is missing, so customers never see a raw key.
Besides t, you get locale, direction, isRTL, isLTR and languageName.
t behaves as in react-i18next, and works during server rendering in the language of the request.
Outside the provider the language fields fall back to Arabic, right to left.
Every field and the full signature are on the useTranslation page.
Placeholders
Put values in the string with {{name}} and pass them as options. Use defaultValue for the fallback in this form:
t('store.closed_notification', { defaultValue: 'Closed until {{nextOpen}}', nextOpen })Plurals
Some Salla strings use Laravel-style plural segments such as {1} once|[3,10] :count times.
Plain t() cannot read those, so run the result through formatSallaPlural:
import { formatSallaPlural } from '@salla.sa/twilight-theme-engine/utils';
<span>{formatSallaPlural(t('pages.products.sold_times'), product.sold_quantity ?? 0)}</span>It picks the matching segment, fills in :count, and strips inline tags.
Write layout that works both ways
Use logical classes so one design serves both directions:
| Use | Instead of |
|---|---|
ms-4, me-4 | ml-4, mr-4 |
ps-4, pe-4 | pl-4, pr-4 |
text-start, text-end | text-left, text-right |
rtl:rotate-180 on an arrow icon | two icons behind an if |
Arrows and chevrons are the usual RTL bug. Open your page at /ar and /en side by side before you call it done.
Next: Settings, to let merchants configure your theme.