Architecture / Internationalization (i18n)
Source on GitHub

Internationalization (i18n)

MangoStudio uses a pure TypeScript i18n system with compile-time translation verification.

Architecture

apps/shared/src/i18n/
  pt-BR.ts    → Source of truth (all keys defined here)
  en.ts       → English translations (typed against Messages)
  types.ts    → Messages type + Locale union

apps/frontend/src/lib/
  locale-dictionaries.ts → English eager, every other locale loaded as its own chunk

apps/frontend/src/hooks/
  use-i18n.tsx → React Context hook over the loaded dictionary

Key Convention

Keys use dot-separated notation organized by feature scope:

auth.loginTitle
chat.input.placeholder
settings.providers.caching.title
tools.generate_image.title

The Messages type is inferred from the pt-BR.ts dictionary using as const. Adding a key to pt-BR without translating it in en.ts is a compile-time error because en.ts is typed as Messages.

How It Works

Type System

types.ts defines:

type DeepStringRecord<T> = {
  [K in keyof T]: T[K] extends string
    ? string
    : T[K] extends object
      ? DeepStringRecord<T[K]>
      : never;
};

type Messages = DeepStringRecord<typeof ptBR>;
type Locale = 'pt-BR' | 'en';

This ensures that en.ts must have all the same keys as pt-BR.ts but values can be different strings.

React Hook

useI18n() returns { t, locale, setLocale }:

import { useI18n } from '@/hooks/use-i18n';

function MyComponent() {
  const { t } = useI18n();
  return <h1>{t.auth.loginTitle}</h1>;
}
  • Locale detection: Checks localStorage (mangostudio:locale) first, then navigator.language, defaults to en.
  • Lazy loading: English is bundled in the entry chunk and is the fallback. Every other locale is its own chunk, imported through its @mangostudio/shared/i18n/<locale> subpath by lib/locale-dictionaries.ts. main.tsx starts loading the startup locale before the first render, alongside the session request. A runtime switch keeps the current text until the next dictionary arrives; a failed load renders English and keeps the stored choice for the next start.
  • Persistence: setLocale() updates both React state and localStorage.

Provider

I18nProvider wraps the app root and provides the i18n context:

<I18nProvider>
  <App />
</I18nProvider>

Uses React 19’s use() API for context consumption. Throws if useI18n() is called outside the provider.

Adding a New Language

  1. Create the dictionary file (e.g., apps/shared/src/i18n/es.ts).
  2. Copy the key structure from pt-BR.ts and translate all values.
  3. Type the file as Messages to ensure structural compatibility:
    import type { Messages } from './types';
    export const messages: Messages = {
      /* translations */
    };
  4. Add the locale to the Locale type in types.ts:
    type Locale = 'pt-BR' | 'en' | 'es';
  5. Wire the lazy loading: a ./i18n/es subpath export in apps/shared/package.json, a loader in apps/frontend/src/lib/locale-dictionaries.ts, a branch in its detectLocale(), and a noRestrictedImports entry for the subpath in biome.json. Shared modules import a dictionary from its own file, never a runtime value from the i18n barrel, or every dictionary lands in the entry chunk.
  6. Add the locale option in the appearance settings UI if desired.

Adding New Keys

  1. Add the key to pt-BR.ts in the appropriate section.
  2. Add the translation to en.ts in the same path. If you miss this step, the build will fail because en.ts must satisfy the Messages type.
  3. Use the key via t.path.to.key in components.

Current Translations

Locale Coverage File
pt-BR Full (source of truth) pt-BR.ts (~444 lines)
en Full en.ts (~440 lines)

Key Sections

The dictionary is organized into these top-level sections:

  • auth — Login, signup, error messages
  • chat — Chat UI, input bar, feed, context, continuation, system events
  • studio — Image workspace
  • gallery — Gallery page
  • settings — Tabs, context, prompts, general, connectors, providers, tools, appearance
  • common — Shared labels (save, cancel, delete, confirm)
  • providers — Provider display names and descriptions
  • thinking — Thinking/reasoning labels
  • tools — Tool names, categories, parameter labels
  • errors — Error messages
  • api — API status messages
  • generation — Generation-related labels