Skip to content

[Feature]: Internationalization (i18n) — German and English language support with community translation template #109

@scheilch

Description

@scheilch

Problem or Motivation

OpenCloudTouch is used internationally — the SoundTouch platform had a global user base, and the community around it spans multiple countries. Currently, the entire UI is English-only. German-speaking users (a significant portion of the community, as seen in discussions and issues) have no native-language experience.

There is no mechanism to:

  • Display UI labels, messages, and explanations in the user's preferred language
  • Allow community members to contribute translations
  • Switch the language without technical knowledge

This creates an unnecessary barrier for non-English speakers, especially for setup, error messages, and device configuration flows.

Desired Behaviour

1. Language Selector in the UI

Add a language selector dropdown in the top navigation bar (or Settings page header) that allows users to switch between supported languages at runtime.

Design requirements:

  • Small flag icon + language name in the native language (e.g. Deutsch, English)
  • Dropdown placement: top-right nav bar, next to theme toggle, or in Settings General
  • Selected language persists in localStorage across sessions
  • Falls back to browser navigator.language on first visit (auto-detect)
  • Instant switch without page reload

Initially supported languages:

Code Native name
en English
de Deutsch

2. All UI text must be externalized

Every user-visible string must be moved to translation files:

  • Page titles and headings
  • Button labels (Discover, Add Device, Save, Cancel, ...)
  • Form labels and placeholders
  • Status messages and notifications (toast messages)
  • Error messages (connection errors, validation errors)
  • Empty state messages
  • Confirmation dialogs
  • Settings labels and descriptions
  • Device card labels (Volume, Now Playing, Presets, ...)
  • Navigation items

3. Translation file format

Use JSON translation files structured by namespace, placed in apps/frontend/src/i18n/locales/:

apps/frontend/src/i18n/
  locales/
    en.json       <- source of truth (English)
    de.json       <- German translation
  index.ts        <- i18n init and export

Example en.json:

{
  "common": {
    "save": "Save",
    "cancel": "Cancel",
    "loading": "Loading...",
    "error": "An error occurred"
  },
  "devices": {
    "discover": "Discover Devices",
    "noDevices": "No devices found. Start discovery or add a device manually.",
    "volume": "Volume",
    "nowPlaying": "Now Playing"
  },
  "settings": {
    "title": "Settings",
    "manualIps": {
      "label": "Manual Device IPs",
      "placeholder": "192.168.1.100",
      "add": "Add IP"
    }
  },
  "errors": {
    "connectionFailed": "Could not connect to the device.",
    "discoveryFailed": "Discovery failed. Check your network settings."
  }
}

4. Recommended i18n library: react-i18next

Industry standard for React:

  • Hooks-based: const { t } = useTranslation()
  • Lazy loading of locale files supported
  • Pluralization, interpolation, and formatting built-in
  • MIT license, actively maintained
// Before
<button>Discover Devices</button>

// After
const { t } = useTranslation();
<button>{t('devices.discover')}</button>

5. Translation Contributing Template

A dedicated GitHub Issue template translation_contribution.yml should be added to .github/ISSUE_TEMPLATE/ to allow community members to contribute translations for new languages.

The template lists all translatable strings in English (source of truth from en.json) as a checklist. Contributors:

  1. Open an issue using the template
  2. Select the target language
  3. Provide translations inline in the issue
  4. A maintainer creates the locale JSON file and merges it

This lowers the contribution barrier — no fork/PR knowledge required for non-technical translators.

Acceptance Criteria

  • en.json contains 100% of all UI strings (no hardcoded English text left in components)
  • de.json contains 100% German translation
  • Language selector dropdown renders flag emoji + native language name (Deutsch, English)
  • Language preference is saved in localStorage and restored on next visit
  • Browser language auto-detection on first visit (navigator.language)
  • Instant language switch without page reload
  • All existing unit tests still pass (no regression)
  • translation_contribution.yml issue template added to .github/ISSUE_TEMPLATE/
  • Frontend coverage >= 80% maintained
  • en.json is documented in CONTRIBUTING.md as source of truth for translations
  • ADR docs/adr/007-i18n.md documents the library decision

Alternatives Considered

  • Separate URL per language (e.g. /de/) — too complex for a single-user household app; localStorage is sufficient
  • Auto-translate via browser — uncontrolled, inconsistent results; no fallback for OCT-specific terminology
  • Hardcode a second language in components — does not scale, creates maintenance nightmare

Additional Context

  • Initial scope: EN + DE only. Additional languages via community contribution (translation_contribution issue template).
  • Backend API responses (device names, preset titles) come from the SoundTouch device itself — no translation needed.
  • Error messages from backend (detail field) can be translated on the frontend using error codes — follow-up.
  • Estimated effort: M (medium) — mostly mechanical string extraction + library setup.

Affected Area

Frontend / UI

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions