پلاگینِ دوجهته (Bidi/RTL) برای اوپنکد — درست کردن نمایش متنهای ترکیبی فارسی/عربی/عبری + انگلیسی در ترمینال، دسکتاپ و وب اوپنکد
زبانها: 🇮🇷 فارسی · 🇬🇧 English · 🇸🇦 العربية
👤 سازنده: سینا خانزاده · آیدی: programmer_1998 (دیسکورد / تلگرام / اینستاگرام) · ایمیل: khanzadeh.1377@gmail.com
وقتی داخل یک جملهٔ فارسی/عربی یک کلمه یا شناسهٔ انگلیسی قرار میگیرد — مثل SINA، یک نام فایل، یک مسیر یا نام پکیج — رابط اوپنکد بدون الگوریتم بیدی UAX #9 ترتیب کلمات را به هم میریزد:
نوشتهی شما (ترتیب منطقی): سلام من SINA هستم
رندر ناقص (بدون حل): هستم SINA سلام من
رندر درست (خروجیِ صفحه): هستم SINA من سلام ← از راست به چپ = «سلام من SINA هستم»
این مشکل هم در ترمینال/TUI، هم در اپ دسکتاپ و هم در رابط وب اوپنکد وجود دارد و هنگام تایپ پرامپت (کادر ورودی) و نمایش پیامها و پاسخهای مدل دیده میشود.
این پروژه مشکل را در دو لایهی مکمل حل میکند:
| لایه | ابزار | چه چیزی را درست میکند |
|---|---|---|
| ۱) لایهٔ متن (سرویس اوپنکد) | پلاگین server (v1) یا ./v2 (v2) |
پیامهای ارسالشدهٔ کاربر، پیامهای تاریخی، پاسخ مدل و خروجی ابزارها — با «ایزولههای بیدی» (RLI / LRI / PDI) مطابق UAX #9. از اوپنکد 1.18.31 به بعد، رندر ترمینال/دسکتاپ هم جهت را به صورت بومی (dir="auto" + plaintext) تشخیص میدهد؛ پس این لایه روی ترمینال/وب و نگهداشتن توکنهای LTR تمرکز دارد. نسخهٔ v2 (اوپنکد ۲) همان منطق را با API جدید (ctx.session.hook("prompt"/"context"/...)) اجرا میکند |
| ۲) لایهٔ رابط (دسکتاپ) | پچ app.asar (موتور patch/lib/patch-asar.mjs + بستهبند هر سیستمعامل)، که پلاگین سرور هم خودکار اعمال و ترمیمش میکند |
همهٔ سطحهای متنی رابط: کادر تایپ زندهٔ پرامپت، پیامهای چت، عنوان تب/جلسهها و تبهای ترمینال، منوها، دیالوگها، توستها، تنظیمات، درخت فایل، نام فایلهای review، ضمیمهها، پرمیشنها و سؤالها — با تزریق dir="auto" + unicode-bidi: plaintext به app.asar (کد، دیف و خروجی شل عمداً LTR میمانند) — بههمراه فونت وزیرمتن (Vazirmatn) برای کل متن رابط |
🔍 چطور کار میکند؟
- پلاگین به هوکهای سمت سرور اوپنکد وصل میشود (
chat.message،experimental.text.complete،tool.execute.afterو…). برای هر پاراگراف، «اولین کاراکتر قوی» جهت را تعیین میکند؛ پاراگراف فارسی/عربی را درRLI…PDIمیگذارد و کلمههای انگلیسی/کدِ داخلش (مثلSINA،/path/to/file) را باLRI…PDI«منزوی» میکند تا ترتیبشان قاطی نشود. این کاراکترهای کنترلِ یونیکد در بافر ترمینال و بافتِ مدل میتوانند اثر نامطلوب بگذارند (مثل نویز^هنگام رندر سلولبهسلول)، پس این تزریق اختیاری است (گزینهٔinlineControls: true). در پیشفرض، پلاگین هیچ کاراکتر کنترلی تزریق نمیکند و حتی کاراکترهای جهتدارِ چسبیده به متن (مثل LRM/RLM از کپیکردن وب) را حذف میکند. - پچ دسکتاپ یک
<style>و<script>بهout/renderer/index.htmlداخلapp.asarاضافه میکند. چون از 1.18.31 به بعد خودِ اوپنکد پیامها و پاسخها را بومی درست میکند، پچ فقط جاهایی را میگیرد که اپ هنوز ندارد: کادر تایپ زنده ([data-component="prompt-input"]و[data-component="prompt-input-v2"]) و عنوان تبها ([data-slot="tab-title"]،[data-slot="terminal-tab-title"]) — با اعمالdir="auto"+ plaintext روی آنها. همین پچ یک@font-faceاز فونت وزیرمتن را هم بهصورت base64 داخل همان صفحه جاسازی میکند و با--font-family-sansکل رابط را وزیرمتن میکند (بدون نیاز به نصب فونت روی سیستم یا اینترنت). - هیچ هوکی به رندرر دسکتاپ نمیرسد: پلاگین سرور فقط هوکهای سرور را دارد، پلاگین CLI فقط ترمینال است، و سیستمِ «GUI extensions» اوپنکد در نسخهٔ منتشرشده فقط برای buildهای توسعه فعال است. پس تنها راه تغییر رابط دسکتاپ، پچ
app.asarاست — و پلاگین سرور همان پچ را خودکار اعمال و بعد از هر آپدیت ترمیم میکند (پایین را ببینید).
- ✅ رندر درست پاراگرافهای ترکیبی فارسی/عربی/عبری/اردو + انگلیسی
- ✅ ایزولهکردن کلمهها و شناسههای انگلیسی وسط متن راستچین (
SINA،API، مسیرها) - ✅ حفاظت کامل از کد — بلوکهای سهبکتیک، کد تورفته و
inline codeدستنخورده میمانند - ✅ پیامهای قبلی (تاریخچهٔ چت) هم هنگام لود تصحیح میشوند
- ✅ تشخیص خودکار جهت با قانون «اولین کاراکتر قوی» + امکان اجبار
rtl/ltr - ✅ راهنمای سیستم به مدل (اختیاری) تا به زبان کاربر پاسخ دهد و کد/مسیرها را LTR نگه دارد
- ✅ ست کردن
OPENCODE_RTL*در environment ابزارها (اختیاری) - ✅ دو دستور وضعیت در TUI:
RTL: Show StatusوRTL: Analyze Sample(در v2 هم با API جدید CLI) - ✅ پچ دسکتاپ برای هر سه سیستمعامل (لینوکس / مک / ویندوز) با بکآپ خودکار، تشخیص بومیسازی
app.asar.unpackedو قابلیت بازگشت (--unpatch) - ✅ پوشش کامل رابط دسکتاپ: چت و کامپوزر، تبهای جلسه و ترمینال، منوها، دیالوگها، توستها، تنظیمات، درخت فایل و مرورگر فایل، پنل review، ضمیمهها، پرمیشنها و سؤالها — با محافظت LTR برای کد/دیف/خروجی شل
- ✅ فونت وزیرمتن (Vazirmatn, OFL) بهصورت جاسازیشده در رابط دسکتاپ (بدون نیاز به نصب فونت)
- ✅ خودترمیمی: پلاگین سرور پچ دسکتاپ را خودکار اعمال میکند و بعد از هر آپدیت دسکتاپ دوباره ترمیم میکند (بدون اجرای دستی)
- ✅ سازگار با اوپنکد 1.19.0 و 2.x (ورودی
./v2برای سرور و./v2/tuiبرای CLI)
کادر تایپ پرامپت وقتی هنوز ارسال نکردهاید، توسط UI اوپنکد رندر میشود و هیچ هوک پلاگینی به بافرِ زندهی آن دسترسی ندارد. برای همین:
- در اپ دسکتاپ → این بخش را پچ
app.asarحل میکند (موتورpatch/lib/patch-asar.mjsبههمراه بستهبند هر سیستمعامل). چون هیچ پلاگین/افزونهای به رندرر دسکتاپ دسترسی ندارد، پلاگین سرور خودش این پچ را اعمال و خودترمیمی میکند — اگر دوست ندارید پلاگین به فایلهای اپ دست بزند، آپشنdesktop: falseرا بگذارید و دستی نصب کنید. - در ترمینال/TUI → سلولبهسلول توسط
@opentui/coreرندر میشود و حل نهایی آن با خود اوپنکد است؛ اما بهمحض ارسال، پیام شما و پاسخ مدل با لایهٔ ۱ درست میشود. - بهمحض ارسال، همهجا (ترمینال، دسکتاپ، وب) درست است.
پلاگینها را فقط هنگام استارت اوپنکد بارگذاری میکند. بعد از هر تغییر کانفیگ، اوپنکد را ببندید و دوباره باز کنید.
git clone git@github.com:programmer-1998/RTL_OpenCode.git
cd RTL_OpenCode
npm ci && npm run buildسپس آدرس پوشه (یا فایل dist/server.js) را در کانفیگ بدهید:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"/home/<user>/RTL_OpenCode",
{ "language": "auto", "isolateToolOutput": "off" }
]
]
}💡 بهجای مسیر مطلق میتوانید
./RTL_OpenCode(نسبت به پوشهٔ کانفیگ) یاfile:///...بدهید؛ هر سه فرمت معتبر است.
پلاگین را داخل یکی از پوشههای زیرِ خودِ پروژه قرار دهید تا اوپنکد بدون کانفیگ پیدایش کند:
.opencode/plugin/rtl/package.json
.opencode/plugin/rtl/dist/…
در v2 کلید plugin به plugins تغییر کرده و پلاگین TUI جدا در cli.json تنظیم میشود:
// opencode.jsonc
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{ "package": "opencode-rtl-fix", "options": { "language": "auto" } }
]
}// cli.json (برای دستورهای RTL در ترمینال)
{
"plugins": ["opencode-rtl-fix"]
}این پکیج هر دو API را دارد: ورودی ./v2 (سرور v2 با ctx.session.hook) و ./v2/tui (دستورهای CLI). نگاشت هوکها: chat.message → prompt، system.transform → context/compaction/generate/title، tool.execute.after → tool.hook("execute.after")، shell.env → shell.hook("create.before").
💡 لایهٔ ۱ (پلاگین سمت سرور که با جاوااسکریپت نوشته شده) روی لینوکس، مک و ویندوز کاملاً یکسان کار میکند. فقط پچ دسکتاپ (لایهٔ ۲) برای هر سیستمعامل فایل جداگانه دارد — پایین را ببینید.
| آپشن | پیشفرض | توضیح |
|---|---|---|
enabled |
true |
روشن/خاموش کل پلاگین |
language |
"auto" |
auto یا none یا fa / ar / he / ur (اجبار زبان) |
systemGuidance |
true |
افزودن راهنمای RTL به سیستمپرامپت (یا متن دلخواه) |
isolateUserMessages |
"auto" |
off / auto / always — ایزولهکردن پیام کاربر |
isolateAssistantText |
"auto" |
ایزولهکردن پاسخ مدل |
isolateToolOutput |
"off" |
ایزولهکردن خروجی ابزارها (اگر کپی/پیست دقیق مهم است خاموش بماند) |
inlineControls |
false |
تزریق نکردن کاراکترهای کنترلِ نامرئی (RLI/LRI/PDI) به متن. در پیشفرض، پلاگین کاراکترهای جهتدارِ موجود در متن را هم حذف میکند تا بافر ترمینال و بافت مدل آلوده نشود (پیشگیری از نویز ^). برای ایزولهکردن دستی: true |
minRtlRatio |
0.2 |
حداقل نسبت کاراکتر RTL برای تشخیص خودکار |
minRtlCharacters |
2 |
حداقل تعداد کاراکتر RTL |
digitMode |
"preserve" |
تبدیل ارقام: preserve / latin / arabic-indic / eastern-arabic |
forceDirection |
"auto" |
اجبار جهت ایزوله: auto / rtl / ltr |
alignRtlParagraphs |
false |
راستچین بصری در ترمینال با padding (مقدار خام متن را هم عوض میکند؛ فقط TUI) |
rtlWrapColumn |
96 |
ستون شکستن سطر برای alignment |
rtlAlignColumn |
96 |
ستون راستچینی |
directionEnv |
true |
ست کردن OPENCODE_RTL* در environment ابزارها |
desktop |
true |
نصب/ترمیم خودکار پچ اپ دسکتاپ (app.asar + فونت وزیرمتن) از داخل پلاگین. برای دستنزدن به فایلهای اپ: false |
debug |
false |
لاگ از طریق client.app.log() |
نکتهٔ جهت: جهت بر اساس «اولین کاراکتر قوی» است؛ جملهای که با فارسی شروع شود RTL و جملهای که با انگلیسی/کد شروع شود LTR در نظر گرفته میشود. برای اجبار همهچیز به راستچین:
"forceDirection": "rtl".
اگر هنگام رندر متن فارسی در TUI، کاراکترهای اضافهای مثل ^^^^^ میبینید، عاملش کاراکترهای نامرئیِ یونیکد (isolateهای بیدی مثل U+2066/U+2067/U+2069 یا LRM/RLMِ چسبیده به متنِ کپیشده از وب) است که یک رندررِ سلولبهسلولِ سادهلوح آنها را درست نمیفهمد. راهحل این پلاگین:
- از این نسخه به بعد، کاراکترهای کنترلی بهطور پیشفرض تزریق نمیشوند و کاراکترهای جهتدارِ موجود هم از متن جاری حذف میشوند (
inlineControls: false). - اگر دستی
inlineControls: trueگذاشتهاید و نویز دیدید، آن را خاموش کنید. - جلسههای قدیمی که با نسخههای قبلی ساخته شدهاند ممکن است هنوز این کاراکترها را در متنِ ذخیرهشده داشته باشند؛ یک جلسهٔ جدید بسازید.
اپ دسکتاپ (Electron) بخشهایی از متن را بدون جهتدهیِ خودکار رندر میکند. پچ یک <style> + <script> به out/renderer/index.html داخل app.asar اضافه میکند تا همهٔ سطحهای متنی — چت و کامپوزر، تبها، منوها، دیالوگها، توستها، تنظیمات، درخت/مرورگر فایل، پنل review، ضمیمهها، پرمیشنها و سؤالها — دقیقاً مثل مرورگرهای مدرن عمل کنند (کد، دیف و خروجی شل عمداً LTR میمانند). همین پچ فونت وزیرمتن را هم بهصورت base64 جاسازی میکند و کل رابط را وزیرمتن میکند (بدون نیاز به نصب فونت روی سیستم یا اینترنت).
🔁 مهم: از نسخهٔ 0.2.0 به بعد، خودِ پلاگین سرور این پچ را اعمال و خودترمیمی میکند؛ یعنی بعد از هر آپدیت دسکتاپ (که
app.asarرا جایگزین میکند) پلاگین دوباره آن را ترمیم میکند. نصب دستی اسکریپتهای زیر فقط برای وقتی است که نمیخواهید پلاگین به فایلهای اپ دست بزند (desktop: false)، یا میخواهید همین حالا (بدون ریاستارت) اعمال شود.
پچ از یک موتور مشترک جاوااسکریپت استفاده میکند (patch/lib/patch-asar.mjs) که روی هر سه سیستمعامل یکسان کار میکند؛ فقط «بستهبند» (wrapper) مخصوص هر سیستمعامل، مسیر اپ را پیدا و دسترسی لازم (sudo / UAC) را میگیرد:
| سیستمعامل | فایل نصب | پیشنیاز |
|---|---|---|
| 🐧 لینوکس | patch/linux/desktop-rtl.sh |
node 20+ و sudo |
| 🍎 مک | patch/macos/desktop-rtl.sh |
node 20+ و sudo |
| 🪟 ویندوز | patch/windows/desktop-rtl.ps1 |
node 20+ |
sudo bash patch/linux/desktop-rtl.shsudo bash patch/macos/desktop-rtl.sh💡 اگر بعد از پچ، اپ بالا نیامد (امضای کد شکسته شده)، دوباره امضا کنید:
sudo codesign --force --deep --sign - "/Applications/OpenCode.app"
PowerShell را باز کنید و اجرا کنید:
powershell -ExecutionPolicy Bypass -File patch\windows\desktop-rtl.ps1با پنجرهٔ UAC موافقت کنید. (یا مستقیماً روی patch\windows\desktop-rtl.cmd دوبار کلیک کنید.)
- یک بکآپ از
app.asarدر همانجا (app.asar.bak-rtl) میسازد؛ app.asarرا باز میکند،index.htmlرا پچ میکند (جهتدهی +@font-faceوزیرمتن)، دوباره میبندد؛- چیدمان
app.asar.unpacked(ماژولهای native مانندnode-pty) را یکیبهیکی با نسخهٔ قبلی مقایسه و تأیید میکند تا چیزی نشکند؛ - یک فایل مهر (
app.asar.rtl-stamp) مینویسد که نسخهٔ پچ را نگه میدارد؛ پلاگین با همین مهر میفهمد پچ تازه است یا نه، و نسخههای قدیمیِ پچ را ارتقا میدهد (بدون پچ دوباره روی پچ).
سپس اوپنکد دسکتاپ را ببندید و دوباره باز کنید.
# لینوکس / مک
sudo bash patch/linux/desktop-rtl.sh --unpatch
sudo bash patch/macos/desktop-rtl.sh --unpatch# ویندوز
powershell -ExecutionPolicy Bypass -File patch\windows\desktop-rtl.ps1 -Unpatchاپ از خودکار-آپدیت استفاده میکند و آپدیت فایل پچشده را جایگزین میکند. از نسخهٔ 0.2.0 به بعد نیازی به اجرای دستی نیست:
- پلاگین سرور (که همراه دسکتاپ/CLI بالا میآید) هنگام لود، وجود و تازگی پچ را چک میکند.
- چون
app.asarدر حین اجرا قفل است، پلاگین اول قابلنوشتنبودن فایل را امتحان میکند و فقط وقتی اپ بسته است پچ میزند؛ در فاصلهٔ اجرا هر ۲ دقیقه دوباره تلاش میکند. - پس: پلاگین را نصب کنید → اپ دسکتاپ را ببندید → دفعهٔ بعد که بازش کنید پچشده است. بعد از هر آپدیت هم همینطور خودش ترمیم میشود.
اگر نمیخواهید پلاگین به فایلهای اپ دست بزند:
"desktop": falseبگذارید و از اسکریپتهای دستی زیر استفاده کنید. هیچکدام از این کارها بهsudo/UAC نیاز ندارد (مگر اپ در مسیر سیستمی نصب شده باشد).
مسیر پیشفرض: لینوکس
/opt/OpenCode/resources/app.asar، مک/Applications/OpenCode.app/Contents/Resources/app.asar، ویندوز%LOCALAPPDATA%\Programs\OpenCode\resources\app.asar. اگر جای دیگری نصب است: لینوکس/مکsudo DESKTOP_ASAR=/path/to/app.asar bash patch/linux (یا macos)/desktop-rtl.sh، ویندوزpowershell ... -Asar C:\path\to\app.asar.
-
اوپنکد را ببندید و دوباره باز کنید (تا پلاگین لود شود).
-
در دسکتاپ، این متن را در کادر تایپ بنویسید — باید طبیعی از راست به چپ و با
SINAوسطش درست دیده شود:سلام من SINA هستم -
یک پرامپت ترکیبی با مسیر/کد بفرستید، مثلاً:
محتوای فایل src/config.ts را بخوان و لیست بدهو بررسی کنید ترتیب
src/config.tsو جملهٔ فارسی جابهجا نشده است. -
در ترمینال، اگر دستور
RTL: Show Statusرا دارید اجرا کنید تا وضعیت پلاگین را ببینید:/rtl.status -
تستهای خودکار:
cd RTL_OpenCode && npm test
npm ci
npm run build # tsc → dist/
npm run typecheck # بررسی تایپها
npm test # build + تستها (شامل موتور پچ دسکتاپ)RTL_OpenCode/
├── src/
│ ├── core.ts ← موتور بیدی: تشخیص جهت، ایزوله، مارکداون/کد، alignment
│ ├── desktop.ts ← گارد خودترمیمی پچ دسکتاپ (اعمال/ترمیم app.asar)
│ ├── server.ts ← هوکهای سمت سرور v1 (1.19)
│ ├── tui.ts ← دستورهای TUI در v1
│ └── index.ts ← خروجیها
├── plugin-v2.ts ← پلاگین سرور v2 (اوپنکد ۲، `./v2`)
├── tui-v2.ts ← پلاگین CLI در v2 (دستورهای RTL، `./v2/tui`)
├── test/core.test.js ← تستها
├── patch/
│ ├── lib/patch-asar.mjs ← موتور مشترک پچ (جاوااسکریپت، همهٔ سیستمعاملها) + گارد خودترمیمی
│ ├── fonts/Vazirmatn-Variable.woff2 ← فونت وزیرمتن (OFL) که base64 جاسازی میشود
│ ├── linux/desktop-rtl.sh ← پچ اپ دسکتاپ لینوکس
│ ├── macos/desktop-rtl.sh ← پچ اپ دسکتاپ مک
│ └── windows/
│ ├── desktop-rtl.ps1 ← پچ اپ دسکتاپ ویندوز
│ └── desktop-rtl.cmd ← اجرای دوبار-کلیک ویندوز
├── examples/opencode.json ← نمونه کانفیگ کامل
└── dist/ ← خروجی بیلد (کامیتشده)
-
.github/workflows/ci.yml— روی push/PR: نصب، typecheck، build، تست در Node 20/22/24 + کنترل همگامبودنdistبا سورس + تست و چک نحوی اسکریپتهای پچهای هر سه سیستمعامل. -
.github/workflows/release.yml— با پوشِ تگِv*:- یک Release در گیتهاب با باندل (zip) +
npm pack(.tgz) میسازد؛ - اگر سکرت
NPM_TOKENتنظیم شده باشد، خودکار در npm هم منتشر میشود (opencode-rtl-fix).
ساخت نسخه:
git tag v0.1.0 && git push origin v0.1.0 - یک Release در گیتهاب با باندل (zip) +
این پروژه توسط سینا خانزاده ساخته شده است.
- 🌐 وبسایت شخصی: sina-khanzadeh.ir
- 📧 ایمیل: khanzadeh.1377@gmail.com
- 🆔 آیدی یکسان در شبکههای اجتماعی:
programmer_1998(دیسکورد · تلگرام · اینستاگرام) - 📦 گیتهاب: programmer-1998/RTL_OpenCode
- opencode — github.com/anomalyco/opencode · opencode.ai
- مشارکتکننده: sadegh19b — فونت جاسازیشدهٔ وزیرمتن، خودترمیمی پچ دسکتاپ و آپشن
desktop(PR #2) - فونت وزیرمتن (Vazirmatn) — ساختهٔ صابر راستیکردار، تحت مجوز SIL OFL 1.1
- پلاگین مرجع opencode-rtl روی npm (نویسنده:
razavioo) که الهامبخش این پیادهسازی بود - مراجع و کارهای مرتبط در مونوریپؤ opencode:
- issue #35319 — متن ترکیبی خراب در دسکتاپ
- issue #32984 — رندر RTL در رابطها
- issue #40286 — RTL/بیدی TUI
- PR #25455 —
dir="auto"برای وب
MIT © سینا خانزاده — sina-khanzadeh.ir · khanzadeh.1377@gmail.com