How to Add Multi-Language Support to the FreeLLMAPI Dashboard

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, and synchronizing the translations in 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. 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. 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:

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

Reference existing files like en.json and 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 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.

// 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 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.

// 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:

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
  • Synchronize the desktop tray by adding the locale to NATIVE_LOCALES and STRINGS in 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 (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 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 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 to ensure full coverage, as missing keys will render as undefined in the UI.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →