अंतरराष्ट्रीयकरण

दाएं-से-बाएं और मिली-जुली दिशा वाला टेक्स्ट, अरबी शेपिंग और फ़ोकस पैड का अनुवाद।

Text.js, DevTools, टर्मिनल और DOM मिरर में हिब्रू, अरबी, फ़ारसी और दाएं से बाएं लिखी जाने वाली अन्य लिपियों को सही क्रम में, सेल ग्रिड के साथ संरेखित करके रेंडर करता है। यह Unicode Bidirectional Algorithm (UAX #9, Unicode 18.0) लागू करता है और आधिकारिक अनुरूपता टेस्ट सूट पास करता है: BidiTest.txt के सभी 7,70,241 केस (हर पैराग्राफ़ दिशा में हर इनपुट) और BidiCharacterTest.txt के सभी 91,707 केस।

लाइव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 से अलग होती है) अपने पहले स्ट्रॉन्ग कैरेक्टर से अपनी बेस दिशा चुनता है, जैसे HTML में dir="auto" करता है। दाएं से बाएं वाले पैराग्राफ़ डिफ़ॉल्ट रूप से दाईं ओर संरेखित होते हैं।

<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, HTML के <bdi> की तरह 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. क्रम तय रखें। सेल में अब ग्लिफ़ डिस्प्ले क्रम में होते हैं। ब्राउज़र और कई टर्मिनल उन पर दोबारा bidi चलाएंगे, इसलिए ड्राइवर क्रम लॉक करते हैं:
आउटपुटक्रम तय रखने का तरीका
DevTools (CssConsoleDriver)दाएं से बाएं कैरेक्टर वाले span को LRO … PDF (U+202D / U+202C) में रैप किया जाता है। DevTools का %c सैनिटाइज़र direction और unicode-bidi हटा देता है।
टर्मिनल (AnsiDriver, text run)हर लाइन पर bidi चलाने वाले टर्मिनल के लिए दाएं से बाएं टेक्स्ट वाली पंक्तियों को LRO … PDF में रैप किया जाता है। renderToTerminal प्रवेश पर एक बार ECMA-48 BDSM एक्सप्लिसिट मोड (CSI 8 l, जिसे VTE-आधारित टर्मिनल मानते हैं) में जाता है और बाहर निकलने पर इम्प्लिसिट मोड (CSI 8 h) में लौटता है; दाएं से बाएं टेक्स्ट वाले वन-शॉट renderToAnsi फ़्रेम CSI 8 l से शुरू होते हैं। सेल को ज्यों का त्यों आउटपुट करने के लिए bidi: "none" पास करें।
DOM मिरर (<ConsoleMirror>)मिरर direction: ltr होता है (ताकि RTL होस्ट पेज उसे पलट न सके), और दाएं से बाएं टेक्स्ट वाले 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 पर मोनोस्पेस्ड है: 12 px पर हर ग्लिफ़ 7.2012 px का होता है, जबकि Menlo का 7.2246 px है। यह 0.3% संकरा है, जिससे हिब्रू की पूरी पंक्ति लगभग आधा सेल खिसक सकती है।

CssConsoleDriver कंसोल फ़ॉन्ट में कैनवस से हर दाएं से बाएं span को मापता है (वही Blink शेपिंग और फ़ॉन्ट फ़ॉलबैक जो DevTools इस्तेमाल करता है), फिर उसमें LayoutUnit (1/64 px) के सटीक अंतर जितनी पैडिंग जोड़ता है। अंत में span उतना ही चौड़ा होता है जितने ग्रिड सेल वह कवर करता है (इमोजी जैसा चौड़ा ग्रैफ़ीम दो गिना जाता है)। किसी span को अधिकतम आधा सेल अंदर खींचा जाता है, इसलिए प्रपोर्शनल फ़ॉलबैक फ़ॉन्ट (Windows पर Cascadia Mono के साथ Times New Roman) अगले span को अपने ग्लिफ़ पर चढ़ने देने के बजाय ओवरफ़्लो करता है। असली DevTools फ़्रंटएंड (Chromium 151) में हिब्रू, अरबी, मिश्रित और हिब्रू में इमोजी वाली लाइनों और RTL बॉक्स शीर्षकों के 64×21 फ़्रेम पर माप (bun bench/devtools/bidi-shot.ts):

फ़्रेमपंक्तियों के बीच दाएं किनारे का खिसकाव
क्रम तय किए बिना लॉग किए गए सेल14.5 px (DevTools क्रम और आकार दोबारा बदलता है)
क्रम तय, चौड़ाई की फ़िटिंग नहीं2.22 px
साथ मिलने वाला ड्राइवर0.156 px
कंट्रोल: वही सेल, हर RTL ग्लिफ़ की जगह x1.67 px

साथ मिलने वाला फ़्रेम इमोजी वाली पंक्ति को छोड़कर हर पंक्ति पर पूरी तरह लैटिन वाले कंट्रोल से मेल खाता है, और इमोजी वाली पंक्ति पर बेहतर है: इमोजी अपने आसपास के दाएं से बाएं span के साथ फ़िट होते हैं (0.03 px), जबकि कंट्रोल की बाएं से दाएं इमोजी वाली पंक्ति फ़िट नहीं होती (1.66 px)।

DevTools हर %c span को contain: paint के साथ पेंट करता है और हर एक को अलग से डिवाइस पिक्सेल पर स्नैप करता है, इसलिए जहां दो बैकग्राउंड भिन्नात्मक स्थिति पर मिलते हैं, वहां बीच का पिक्सेल किसी को भी नहीं मिल सकता, जिससे 1 px की बारीक दरार रह जाती है। Chrome और Edge में, बैकग्राउंड वाला span उसे दाईं ओर के अपारदर्शी पड़ोसी के नीचे 1 px तक बढ़ाता है, और फ़िट किया गया span उस पिक्सेल को अपनी पैडिंग में जोड़ता है। ऊपर के चारों फ़्रेम में से किसी में बारीक दरार नहीं दिखती।

फ़िटिंग बंद करने के लिए measureText: false पास करें, या अपना (text, bold, italic) => px मेज़रर दें।

इनपुट और एडिटिंग

<Input> और <Textarea> लॉजिकल कर्सर रखते हैं। ऐरो कुंजियां टेक्स्ट में स्टोरेज क्रम से चलती हैं, इसलिए दाएं से बाएं टेक्स्ट में ← दिखने में दाईं ओर जाता है। ब्लॉक कर्सर उस लॉजिकल कैरेक्टर पर रहता है जिसे वह कवर करता है, क्रम बदलने से वह जहां भी पहुंचे। दाएं से बाएं रन में यह पिछले कैरेक्टर के बाईं ओर वाला सेल होता है। सिलेक्शन भी एक लॉजिकल रेंज है, इसलिए उसका हाइलाइट मिश्रित दिशा वाली लाइनों में ऐसे सेल कवर कर सकता है जो एक-दूसरे से सटे नहीं हैं। सिंगल-लाइन <Input> का हॉरिज़ॉन्टल स्क्रॉल लॉजिकल कॉलम के साथ चलता है, इसलिए लंबी दाएं से बाएं वैल्यू कर्सर को स्क्रॉल करके नज़र से बाहर कर सकती है। स्पेस पर या टेक्स्ट के अंत में कर्सर को अलग नो-ब्रेक स्पेस के रूप में ड्रॉ किया जाता है, इसलिए वह न्यूट्रल रहता है और किसी भी तरफ़ की संख्याओं से कभी नहीं जुड़ता।

सीमाएं

  • पैराग्राफ़ सेपरेटर। लाइनें \n पर बंटती हैं। U+2029 और अन्य पैराग्राफ़ सेपरेटर bidi पैराग्राफ़ खत्म करते हैं लेकिन नई लाइन शुरू नहीं करते।
  • टैब। bidi चलने से पहले टैब स्पेस में बदल जाते हैं, इसलिए वे सेगमेंट सेपरेटर (S) के बजाय व्हाइटस्पेस (WS) की तरह काम करते हैं।
  • शेपिंग। सिर्फ़ प्रेज़ेंटेशन फ़ॉर्म वाले अक्षरों को आकार दिया जाता है: अरबी, फ़ारसी, उर्दू और U+FB50–U+FDFF तथा U+FE70–U+FEFF के अन्य अक्षर। Lam-alef एक ग्लिफ़ वाला लिगेचर बनने के बजाय दो सेल में दो जुड़े हुए अक्षर रहता है। Syriac, N'Ko और अन्य जुड़ने वाली लिपियां अपने मूल रूप बनाए रखती हैं।
  • कॉपी करना। कंसोल से कॉपी किया गया टेक्स्ट डिस्प्ले क्रम में होता है (LRO/PDF चिह्नों के साथ), लॉजिकल क्रम में नहीं।
  • टर्मिनल। जो टर्मिनल BDSM और LRO दोनों को अनदेखा करके अपना bidi चलाते हैं (उदाहरण के लिए iTerm2 में bidi चालू होने पर), वे दाएं से बाएं पंक्तियों का क्रम दोबारा बदल सकते हैं।
  • DOM मिरर। <ConsoleMirror> चौड़ाई फ़िट नहीं करता, इसलिए वहां दाएं से बाएं रन अपनी चौड़ाई के 0.3% तक खिसक सकते हैं।

फ़ोकस पैड का अनुवाद

@textjs/react/dom का <FocusPad> (जिसे <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 prop मिलता है, या action न होने पर defaultAction का टेक्स्ट मिलता है। defaultAction को पहले हिंट की कुंजियां मिलती हैं, या हिंट न होने पर null मिलता है। pressShortcut को स्क्रीन रीडर के लिए शॉर्टकट की कुंजियां मिलती हैं, और shortcutOpens को शॉर्टकट <kbd> एलिमेंट के रूप में मिलता है।

messages में स्क्रीन रीडर से घोषित होने वाले लेबल और कंसोल साइज़ कंट्रोल (sizeControl) भी शामिल हैं। <TextCanvas> वही messages prop लेता है। <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 पर यह पेज एडिट करें