Skip to content

i18n: add Persian (fa) as RTL and per-bubble bidi isolation for chat …#815

Open
esbtc wants to merge 1 commit into
fathah:mainfrom
esbtc:fix/rtl-farsi
Open

i18n: add Persian (fa) as RTL and per-bubble bidi isolation for chat …#815
esbtc wants to merge 1 commit into
fathah:mainfrom
esbtc:fix/rtl-farsi

Conversation

@esbtc

@esbtc esbtc commented Jul 3, 2026

Copy link
Copy Markdown

Description This PR fixes Persian (fa) rendering and mixed LTR/RTL chat bubble layout by making two small, focused changes:

Add "fa" to APP_LOCALES and RTL_LOCALES so the app sets document.dir = "rtl" when Persian is the active locale.
Improve per-message bidi handling in the chat UI:
Detect strong RTL characters (Hebrew/Arabic/Persian) in each chat bubble.
Set dir="rtl" / dir="ltr" per bubble, apply unicode-bidi: isolate and align text accordingly so mixed Persian/English messages keep correct visual order.
Why

Persian was not treated as an RTL locale, so switching the app to Persian did not mirror the UI.
Even when the app locale is LTR, individual messages containing Persian could be visually mangled (mixed-direction text displays incorrectly). This change fixes both the global and per-bubble cases.
Files changed

src/shared/i18n/config.ts
Add "fa" to APP_LOCALES and RTL_LOCALES.
src/renderer/src/screens/Chat/MessageRow.tsx
Add looksRtl(text) helper, set per-bubble dir and apply unicode-bidi isolation + textAlign style.
src/renderer/src/assets/main.css
Ensure .chat-bubble uses unicode-bidi: isolate and add dir-based alignment rules.
src/shared/i18n/index.test.ts
Add a unit test asserting getLocaleDirection("fa") === "rtl".
Testing / How to verify

Unit tests

Checkout branch: git fetch origin && git checkout fix/rtl-farsi
Install deps and run tests: npm install npm test
Expect the new i18n test to pass.
Dev smoke test (renderer / chat)

Start dev app: npm run dev
In the app (or in DevTools Console), set the locale to Persian: localStorage.setItem('hermes-locale','fa'); location.reload();
Verify:
document.documentElement.dir === 'rtl'
The UI (sidebar, header, etc.) mirrors appropriately.
Send a mixed message like: "سلام, this is a test" or "لینک: https://example.com/ برای تست"
Verify:
The chat bubble for Persian text has dir="rtl".
The bubble is right-aligned and inline English/URLs remain readable and in logical order.
Alternate check (if you cannot run full app)

Open DevTools for renderer, run the small test injector (previously provided) to apply per-bubble dir and observe behavior.
Notes on backwards compatibility

Non-RTL locales are unaffected.
The change is limited and opt-in: adding "fa" simply classifies Persian as RTL; per-bubble handling only sets dir on bubbles based on their content.
A unit test was added to prevent regression.
Implementation choices & possible follow-ups

unicode-bidi: isolate was chosen to isolate directionality of each bubble from neighboring UI. If we want to experiment, unicode-bidi: plaintext is an alternative that uses the first-strong-character heuristic; either is viable and can be adjusted if reviewers prefer.
Fonts: the project already includes Cairo scoped for Arabic-range glyphs; if Persian-specific glyph or shaping issues appear we can add/adjust a Persian-friendly font.
If maintainers prefer server-side or i18n-lib-driven detection, we can move the detection logic into a shared helper.
PR checklist (suggested)

Adds "fa" to APP_LOCALES and RTL_LOCALES
Adds per-bubble bidi handling and isolation
Adds unit test for getLocaleDirection("fa")
Manual smoke-tested in dev (instructions above)
How I tested locally

Added the changes on branch fix/rtl-farsi (esbtc fork), executed unit tests for i18n, and validated per-bubble dir behavior via DevTools injection in the renderer.

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Persian RTL support and per-message bidi handling for chat. The main changes are:

  • fa added to the app locale and RTL locale lists.
  • Chat bubbles now derive a dir value from message content.
  • Bubble CSS now adds bidi isolation and direction-based text alignment.
  • A unit test now checks Persian locale direction.

Confidence Score: 4/5

The changed locale and stylesheet paths need fixes before merging.

  • The Persian locale entry can fail type checking and is not backed by the inspected locale metadata.
  • The main renderer stylesheet now omits large sets of existing app styles and themes.
  • The chat direction heuristic can mis-render common mixed-language messages.

src/shared/i18n/config.ts, src/shared/i18n/types.ts, src/renderer/src/assets/main.css, src/renderer/src/screens/Chat/MessageRow.tsx

Important Files Changed

Filename Overview
src/shared/i18n/config.ts Adds Persian to the locale arrays, but the locale type and related metadata/resources are not updated.
src/renderer/src/assets/main.css Adds chat bubble bidi rules while removing most existing app, theme, font, and layout CSS.
src/renderer/src/screens/Chat/MessageRow.tsx Adds per-bubble direction detection and inline bidi styling, with an any-RTL-character heuristic.
src/shared/i18n/index.test.ts Adds a direction test for Persian but does not cover full locale integration.

Reviews (1): Last reviewed commit: "i18n: add Persian (fa) as RTL and per-bu..." | Re-trigger Greptile

Comment thread src/shared/i18n/config.ts
"tr",
"zh-CN",
"zh-TW",
"fa",

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.

P1 Locale Type Excludes Persian

APP_LOCALES is typed as AppLocale[], but AppLocale still does not include "fa". A normal TypeScript build can reject this array, and the same new value is also missing from the locale resources and native-name map used by the language picker.

color: var(--text-muted);
margin: 0;
}

/* ========================================================================

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.

P1 Main Stylesheet Is Truncated

main.css is the renderer stylesheet, but this hunk replaces most of it with a placeholder comment. Existing screens still emit deleted classes like layout, sidebar, button, setup, and install styles, and the theme picker still exposes theme ids whose CSS blocks were removed, so normal app screens can render unstyled or with missing theme variables.

const bubbleTime = isTimeValid ? formatBubbleTime(epochMs) : null;

// Determine per-bubble direction to handle mixed LTR/RTL content gracefully.
const bubbleDir = looksRtl(bubbleContent ?? "") ? "rtl" : "ltr";

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.

P1 Embedded RTL Flips Bubble

looksRtl makes the whole bubble RTL when any RTL character appears anywhere in the message. An LTR message like Can you translate "שלום"? or an agent markdown reply with one Persian word after a code block will inherit dir="rtl" and textAlign: "right" across links, lists, tables, and code, causing readable LTR content to reorder or right-align incorrectly.

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