Skip to main content
Tracent TechnologiesTracent Technologies
Get started
All guides

Add a new language to your console

The Tracent console ships in six languages and falls back gracefully when a translation is missing. This guide covers what users do to switch language, what they get when the catalogue is partial, how persistence works across devices, and what a developer adds when a new locale joins the set.

~7 min read

The Phase 1 language set

Tracent ships six Phase 1 launch languages, named in the conventions their speakers actually use:

  • en: English (default)
  • pcm: Naijá (Nigerian Pidgin)
  • yo: Yorùbá
  • ha: Hausa
  • ig: Igbo
  • sw: Kiswahili (Kenya and East Africa)

Phase 3 adds Arabic (ar) and Amharic (am) for North Africa and Ethiopia. The list lives in i18n/config.ts as a typed locales tuple and a localeNames map; adding a locale is a one-line change on each.

Switching language as a user

The globe icon in the top-right of the marketing site and the one in the console sidebar both open the same picker, the TracentLanguageSwitcher component. Clicking opens a dropdown with every available locale, the current one ticked. The dropdown shows the locale code in mono next to the native display name (Yorùbá, Naijá, Kiswahili) so users find their language by sight.

Choosing a locale calls the setLocale server action. The action does two things:

  1. Sets the TRACENT_LOCALE cookie with a one-year max-age, sameSite: lax, on /. Every subsequent request reads it via i18n/request.ts.
  2. If the user is signed in, mirrors the choice into user_profiles.preferred_language. This is what survives clearing the browser cookies or signing in on a new device.

The page then router.refresh()es; the next render uses the new catalogue. No reload, no flash.

The deep-merge fallback

The Phase 1 catalogues are partial. English carries every string; the other five locales carry the high-traffic strings (the marketing hero, the nav, common verbs) and gradually deepen as a native-speaker pass fills in the rest. To make partial catalogues safe to ship, i18n/request.ts deep-merges the locale's file over the English base:

function withFallback(base: MessageTree, override: MessageTree): MessageTree {
  const merged: MessageTree = { ...base };
  for (const [key, value] of Object.entries(override)) {
    const baseValue = merged[key];
    merged[key] =
      isTree(baseValue) && isTree(value)
        ? withFallback(baseValue, value)
        : value;
  }
  return merged;
}

Any key present in the override beats the base; any key missing from the override falls through to English. The result is that a Yorùbá user sees Yorùbá where the team has written it and English where the team has not. The page never breaks on a missing key, the experience degrades gracefully, and the native-speaker pass can land translations incrementally without coordinating a single big-bang release.

Cross-device persistence

The cookie lives in one browser. The profile column lives in the database. The connection between them happens at the auth callback at app/auth/callback/route.ts: after a successful sign-in, the callback reads user_profiles.preferred_languageand writes the cookie if the value is a recognised locale. The next render picks up the user's chosen language on the new device, even though the cookie did not exist there ten seconds ago.

The trade-off this design accepts: a profile change made outside the switcher (say, a future settings page or a direct SQL update) does not propagate to a live session until the user signs in again or uses the switcher. For Phase 1 that is acceptable; if it becomes a problem, the fix is to read the profile in middleware and sync the cookie there at the cost of one DB hit per request.

For developers: add a new locale

A new locale is two additions, no migrations, no code beyond the switcher already picking it up:

  1. Add the new locale code to the locales tuple and the localeNames map in i18n/config.ts. The tuple is typed; TypeScript ensures every consumer (the switcher, the request config) sees the new option.
  2. Create messages/<locale>.json. Start with the keys you have translated; the deep-merge handles the rest. The most-visible keys for early translation coverage are common, nav, hero, and the marketing-page strings; the console can stay English until late in the rollout if you are language-launching from marketing.

A typical starter file:

{
  "common": {
    "getStarted": "...",
    "signIn": "...",
    "language": "..."
  },
  "nav": {
    "integrations": "...",
    "pricing": "...",
    "docs": "..."
  }
}

Ship the change. The switcher picks up the new option on the next deploy. Users in your existing base do not see anything different until they choose the new locale; the deep-merge means even a 5%-complete catalogue does not regress the experience for users who try it.

The native-speaker translation pass

Translation quality matters more than coverage for the Tracent positioning. Yorùbá that reads like literal English translated word-by-word is worse than missing keys. The translation pass therefore proceeds through three stages per locale:

  1. Coverage: the engineering scaffold above, which we have shipped. The catalogue exists, the switcher works, the fallback is graceful.
  2. Hand-translation by a native speaker: the highest-traffic strings (marketing hero, nav, common verbs, sign-up flow) get the first pass. Founder commissions a native speaker per locale; the Yorùbá pass should be done by a Lagos-based native speaker, the Hausa pass by a Kano or Kaduna native, and so on. Pidgin specifically benefits from a writer familiar with the publication-grade conventions; do not auto-generate.
  3. Reading review: a second native speaker reads the catalogue end to end. Catches the auto-correct artefacts and the false cognates that pass linting but embarrass the brand.

Phase 1 ships with all six catalogues at coverage-only stage; the hand-translation pass starts post-launch for the languages a real customer actually speaks. Yorùbá and Hausa first; Pidgin and Igbo follow; Kiswahili once we have East African customers.

Where to go from here

  • The marketing footer's language switcher and the console sidebar's both call the same TracentLanguageSwitcher component; styling is consistent by construction.
  • For Phase 3, Arabic adds a right-to-left layer that the current shell does not yet flip. Plan the RTL pass alongside the Arabic translation rather than after it.
  • The quickstart guide ends with a multilingual prompt strip showing the same Paystack transfer in all six Phase 1 languages; a useful cross-check that the marketing copy renders correctly.