# How to Add Multi-Language Support to the FreeLLMAPI Dashboard

> Learn to add multi-language support to the FreeLLMAPI dashboard by creating locale files, registering languages, and synchronizing translations for a seamless user experience.

- Repository: [Tashfeen/freellmapi](https://github.com/tashfeenahmed/freellmapi)
- Tags: how-to-guide
- Published: 2026-06-28

---

**Adding multi-language support to the FreeLLMAPI dashboard requires creating a JSON locale file in `client/src/i18n/locales/`, registering the language in the `SUPPORTED_LOCALES` tuple within [`I18nProvider.tsx`](https://github.com/tashfeenahmed/freellmapi/blob/main/I18nProvider.tsx), and synchronizing the translations in [`desktop/src/i18n.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/desktop/src/i18n.ts) to enable the native desktop tray interface.**

FreeLLMAPI ships with a lightweight, zero-dependency internationalization (i18n) layer that powers both the React-based web dashboard and the native desktop tray interface. Adding multi-language support involves updating three coordinated configuration files to introduce new translations without installing external libraries like react-i18next or i18next.

## Understanding the Zero-Dependency Architecture

The FreeLLMAPI i18n implementation uses a custom provider pattern with **zero external dependencies**, relying on TypeScript tuples and flat JSON dictionaries for type-safe translations.

### Web Dashboard Implementation

The web dashboard uses a custom React provider defined in [`client/src/i18n/I18nProvider.tsx`](https://github.com/tashfeenahmed/freellmapi/blob/main/client/src/i18n/I18nProvider.tsx). This component manages the `SUPPORTED_LOCALES` tuple and handles automatic language detection via the `detectLocale()` function (lines 39-58) without external packages. The provider imports locale JSON files and exposes them through a React context.

### Native Desktop Tray Support

The Electron-based desktop application maintains a separate string dictionary in [`desktop/src/i18n.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/desktop/src/i18n.ts). The `NATIVE_LOCALES` array and `STRINGS` object power the system tray menu and pop-over interfaces through the `nativeStrings()` helper function. This ensures the tray UI remains consistent with the web dashboard when running the desktop build.

## Step 1: Create the Locale JSON File

Create a new JSON file in `client/src/i18n/locales/` using the flat key structure established by existing translations. Each key uses dot-notation (e.g., `nav.models`, `premium.renewsOn`) and supports interpolation placeholders like `{date}`.

For example, to add German support, create [`de.json`](https://github.com/tashfeenahmed/freellmapi/blob/main/de.json):

```json
{
  "nav": {
    "models": "Modelle",
    "keys": "Schlüssel",
    "analytics": "Analyse"
  },
  "premium": {
    "renewsOn": "Erneuert am {date}"
  },
  "dashboard": {
    "welcome": "Willkommen bei FreeLLMAPI"
  }
}

```

Reference existing files like [`en.json`](https://github.com/tashfeenahmed/freellmapi/blob/main/en.json) and [`fr.json`](https://github.com/tashfeenahmed/freellmapi/blob/main/fr.json) in the same directory to ensure you cover all required keys.

## Step 2: Register in the React i18n Provider

Update [`client/src/i18n/I18nProvider.tsx`](https://github.com/tashfeenahmed/freellmapi/blob/main/client/src/i18n/I18nProvider.tsx) to import the new dictionary and append the language code to the `SUPPORTED_LOCALES` tuple (lines 27-34). Then register the locale in the `dictionaries` record to enable runtime lookups.

```tsx
// client/src/i18n/I18nProvider.tsx
import de from './locales/de.json';

export const SUPPORTED_LOCALES = [
  'en', 'zh-CN', 'fr', 'es', 'pt-BR', 'it', 'de'
] as const;

const dictionaries: Record<Locale, Dictionary> = {
  en,
  'zh-CN': zhCN,
  fr,
  es,
  'pt-BR': ptBR,
  it,
  de: de as Dictionary,
};

```

The provider automatically detects the browser’s `navigator.language` value and falls back to English if the detected locale is not present in `SUPPORTED_LOCALES`.

## Step 3: Synchronize Desktop Tray Strings

Mirror the new language in [`desktop/src/i18n.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/desktop/src/i18n.ts) by adding the code to the `NATIVE_LOCALES` tuple and providing translated strings in the `STRINGS` record. This ensures the system tray menu renders correctly when users run the desktop application.

```ts
// desktop/src/i18n.ts
export const NATIVE_LOCALES = ['en', 'zh-CN', 'fr', 'es', 'pt-BR', 'it', 'de'] as const;

const STRINGS: Record<NativeLocale, Strings> = {
  // ... existing locales ...
  de: {
    tooltip: 'FreeLLMAPI — lokaler LLM Router',
    runningOn: 'Läuft auf {addr}',
    openDashboard: 'Dashboard öffnen',
    quitApp: 'FreeLLMAPI beenden',
  },
};

```

The `nativeStrings()` function bundles these strings for the file-based pop-over renderer. No additional code changes are required beyond ensuring all keys from the English block are translated.

## Using Translations in Components

Access translated strings using the `useI18n` hook throughout your React components:

```tsx
import { useI18n } from '@/i18n/I18nProvider';

function Header() {
  const { t } = useI18n();
  return <h1>{t('dashboard.welcome')}</h1>;
}

```

The `t()` function handles key lookup and interpolation automatically based on the currently selected locale.

## Rebuilding and Testing

After adding the translation files:

1. Restart the development server with `npm run dev` to test the dashboard changes
2. Build the desktop application with `npm run desktop:dist` to verify tray menu translations
3. Test automatic detection by changing your browser or system language settings
4. Verify manual toggling via the **⋯** menu in the dashboard header

The UI will display the new language immediately when the browser or system language matches your added locale code.

## Summary

- **Create** a flat JSON file in `client/src/i18n/locales/<lang>.json` with dot-notation keys matching the English source
- **Register** the import and locale code in `SUPPORTED_LOCALES` within [`client/src/i18n/I18nProvider.tsx`](https://github.com/tashfeenahmed/freellmapi/blob/main/client/src/i18n/I18nProvider.tsx)
- **Synchronize** the desktop tray by adding the locale to `NATIVE_LOCALES` and `STRINGS` in [`desktop/src/i18n.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/desktop/src/i18n.ts)
- **Access** translations via the `useI18n()` hook and its `t()` function in React components

## Frequently Asked Questions

### Does FreeLLMAPI require external i18n libraries?

No. According to the source code in `tashfeenahmed/freellmapi`, the i18n layer uses zero external dependencies. The implementation relies on native `navigator.language` detection, TypeScript tuples for type safety, and a lightweight context provider pattern rather than libraries like react-i18next or i18next.

### How does the dashboard automatically detect the user's language?

The `detectLocale()` function in [`client/src/i18n/I18nProvider.tsx`](https://github.com/tashfeenahmed/freellmapi/blob/main/client/src/i18n/I18nProvider.tsx) (lines 39-58) reads `navigator.language` from the browser or Electron environment and maps it to the nearest supported locale in `SUPPORTED_LOCALES`. If no match exists, the system falls back to English (`en`) automatically.

### Why must I update [`desktop/src/i18n.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/desktop/src/i18n.ts) separately from the web locale?

The desktop tray UI runs in a separate Electron context with its own renderer process. The `nativeStrings()` helper in [`desktop/src/i18n.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/desktop/src/i18n.ts) bundles translations for the system tray menu and pop-over interfaces, which exist outside the React component tree. Synchronizing both files ensures consistent branding across the web dashboard and native desktop application.

### What format should translation keys follow?

FreeLLMAPI uses flat dot-notation keys (e.g., `nav.models`, `premium.renewsOn`) within nested JSON objects. Keys support interpolation via curly braces like `{date}` or `{addr}`. Copy the complete key set from [`client/src/i18n/locales/en.json`](https://github.com/tashfeenahmed/freellmapi/blob/main/client/src/i18n/locales/en.json) to ensure full coverage, as missing keys will render as `undefined` in the UI.