Skip to content

🌐 feat: Add Persian Translation with Right-to-Left Layout and Language Switcher - #211

Open
Delpak wants to merge 1 commit into
LibreChat-AI:mainfrom
godarai-ir:feat/i18n-fa
Open

Delpak wants to merge 1 commit into
LibreChat-AI:mainfrom
godarai-ir:feat/i18n-fa

Conversation

@Delpak

@Delpak Delpak commented Oct 30, 2026

Copy link
Copy Markdown

Summary

The admin panel already runs on i18next, but it only ships English and its layout assumes left-to-right text, so a right-to-left language cannot be added by dropping in a translation file. This PR adds Persian (fa) and the RTL groundwork any future RTL locale (Arabic, Hebrew) can reuse.

Language choice

  • fa is registered in src/locales/i18n.ts with supportedLngs, load: 'languageOnly', and detection from localStorage (admin-panel:language) and then the browser.
  • Users can pick the language in three places: a row in the Settings dialog, "Set language" commands in the command menu, and a LanguageSelector toggle on the login page.

Document direction

  • RootDocument renders <html lang dir> from the active language through a new useLanguage() hook.
  • React 19 owns the <html> singleton and resets attributes it did not render, so setting dir only from i18next was wiped on the next render.

Mirrored layout

  • Physical Tailwind classes are now logical: ml/mr → ms/me, left/right → inset-s/inset-e, text-left → text-start, rounded-l → rounded-s, and so on, across about 70 components.
  • styles.css uses logical properties.
  • Drawer slide animations read --drawer-offset, which flips under [dir='rtl']. The scope switch knob mirrors too.

Typography and dates

  • Vazirmatn is bundled through @fontsource-variable/vazirmatn and applies only under :lang(fa). Code and monospace text keep their fonts.
  • Audit-log and user dates format in the UI language. Configuration numbers are unchanged, so they still match what is typed into fields.

Translation

  • All 3,754 keys are translated, and every {{placeholder}} and ${ENV} token is preserved.
  • Two exceptions: com_config_issue_inherited_entry_immutable and com_config_issue_yaml_entry_immutable. In these, {{action}} receives a raw English word (renamed / removed) from src/utils/collections.ts, so the Persian text names both actions instead of interpolating it. Localizing that word upstream would let the placeholder come back.
src/
├── components/LanguageSelector.tsx   new: login-page toggle
├── constants/locale.ts               new: LANGUAGES, storage key, default
├── hooks/language.ts                 new: useLanguage() → { language, direction, setLanguage }
├── types/locale.ts                   new: LanguageCode, LanguageOption
├── locales/
│   ├── i18n.ts                       fa resource, detection, lang/dir sync
│   ├── fa/translation.json           new
│   └── fa.test.ts                    new
├── routes/__root.tsx                 <html lang dir> from useLanguage()
└── styles.css                        logical properties, RTL drawer offset, Persian font

Change Type

  • New feature (non-breaking change which adds functionality)
  • Translation update

Testing

  • bunx vitest run src/locales src/hooks: 194 tests pass. The new src/locales/fa.test.ts checks:
    • fa has no keys outside en.
    • Placeholders match en, except the two keys above.
    • Switching to fa sets dir="rtl", and switching back sets ltr.
    • useLocalize returns Persian after setLanguage('fa').
  • npx tsc --noEmit: no new errors. On Windows, the only error is the existing Transport.tsx / transport.ts casing clash.
  • eslint: clean.
  • The production Docker image (docker build .), run against a LibreChat API: the login page switches to Persian with dir="rtl" and Vazirmatn loaded, the choice persists across reloads, and English stays left-to-right as before.
  • Not covered: authenticated pages were not checked by hand in RTL. In particular, click-ui components that position themselves with physical offsets may still need adjustments.

Test Configuration:

Windows 10, Bun 1.3.14, Docker Desktop. LibreChat API from docker-compose.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

🤖 Generated with Claude Code

…e Switcher

Adds a complete fa locale, a language choice persisted in localStorage
(settings dialog, command menu and login page), and renders <html lang/dir>
from the active language so React 19 does not reset them. Physical-direction
Tailwind classes and CSS properties are converted to logical ones, drawer and
switch animations mirror in RTL, Vazirmatn is bundled locally for Persian,
and audit/user dates format in the UI language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant