Skip to content

Commit 904f862

Browse files
committed
feat(i18n): Add RTL (Right-to-Left) support for Arabic and other RTL languages anomalyco#10908
1 parent 3aaf29b commit 904f862

2 files changed

Lines changed: 59 additions & 2 deletions

File tree

‎packages/app/src/components/prompt-input.tsx‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,34 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
403403
const [composing, setComposing] = createSignal(false)
404404
const isImeComposing = (event: KeyboardEvent) => event.isComposing || composing() || event.keyCode === 229
405405

406+
const [isRTL, setIsRTL] = createSignal(false)
407+
const isRtlText = (text: string) => {
408+
const rtlChars = /[\u0590-\u05FF\u0600-\u06FF]/
409+
let rtlCount = 0
410+
let totalCount = 0
411+
412+
for (const char of text) {
413+
if (char.trim() !== "") {
414+
totalCount++
415+
if (rtlChars.test(char)) {
416+
rtlCount++
417+
}
418+
}
419+
}
420+
421+
if (totalCount < 3) return false
422+
423+
return rtlCount / totalCount > 0.5
424+
}
425+
426+
createEffect(() => {
427+
const text = prompt
428+
.current()
429+
.map((part) => ("content" in part ? part.content : ""))
430+
.join("")
431+
setIsRTL(isRtlText(text))
432+
})
433+
406434
createEffect(() => {
407435
if (!isFocused()) closePopover()
408436
})
@@ -1119,12 +1147,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
11191147
onCompositionStart={() => setComposing(true)}
11201148
onCompositionEnd={() => setComposing(false)}
11211149
onKeyDown={handleKeyDown}
1150+
dir={isRTL() ? "rtl" : "ltr"}
11221151
classList={{
11231152
"select-text": true,
11241153
"w-full pl-3 pr-2 pt-2 pb-11 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
11251154
"[&_[data-type=file]]:text-syntax-property": true,
11261155
"[&_[data-type=agent]]:text-syntax-type": true,
11271156
"font-mono!": store.mode === "shell",
1157+
"text-right": isRTL(),
11281158
}}
11291159
/>
11301160
<Show when={!prompt.dirty()}>

‎packages/ui/src/components/message-part.tsx‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,25 @@ function getDiagnostics(
6969
return diagnostics.filter((d) => d.severity === 1).slice(0, 3)
7070
}
7171

72+
const isRtlText = (text: string) => {
73+
const rtlChars = /[\u0590-\u05FF\u0600-\u06FF]/
74+
let rtlCount = 0
75+
let totalCount = 0
76+
77+
for (const char of text) {
78+
if (char.trim() !== "") {
79+
totalCount++
80+
if (rtlChars.test(char)) {
81+
rtlCount++
82+
}
83+
}
84+
}
85+
86+
if (totalCount < 3) return false
87+
88+
return rtlCount / totalCount > 0.5
89+
}
90+
7291
function DiagnosticsDisplay(props: { diagnostics: Diagnostic[] }): JSX.Element {
7392
const i18n = useI18n()
7493
return (
@@ -653,6 +672,10 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
653672
)
654673

655674
const text = createMemo(() => textPart()?.text || "")
675+
const [isRTL, setIsRTL] = createSignal(false)
676+
createEffect(() => {
677+
setIsRTL(isRtlText(text()))
678+
})
656679

657680
const files = createMemo(() => (props.parts?.filter((p) => p.type === "file") as FilePart[]) ?? [])
658681

@@ -750,7 +773,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
750773
<Show when={text()}>
751774
<>
752775
<div data-slot="user-message-body">
753-
<div data-slot="user-message-text">
776+
<div data-slot="user-message-text" dir={isRTL() ? "rtl" : "ltr"} classList={{ "text-right": isRTL() }}>
754777
<HighlightedText text={text()} references={inlineFiles()} agents={agents()} />
755778
</div>
756779
</div>
@@ -1082,6 +1105,10 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
10821105
})
10831106

10841107
const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
1108+
const [isRTL, setIsRTL] = createSignal(false)
1109+
createEffect(() => {
1110+
setIsRTL(isRtlText(displayText()))
1111+
})
10851112
const throttledText = createThrottledValue(displayText)
10861113
const isLastTextPart = createMemo(() => {
10871114
const last = (data.store.part?.[props.message.id] ?? [])
@@ -1107,7 +1134,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
11071134

11081135
return (
11091136
<Show when={throttledText()}>
1110-
<div data-component="text-part">
1137+
<div data-component="text-part" dir={isRTL() ? "rtl" : "ltr"} classList={{ "text-right": isRTL() }}>
11111138
<div data-slot="text-part-body">
11121139
<Markdown text={throttledText()} cacheKey={part.id} />
11131140
</div>

0 commit comments

Comments
 (0)