Skip to content

Localization

The chat UI can render in any language, with automatic right-to-left layout. English ships built in and is always the fallback: any string you don't translate shows its English default, so partial translations are safe.

See the full list of translation keys for every translatable string and its English default.

Quick start

Choose a language with the locale attribute and point translations-url at a hosted catalog. No JavaScript required:

html
<saytv-chat
  app-id="your-app-id"
  locale="es"
  translations-url="https://sdk.saytv.net/@saytv/chat-sdk@latest/locales/es.json"
></saytv-chat>

SayTV hosts ready-made catalogs for the languages below, and right-to-left languages (like Arabic) lay out automatically.

Available languages

Catalogs are hosted, cached and CORS-enabled on the SDK CDN, so translations-url can point straight at them. translations-url is fetched by the browser at runtime, so if nothing translates see When the UI stays in English.

LanguagelocaleCatalog URL
English (template)en…/locales/en.json
Arabic (RTL)ar…/locales/ar.json
Spanishes…/locales/es.json
Frenchfr…/locales/fr.json

Pin a version for production

@latest always serves the newest catalogs (short cache). For production, pin a version (e.g. @saytv/chat-sdk@2.2.2/locales/es.json) so the catalog matches the SDK you ship (immutable, cached for a year). The current version is in the top navigation and on the Changelog.

Review machine-generated translations

ar, es, and fr are ready to use but machine-translated, have a native speaker review them before production. en.json is the authoritative key set and is always safe as a template.

Supplying your own translations

Want a language we don't host, or to tweak the wording? Supply your own catalog, a flat map of keys to strings, e.g. { "auth.login.title": "…" }. Provide it one of two ways; either way, untranslated keys fall back to English, so you only translate what you need.

From a URL (no JavaScript)

Host a catalog and point translations-url at it:

html
<saytv-chat locale="fr" translations-url="https://your-cdn.com/fr.json"></saytv-chat>

The file must be a flat, single-locale catalog matching the locale. Serve it with an Access-Control-Allow-Origin header that permits your page's origin. If the fetch fails the widget stays on English and logs an error to the console — it never breaks the page.

Don't hotlink the docs site

The /locales/*.json links on this site are for downloading and inspection. For runtime use, host your own copy (or use the SDK CDN), not the docs domain.

From an object (programmatic)

Assign the translations property, keyed by locale, handy when you already have the catalog in JS or switch languages dynamically:

js
const el = document.querySelector('saytv-chat');
el.translations = {
  ar: { 'auth.login.title': 'مرحبا بعودتك', 'nav.chat': 'الدردشة' },
};
el.setAttribute('locale', 'ar'); // set translations first, then the locale

Interpolation: some values contain {placeholder} tokens the widget fills at runtime. Keep them intact in your translation:

js
el.translations = { fr: { 'time.minutesAgo': 'il y a {count} min' } };

Customizing & adding a language

Adding a language needs no SDK code, just a catalog:

  1. Download the English template, en.json, which lists every key.
  2. Translate the values, keeping any {placeholder} tokens as-is.
  3. Host it and point translations-url at it (or pass it via translations), then set locale.

Keys are the contract

The translation keys are the stable interface. New SDK versions may add keys; until you translate them they fall back to English, so existing languages keep working.

Switching at runtime & RTL

Changing the locale attribute switches the language live, the widget re-renders, no reload:

js
document.querySelector('saytv-chat').setAttribute('locale', 'fr');

Right-to-left languages (ar, he, fa, ur, …) set the text direction automatically.

One language per page

Language is shared by every widget on a page. Two <saytv-chat> elements (or a widget plus a bubble) cannot show different locales — the last one to set locale wins, and both re-render in it. The widget logs a warning when it detects this.

Quizzes and polls in their own language

A quiz or poll carries the language it was written in. That comes either from a client-wide default — set once in the client panel, for a client that runs a single language — or from the language a moderator picked on that specific interaction, which wins over the default. The widget renders that interaction's own labels — the submit button, the panel heading, the close button — in that language, whatever language the rest of the chat is in. So a French poll running in an English chat reads "Envoyer la réponse", not "Submit Answer".

Nothing to configure in the embed. Two things are worth knowing:

  • The catalog still has to be registered. A poll marked fr falls back to English labels unless a French catalog has been supplied, exactly as the widget itself would. If you run interactions in several languages, register a catalog for each one up front:

    js
    const el = document.querySelector('saytv-chat');
    el.translations = { fr: frCatalog, es: esCatalog };

    A catalog that arrives late — a translations-url fetch resolving after the poll opens — is picked up as soon as it lands; the panel re-renders.

  • Right-to-left interactions lay themselves out. An Arabic poll inside an English chat gets dir="rtl" on its own panel and nothing else moves.

With neither set, the interaction follows whatever language the widget is in — the behaviour before this existed.

Messages outside the panel stay in the widget's language. An error such as "Failed to submit vote" speaks for the app rather than for the poll, so it follows locale.

When the UI stays in English

The symptom: your content is in the right language — questions, options, messages, all of which come from your admin — but the widget's own labels ("Submit Answer", "Type a message…", the tab names) are English.

That means no catalog was registered for the active locale. locale on its own only selects a language; the catalog has to be supplied too. Check, in order:

  1. Is a catalog supplied at all? locale="fr" with no translations-url and no translations property renders English. Add one of the two.
  2. Open the browser console. A failed catalog fetch logs [saytv] Could not load the "…" translations from "…". The widget also fires an stv:error event, so you can surface it in your own monitoring.
  3. Is it a CORS error? Access to fetch at '…' has been blocked by CORS policy means the host serving the catalog is not sending Access-Control-Allow-Origin. The request succeeds in curl (which does not enforce CORS) and fails in the browser, so test it from a page, not a terminal. Fix the header on that host, or embed the catalog with the translations property, which needs no request at all.
  4. Do the locale and the catalog match? A catalog is registered under the locale you name, not the language inside the file — locale="de" with translations-url pointing at fr.json registers French as German. Region subtags are safe: fr-CA resolves to the fr catalog.

The Playground's Code tab sidesteps all of this: pick a language and the snippet it generates embeds the whole catalog inline.

Dates & numbers

Dates and numbers format with the browser's native Intl APIs for the active locale, no setup needed. Relative-time words ("Just now", "Yesterday", "5m ago") come from the catalog (time.* keys), so they translate like any other string.

Framework wrappers

React and Vue expose locale, translations, and translationsUrl props.

tsx
// React
<SayTVChat
  appId="your-app-id"
  locale="es"
  translationsUrl="https://sdk.saytv.net/@saytv/chat-sdk@latest/locales/es.json"
/>
vue
<!-- Vue -->
<SayTVChat
  app-id="your-app-id"
  locale="es"
  translations-url="https://sdk.saytv.net/@saytv/chat-sdk@latest/locales/es.json"
/>

Both also accept a translations object. See the React and Vue guides.

Programmatic API

For advanced use, the SDK exports its i18n primitives directly:

ts
import { registerTranslations, setLocale, t, locale, direction } from '@saytv/chat-sdk';

registerTranslations('ar', { 'auth.login.title': 'مرحبا بعودتك' });
setLocale('ar');
t('auth.login.title'); // 'مرحبا بعودتك'
t('time.minutesAgo', { count: 5 }); // interpolated
locale.value; // 'ar'  (signal)
direction.value; // 'rtl' (signal)
ExportDescription
setLocale(tag)Switch the active locale (unknown/empty → en).
registerTranslations(tag, catalog)Register or extend one locale's catalog.
registerAllTranslations(map)Register several catalogs at once, keyed by locale.
loadTranslations(tag, url)Fetch a hosted catalog and register it for tag. The imperative form of translations-url; resolves once registered. A failed fetch never throws — it logs an error, fires stv:error and leaves the UI on English.
t(key, params?)Resolve a key for the active locale, interpolating {tokens}.
tIn(tag, key, params?)Resolve a key in an explicit locale, ignoring the active one. Pass null to fall back to the active locale. This is what renders a quiz or poll in its own language.
locale / directionSignals holding the active tag and 'ltr'/'rtl'.
directionFor(tag)'ltr'/'rtl' for an explicit locale. The counterpart to tIn.

SayTV Chat SDK Documentation