Skip to content
Twilight React Playground
ثيم رائدaren

translations

objectAdvancedserverbrowserlive demo

Downloads Salla's shared translations file, every storefront message in Arabic and English, which the engine's translate function reads.

import { translations } from '@salla.sa/twilight-theme-engine/api/translations';

In plain words

Labels such as "Add to cart" are not written in your theme: Salla keeps them in one shared file, with every message in each language. translations.get() downloads that file.

The engine does this before every page and builds the translate function t from it, which is what components should use (useTranslation().t('pages.cart.total')). Read the file yourself only to inspect or search it.

Signature

translations.get(): Promise<TranslationMessages | null>   // GET https://cdn.salla.network/js/translations.json
translations.queries.byLocale()                            // key ['translations']: takes no locale

type TranslationMessages = Record<string, Record<string, string>>;
// real shape: { 'ar.trans': { common: {…}, pages: {…}, … }, 'en.trans': {…} }

Try it live

Salla's shared translations file, already in the cache: one file with every language, looked up by a dotted key.Try this: try pages.cart.total, then shorten it to pages.cart to reach a group instead of a message.
Storefront canvas · ar · RTL
data['ar.trans']إضافة للسلة
data['en.trans']Add to cart
t() on this ar pageإضافة للسلة
Controls
What a theme writes
import { useTranslation } from '@salla.sa/twilight-theme-engine/i18n';

// In a component, prefer t(): it reads this same file for the page's language.
export function AddToCartLabel() {
  const { t } = useTranslation();
  return <span>{t('pages.cart.add_to_cart')}</span>;
}

Example

app/dev/CartMessages.tsx
import { useQuery } from '@tanstack/react-query';
import { translations } from '@salla.sa/twilight-theme-engine/api/translations';
import { useTranslation } from '@salla.sa/twilight-theme-engine/i18n';

/** Development helper: the cart messages Salla provides for this page's language. */
export function CartMessages() {
  const { locale } = useTranslation();
  const { data } = useQuery(translations.queries.byLocale());
  // Typed as flat strings, but each file is nested: pages → cart → total.
  const file = data?.[locale + '.trans'] as unknown as
    | { pages?: { cart?: Record<string, string> } }
    | undefined;

  return (
    <dl>
      {Object.entries(file?.pages?.cart ?? {}).map(([key, text]) => (
        <div key={key}>
          <dt>pages.cart.{key}</dt>
          <dd>{text}</dd>
        </div>
      ))}
    </dl>
  );
}

How it behaves

  • Fetched through the cdn client, so no store or customer headers are sent: the file is the same for every store. Timeout 5 s. About 150 KB with only ar.trans and en.trans in it (2026-09-16).

  • It never throws: a failed download logs a warning and returns null, and t is then built with no Salla messages, so a key renders as itself (or as the fallback you pass).

  • rootBeforeLoad awaits ensureQueryData(translations.queries.byLocale()) on every navigation and passes the file to createI18nInstance(locale, …), which takes <locale>.trans as the default namespace and falls back to the theme's own bundled translations.

  • Because the file sits in the query cache after the server render, it travels to the browser with the page HTML.

Gotchas

  • TranslationMessages promises string values one level down, but the files are nested (pagescarttotal). data['ar.trans']['pages.cart.total'] finds nothing: walk the dotted key, or use t().

  • Despite its name, byLocale() takes no locale and its key is only ['translations']: one file holds every language.

  • docs/11-internationalization.md shows translationsQueries.byLocale(locale), a store-specific CDN address and a keys factory. None of that exists: the export is translations, and it reads one shared file.

Related

Source and docs