بین‌المللی‌سازی

متن راست‌به‌چپ و چندجهته، شکل‌دهی حروف عربی و ترجمهٔ پد فوکوس.

⁦Text.js⁩ خط‌های عبری، عربی، فارسی و دیگر خط‌های راست‌به‌چپ را با ترتیب درست و هم‌تراز با شبکهٔ سلول‌ها در DevTools، ترمینال‌ها و آینه‌های DOM رندر می‌کند. این فریم‌ورک الگوریتم دوجهتهٔ Unicode ‏(⁦UAX #9⁩، ⁦Unicode 18.0⁩) را پیاده‌سازی می‌کند و مجموعه‌آزمون‌های رسمی انطباق را می‌گذراند: تمام 770,241 مورد BidiTest.txt (هر ورودی در هر جهت پاراگراف) و تمام 91,707 مورد BidiCharacterTest.txt.

زنده48×6 · booting Turbolog…tsx
preview://bidi:1
import { Box, Text } from "@textjs/react";export default function Page() {  return (    <Box flexDirection="column" padding={1}>      <Text>שלום עולם! זהו טקסט בעברית.</Text>      <Text>مرحبا بالعالم! هذا نص عربي.</Text>      <Text>Order #1234 (<Text color="accent">שלום</Text>) total: 25.50 ₪</Text>      <Text>user <Text dir="auto" color="success">שלום!</Text> replied 3 times</Text>    </Box>  );}

جهت

هر پاراگراف (هر خط از یک <Text> که با \n جدا شده است) جهت پایهٔ خود را از اولین نویسهٔ قوی خود تعیین می‌کند، همان‌طور که dir="auto" در HTML عمل می‌کند. پاراگراف‌های راست‌به‌چپ به‌طور پیش‌فرض راست‌چین هستند.

<Text>שלום</Text>                {/* auto: RTL, right-aligned */}<Text dir="rtl">hello!</Text>     {/* "!hello", right-aligned */}<Text dir="ltr">שלום!</Text>      {/* LTR paragraph: "םולש!" reads correctly left of the "!" */}<Text align="left">שלום</Text>    {/* RTL order, pinned to the left edge */}
alignپاراگراف چپ‌به‌راستپاراگراف راست‌به‌چپ
"start" (پیش‌فرض)چپراست
"end"راستچپ
"left" / "center" / "right"مطابق مقدار نوشته‌شدهمطابق مقدار نوشته‌شده

در یک <Text> تودرتو، ویژگی dir مانند <bdi> در HTML، span را ایزوله می‌کند: محتوای آن مستقل چیده می‌شود و نمی‌تواند ترتیب متن پیرامون خود را تغییر دهد. از آن برای نام‌ها، عنوان‌ها و دیگر متن‌های ارائه‌شده از سوی کاربر با جهت نامشخص استفاده کنید.

<Text>user <Text dir="auto">{name}</Text> replied 3 times</Text>// name = "שלום!"  →  user !םולש replied 3 times   (isolated: "!" ends the name)// without dir     →  user םולש! replied 3 times   ("!" resolves with the Latin text around it)

نحوهٔ رندر

  1. شکستن خطوط با ترتیب منطقی. خطوط دقیقاً مانند متن چپ‌به‌راست، بر اساس عرض سلول شکسته می‌شوند.
  2. بازچینی هر خط. الگوریتم سطوح تعبیه را برای کل پاراگراف تعیین می‌کند (تعبیه‌ها و ایزوله‌سازی‌های صریح، انواع ضعیف و خنثی، پرانتزهای جفت‌شده)، سپس هر خط شکسته‌شده به‌صورت مستقل بازچینی می‌شود (قواعد ⁦L1–L2⁩). پرانتزها و دیگر نویسه‌های آینه‌ای در سطوح راست‌به‌چپ قرینه می‌شوند (⁦L4⁩)، بنابراین (abc) در متن عبری همان (abc) می‌ماند.
  3. شکل‌دهی حروف عربی. حروف شکل‌های بافتی خود (منفصل، ابتدایی، میانی، پایانی) را از بلوک‌های ⁦Arabic Presentation Forms⁩ می‌گیرند که با ترتیب منطقی در هر دنبالهٔ هم‌سطح خط محاسبه می‌شوند (ZWJ و ZWNJ در محل قرارگیری خود محاسبه می‌شوند). هر حرف یک سلول است، شکل‌های پیوسته در هر مرز span (مکان‌نما، تغییر رنگ) حفظ می‌شوند و کنسول هرگز نیازی به شکل‌دهی آن‌ها ندارد.
  4. تثبیت ترتیب. سلول‌ها اکنون گلیف‌ها را با ترتیب نمایشی نگه می‌دارند. مرورگرها و بسیاری از ترمینال‌ها الگوریتم دوجهته را بار دوم روی آن‌ها اجرا می‌کنند، بنابراین درایورها ترتیب را قفل می‌کنند:
خروجینحوهٔ تثبیت ترتیب
DevTools (CssConsoleDriver)spanهای دارای نویسه‌های راست‌به‌چپ در LRO … PDF قرار می‌گیرند (⁦U+202D / U+202C⁩). پاک‌ساز %c در DevTools، ویژگی‌های direction و unicode-bidi را حذف می‌کند.
ترمینال‌ها (AnsiDriver، text run)ردیف‌های دارای متن راست‌به‌چپ برای ترمینال‌هایی که الگوریتم دوجهته را روی هر خط اجرا می‌کنند، در LRO … PDF قرار می‌گیرند. تابع renderToTerminal هنگام ورود یک بار به حالت صریح BDSM در ⁦ECMA-48⁩ تغییر می‌کند (CSI 8 l، که ترمینال‌های مبتنی بر VTE رعایت می‌کنند) و هنگام خروج به حالت ضمنی (CSI 8 h) برمی‌گردد؛ فریم‌های تک‌اجرای renderToAnsi که متن راست‌به‌چپ دارند با CSI 8 l آغاز می‌شوند. برای خروجی دادن سلول‌ها بدون تغییر، مقدار bidi: "none" را ارسال کنید.
آینه‌های DOM (<ConsoleMirror>)آینه دارای direction: ltr است (تا صفحهٔ میزبان راست‌به‌چپ نتواند آن را وارونه کند) و spanهای دارای متن راست‌به‌چپ، ویژگی unicode-bidi: bidi-override می‌گیرند.

کامپوننت‌های <Box title>، <Box bottomTitle> و <Divider title> همین مراحل را طی می‌کنند (تابع drawLogicalText(buffer, x, y, text, …) هر خط منفرد را به این روش رسم می‌کند)، بنابراین برچسب‌های آن‌ها با ترتیب درست و حروف پیوسته نمایش داده می‌شوند.

متن بدون نویسه‌های راست‌به‌چپ از تمام این مراحل عبور نمی‌کند. مدل متن کاملاً چپ‌به‌راست هنگام قطعه‌بندی گرافم‌های آن تشخیص داده می‌شود (یک مقایسه برای هر گرافم) و از مسیر بدون تغییر رسم می‌شود؛ فریم‌های کنسول بایت‌به‌بایت یکسان هستند.

عرض سلول‌ها

پشتهٔ فونت ارائه‌شده "SF Mono", Menlo, "Cascadia Mono", Consolas, monospace است. هیچ‌کدام از این فونت‌ها گلیف عبری یا عربی ندارند، بنابراین مرورگرها برای هر نویسه به فونت جایگزین رجوع می‌کنند. در macOS این فونت ⁦Courier New⁩ است که با عرض ثابت 0.6 em عرضه می‌شود: 7.2012 px برای هر گلیف در اندازهٔ 12 px در برابر 7.2246 px در Menlo. عرض آن ⁦0.3%⁩ کمتر است که برای جابه‌جایی یک ردیف کامل عبری به‌اندازهٔ حدود نیم سلول کافی است.

درایور CssConsoleDriver هر span راست‌به‌چپ را با یک canvas و فونت کنسول اندازه می‌گیرد (همان شکل‌دهی Blink و فونت جایگزینی که DevTools استفاده می‌کند)، سپس به‌اندازهٔ دقیق اختلاف LayoutUnit ‏(1/64 px) به آن فاصلهٔ داخلی می‌افزاید. عرض نهایی span برابر عرض سلول‌های شبکه‌ای می‌شود که می‌پوشاند (گرافم پهنی مانند ایموجی دو سلول محسوب می‌شود). هر span حداکثر نیم سلول جمع می‌شود، بنابراین فونت جایگزین با عرض متغیر (⁦Times New Roman⁩ کنار ⁦Cascadia Mono⁩ در Windows) سرریز می‌کند، به‌جای آنکه span بعدی روی گلیف‌های آن بیفتد. اندازه‌گیری در رابط واقعی DevTools ‏(⁦Chromium 151⁩) روی یک فریم ⁦64×21⁩ شامل خطوط عبری، عربی، ترکیبی و ایموجی در متن عبری و عنوان‌های راست‌به‌چپ کادر انجام شده است (bun bench/devtools/bidi-shot.ts):

فریمانحراف لبهٔ راست بین ردیف‌ها
سلول‌های ثبت‌شده بدون تثبیت14.5 px (DevTools بازچینی و شکل‌دهی مجدد انجام می‌دهد)
تثبیت‌شده، بدون تنظیم عرض2.22 px
درایور ارائه‌شده0.156 px
نمونهٔ کنترل: همان سلول‌ها، با جایگزینی هر گلیف راست‌به‌چپ با x1.67 px

فریم ارائه‌شده در تمام ردیف‌ها با نمونهٔ کنترل تمام‌لاتین مطابقت دارد، جز ردیف ایموجی که در آن بهتر عمل می‌کند: عرض ایموجی‌ها همراه span راست‌به‌چپ پیرامونشان تنظیم می‌شود (0.03 px)، درحالی‌که عرض ردیف ایموجی چپ‌به‌راست نمونهٔ کنترل تنظیم نمی‌شود (1.66 px).

⁦DevTools⁩ هر span دارای %c را با contain: paint رسم می‌کند و هرکدام را جداگانه با پیکسل‌های دستگاه منطبق می‌کند، بنابراین در جایی که دو پس‌زمینه در موقعیتی کسری به هم می‌رسند، ممکن است پیکسل میان آن‌ها به هیچ‌کدام تعلق نگیرد و شکافی باریک به عرض 1 px باقی بماند. در Chrome و Edge، یک span دارای پس‌زمینه آن را 1 px زیر همسایهٔ مات سمت راست گسترش می‌دهد و span با عرض تنظیم‌شده آن پیکسل را به فاصلهٔ داخلی خود می‌افزاید. هیچ‌یک از چهار فریم بالا شکاف باریکی نشان نمی‌دهد.

برای غیرفعال کردن تنظیم عرض، مقدار measureText: false را ارسال کنید یا اندازه‌گیر (text, bold, italic) => px خود را ارائه دهید.

ورودی و ویرایش

کامپوننت‌های <Input> و <Textarea> مکان‌نمای منطقی را حفظ می‌کنند. کلیدهای جهت‌نما در متن به ترتیب ذخیره‌سازی حرکت می‌کنند، بنابراین در متن راست‌به‌چپ، ← از نظر بصری به راست حرکت می‌کند. مکان‌نمای بلوکی روی نویسهٔ منطقی تحت پوشش خود قرار می‌گیرد، هر جا که بازچینی آن را قرار دهد. در یک دنبالهٔ راست‌به‌چپ، این مکان همان سلول سمت چپ نویسهٔ قبلی است. انتخاب نیز یک بازهٔ منطقی است و بنابراین برجسته‌سازی آن می‌تواند سلول‌های ناپیوسته را در خطوط با جهت‌های ترکیبی پوشش دهد. پیمایش افقی در <Input> تک‌خطی از ستون منطقی پیروی می‌کند، بنابراین یک مقدار طولانی راست‌به‌چپ می‌تواند با پیمایش، مکان‌نما را از دید خارج کند. مکان‌نمای روی فاصله یا انتهای متن به شکل یک فاصلهٔ نشکنِ ایزوله رسم می‌شود تا خنثی بماند و هرگز به اعداد دو طرف نپیوندد.

محدودیت‌ها

  • جداکننده‌های پاراگراف. خطوط در \n جدا می‌شوند. ⁦U+2029⁩ و دیگر جداکننده‌های پاراگراف، پاراگراف دوجهته را پایان می‌دهند اما خط جدیدی آغاز نمی‌کنند.
  • تب‌ها. تب‌ها پیش از اجرای الگوریتم دوجهته به فاصله تبدیل می‌شوند، بنابراین مانند فضای سفید (WS) عمل می‌کنند، نه جداکنندهٔ قطعه (S).
  • شکل‌دهی. فقط حروف دارای شکل‌های نمایشی شکل‌دهی می‌شوند: حروف عربی، فارسی، اردو و دیگر حروف در ⁦U+FB50–U+FDFF⁩ و ⁦U+FE70–U+FEFF⁩. لام‌الف به‌صورت دو حرف پیوسته در دو سلول باقی می‌ماند، نه یک ترکیب تک‌گلیفی. خط‌های سریانی، ⁦N'Ko⁩ و دیگر خط‌های پیوسته شکل‌های اسمی خود را حفظ می‌کنند.
  • کپی کردن. متن کپی‌شده از کنسول با ترتیب نمایشی است (همراه با علامت‌های LRO/PDF)، نه ترتیب منطقی.
  • ترمینال‌ها. ترمینال‌هایی که هم BDSM و هم LRO را نادیده می‌گیرند و الگوریتم دوجهتهٔ خود را اجرا می‌کنند (برای مثال با فعال بودن قابلیت دوجهته در ⁦iTerm2⁩)، ممکن است ردیف‌های راست‌به‌چپ را دوباره بازچینی کنند.
  • آینه‌های DOM. کامپوننت <ConsoleMirror> عرض‌ها را تنظیم نمی‌کند، بنابراین دنباله‌های راست‌به‌چپ در آن ممکن است تا ⁦0.3%⁩ عرض خود انحراف داشته باشند.

ترجمهٔ پد فوکوس

کامپوننت <FocusPad> از @textjs/react/dom (که <TextCanvas> نیز آن را رندر می‌کند) به بازدیدکنندگان می‌گوید کنسول را باز کنند، روی صفحه کلیک کنند و کلیدی را فشار دهند. متن آن به‌طور پیش‌فرض انگلیسی است. برای ترجمهٔ آن، پراپ messages را با رشته‌هایی که می‌خواهید جایگزین کنید ارسال کنید. کلیدهایی که حذف می‌کنید متن انگلیسی خود را حفظ می‌کنند که در defaultFocusPadMessages فهرست شده است.

import { FocusPad, type FocusPadMessages } from "@textjs/react/dom";const ja: Partial<FocusPadMessages> = {  openConsole: "コンソールを開く",  step: (n, total) => `${total}ステップ中${n}ステップ目`,  then: (action) => `次にここをクリックして、${action}。`,  shortcutOpens: (shortcut) => <>{shortcut}でコンソールが開きます。</>,};<FocusPad root={root} hints={[{ keys: "↑ ↓", label: "件数" }]} action="↑ ↓を押します" messages={ja} />;

رشته‌ای که مقداری را در خود دارد، تابعی است که کل جمله را برمی‌گرداند، بنابراین ترجمه می‌تواند مقدار را در هر جای آن قرار دهد. مقادیر step، pressShortcut، then، defaultAction و shortcutOpens تابع هستند. تابع step شمارهٔ مرحله و تعداد مراحل را دریافت می‌کند. تابع then پراپ action را دریافت می‌کند یا وقتی action وجود ندارد، متن defaultAction را می‌گیرد. تابع defaultAction کلیدهای اولین راهنما را دریافت می‌کند یا در نبود راهنما، null می‌گیرد. تابع pressShortcut کلیدهای میان‌بر را برای صفحه‌خوان‌ها دریافت می‌کند و shortcutOpens میان‌بر را به‌صورت عناصر <kbd> می‌گیرد.

پراپ messages برچسب‌هایی را که صفحه‌خوان‌ها اعلام می‌کنند و کنترل اندازهٔ کنسول (sizeControl) نیز پوشش می‌دهد. کامپوننت <TextCanvas> همان پراپ messages را می‌پذیرد. کامپوننت <ViewportControl> نیز آن را می‌پذیرد و از کلیدهای کنترل اندازه استفاده می‌کند. نوشته‌های روی کلیدها مانند ⁦⌘⌥J⁩، ⁦Ctrl+Shift+J⁩ و Enter ترجمه نمی‌شوند.

API

import { reorderLine, paragraphDirection, resolveLevels, bidiClass, shapeArabicCodePoints } from "@textjs/core";reorderLine("שלום (abc) 123!");          // "!123 (abc) םולש": one line in display orderreorderLine("مرحبا", "auto", { shape: true }); // presentation forms, display orderparagraphDirection("123 שלום");           // "rtl" (rules P2–P3)

توابع resolveLevels(types, codePoints, direction)، applyLineRules و reorderIndices خود الگوریتم را در دسترس قرار می‌دهند: قواعد ⁦P1–I2⁩، ⁦L1⁩ و ⁦L2⁩.

ویرایش این صفحه در GitHub