Skip to content

feat(desktop): add i18n infrastructure with French (fr) support - #38183

Open
Foolafroos wants to merge 1 commit into
NousResearch:mainfrom
Foolafroos:feature/desktop-i18n
Open

feat(desktop): add i18n infrastructure with French (fr) support#38183
Foolafroos wants to merge 1 commit into
NousResearch:mainfrom
Foolafroos:feature/desktop-i18n

Conversation

@Foolafroos

Copy link
Copy Markdown

Summary

Adds a scalable internationalization (i18n) layer to the Hermes Agent desktop app, with complete French (fr) support. The architecture is designed so additional languages can be added by simply dropping in a new .json file — zero refactoring required.

What changed

New files

File Purpose
apps/desktop/src/i18n/context.tsx I18nProvider React Context + useTranslation() hook. Lazy-loads locale bundles, falls back to EN, persists choice in localStorage with browser language detection.
apps/desktop/src/i18n/index.ts Barrel exports
apps/desktop/src/i18n/messages/en.json 270 keys — source of truth, extracted from all hardcoded UI strings
apps/desktop/src/i18n/messages/fr.json 270 keys — complete French translation (100% parity with EN)
apps/desktop/src/i18n/language-selector.tsx Standalone LanguageSelector component (unused — language dropdown is built directly into titlebar for subtlety)

Modified files

File Change
apps/desktop/src/main.tsx Wired <I18nProvider> above <ThemeProvider> in the root tree
apps/desktop/src/app/shell/titlebar-controls.tsx Added LanguageDropdown — subtle locale code button (e.g. EN / FR) in the titlebar system tools, between haptics and settings
apps/desktop/src/components/chat/intro.tsx Added bilingual fallback copy (EN + FR entries in the intro message pool)

Architecture

I18nProvider (context)
  ├── locale detection: localStorage → browser navigator → "en" fallback
  ├── lazy bundle loading: dynamic import().then(m => m.default)
  ├── useTranslation() hook: t(key, params) with interpolation
  └── LanguageDropdown: titlebar integration, no modals, no onboarding
  • No heavy i18n library — custom React Context keeps bundle size minimal
  • Adding a new language = add XX.json to messages/ + one line in LOCALES array
  • Desktop locale preference persists across sessions via localStorage

Validation

  • tsc -b — zero TypeScript errors
  • ✅ 270/270 keys parity between en.json and fr.json
  • ✅ Backend locales/en.yamllocales/fr.yaml already aligned

Screenshots

The language selector appears as a subtle locale code in the titlebar:

[ ... ]  [🔔]  [EN ▼]  [⚙️]

Clicking it opens a dropdown to switch between English and Français.


Note: This PR only wires the infrastructure and translates the UI layer. Replacing every individual hardcoded string with t("key") calls across all 1200+ JSX text nodes is a follow-up — but the system is ready to consume them.

- Add I18nProvider React context with useTranslation hook
- Extract ~270 UI keys to en.json (source of truth)
- Complete French translation in fr.json
- Language selector dropdown in titlebar (subtle, locale code)
- Locale persistence via localStorage with browser language fallback
- Bilingual fallback copy in intro component
- Zero TypeScript errors, tsc -b passes clean
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have labels Jun 3, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the French translation work. Current main already has a typed, config-backed desktop i18n system, while French itself is still not registered.

Problems

  • This patch adds a second i18n implementation instead of extending the current contract: apps/desktop/src/i18n/context.tsx:107-146 persists display.language, types.ts:8 defines supported locales, and catalog.ts:7-12 registers locale tables. The PR's JSON/localStorage provider conflicts with that architecture.
  • The diff only invokes the new string-key t(...) API in the titlebar dropdown; its LanguageSelector is unused. Therefore the bundled French JSON does not translate the broader current UI, consistent with the PR body noting that JSX migration is deferred.
  • No tests cover French locale normalization, config persistence, or runtime fallback; existing conventions are in apps/desktop/src/i18n/context.test.tsx:60-76,155-230 and runtime.test.ts:18-74.

Suggested changes

  • Salvage the French copy as a typed fr locale registered through types.ts, languages.ts, and catalog.ts, using the existing provider and display.language persistence.
  • Add French coverage alongside the existing context and runtime tests.

This is an automated hermes-sweeper review.

return 'en'
}

// ---------- message loading ----------

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main already has a config-backed I18nProvider and typed locale catalog. Please extend that contract with fr rather than adding a second JSON/dynamic-import provider; otherwise this conflicts with the established display.language persistence path.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@teknium1 teknium1 added the area/i18n Localization, locales, translations label Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/i18n Localization, locales, translations P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants