<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>BasicCAT — Computer-Aided Translation (CAT) Tools</title>
    <description>BasicCAT Website</description>
    <link>https://www.basiccat.org/</link>
    <atom:link href="https://www.basiccat.org/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 07 Oct 2026 10:08:15 +0000</pubDate>
    <lastBuildDate>Wed, 07 Oct 2026 10:08:15 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>9 Manga Translation Tools Tested and Compared: AI Translation Plus Manual Editing, Which One Fits You</title>
        <description>&lt;p&gt;Recent years have seen a flood of manga translation tools. They can not only fully automate translation and lettering, producing a translated comic, but also support manual editing so you can polish and fine-tune the AI output.&lt;/p&gt;

&lt;p&gt;This article first covers what manga translation tools are used for, how to evaluate them, and the technical details behind them. Then it runs a rough hands-on comparison of nine tools that support post-editing, using a single page of &lt;em&gt;Detective Conan&lt;/em&gt; translated into Chinese and English as the example.&lt;/p&gt;

&lt;h2 id=&quot;what-manga-translation-tools-are-for-and-how-to-judge-them&quot;&gt;What Manga Translation Tools Are For, and How to Judge Them&lt;/h2&gt;

&lt;p&gt;You might reach for a manga translation tool for any number of reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;To quickly translate raw manga on a web page for your own reading&lt;/li&gt;
  &lt;li&gt;To extract the text from a comic for language learning, building a corpus, or academic research&lt;/li&gt;
  &lt;li&gt;To produce high-quality translated pages for other readers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different purposes call for different criteria. For quick reading, image translation needs to finish within seconds, quality is not the priority, and a browser extension matters. For high-quality work, you need configuration options, a convenient human-in-the-loop interface, and where necessary the ability to export the data and carry on translating in third-party software such as Photoshop.&lt;/p&gt;

&lt;p&gt;Ease of use and the operating systems supported are further dimensions worth weighing.&lt;/p&gt;

&lt;h2 id=&quot;technical-details-behind-manga-translation-tools&quot;&gt;Technical Details Behind Manga Translation Tools&lt;/h2&gt;

&lt;p&gt;Before evaluating anything, it helps to understand how these tools work under the hood.&lt;/p&gt;

&lt;p&gt;A manga translation tool usually involves four steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Text recognition&lt;/li&gt;
  &lt;li&gt;Text removal&lt;/li&gt;
  &lt;li&gt;Text translation&lt;/li&gt;
  &lt;li&gt;Lettering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These steps are normally carried out separately. With the arrival of multimodal and image-generation large language models, end-to-end approaches have appeared as well — a single model that solves all of the above. Nearly every tool offers the traditional step-by-step mode and has adopted large language models to varying degrees.&lt;/p&gt;

&lt;h3 id=&quot;text-recognition&quot;&gt;Text Recognition&lt;/h3&gt;

&lt;p&gt;Text recognition (OCR) typically takes two stages: text localization and character recognition.&lt;/p&gt;

&lt;p&gt;Every manga translation tool uses some combination of text detection and character recognition models, and many train specifically on comics. Some common models:&lt;/p&gt;

&lt;p&gt;Detection models:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;comic-text-detector: a text detection model trained by dmMaze. It accurately produces text masks, which can then be used for text removal and for determining text rotation.&lt;/li&gt;
  &lt;li&gt;YSGYolo: a detection model trained on YOLO that can detect speech bubbles, text lines and more, while deliberately skipping sound effects.&lt;/li&gt;
  &lt;li&gt;PaddleOCR: PaddleOCR provides a text detection model where one model handles localization across languages, plus layout detection models such as PPDocLayout that can determine reading order at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recognition models:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;mitOCR 48px: the character recognition model from the manga-image-translator open-source project. It also detects text color and must be run line by line.&lt;/li&gt;
  &lt;li&gt;manga-ocr: a Transformer model trained on the manga109 Japanese manga dataset. It handles multi-line text images and ignores furigana automatically.&lt;/li&gt;
  &lt;li&gt;PaddleOCR: PaddleOCR also provides recognition models, from small fast ones to PaddleOCR-VL, a large language model with high accuracy but slower inference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are auxiliary models too, such as panel detection, text direction detection, language identification and font recognition. Panel detection is the most useful of these, since it helps correct the reading order.&lt;/p&gt;

&lt;h3 id=&quot;text-removal&quot;&gt;Text Removal&lt;/h3&gt;

&lt;p&gt;Text removal can be done in many ways: blurring the text, filling with a solid color, or using image inpainting to strip the text and redraw the area it covered.&lt;/p&gt;

&lt;p&gt;Many inpainting methods are used by these tools.&lt;/p&gt;

&lt;p&gt;Traditional image processing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;OpenCV Telea&lt;/li&gt;
  &lt;li&gt;PatchMatch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deep learning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AOT&lt;/li&gt;
  &lt;li&gt;Lama&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Image-editing large models:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Flux&lt;/li&gt;
  &lt;li&gt;Gemini Nano Banana&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inpainting requires a text mask first. The comic-text-detector mentioned above can produce a fairly precise mask. For ordinary images, a plain rectangle combined with a good removal model already works well. If the text separates easily from the background, simple binarization is enough — fast and accurate.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-features/text-removal-and-reinjection.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;text-translation&quot;&gt;Text Translation&lt;/h3&gt;

&lt;p&gt;Traditional neural machine translation generally translates sentence by sentence and can send several sentences in one request. Some of the more advanced options support custom terminology; DeepL and Caiyun both perform well.&lt;/p&gt;

&lt;p&gt;Large language models now substantially outperform traditional machine translation. They handle very long context, can read images with vision models, let you constrain the style through prompts, and support terminology as well — and they can still translate correctly even when the OCR result contains errors.&lt;/p&gt;

&lt;h3 id=&quot;lettering&quot;&gt;Lettering&lt;/h3&gt;

&lt;p&gt;Placing text into the original image looks simple but is actually fairly involved. It needs to handle letter spacing, line spacing, font size, font family, rich text (bold and italic), color, rotation, outlines, vertical Chinese characters and many other settings. It also needs a degree of automation — automatically adjusting font size, setting text direction, and so on.&lt;/p&gt;

&lt;h3 id=&quot;technology-stacks&quot;&gt;Technology Stacks&lt;/h3&gt;

&lt;p&gt;The technology stack is whatever the tool is built with. Because Python is the dominant language in AI and is easy to learn, most manga translation tools are written in it, with QT as the UI library and PyTorch or ONNXRuntime for inference.&lt;/p&gt;

&lt;p&gt;Plenty of tools use something different, though — Java, Rust, JavaScript, Flutter, Go and so on. Each has its strengths: some are easy to distribute, some are cross-platform, some are fast. The field today is genuinely a contest of many schools.&lt;/p&gt;

&lt;h2 id=&quot;basic-information-on-the-nine-manga-translation-tools&quot;&gt;Basic Information on the Nine Manga Translation Tools&lt;/h2&gt;

&lt;p&gt;A note on versions: these tools are all under active development and release frequently, so the descriptions here may change over time. The versions compared in this article are: ImageTrans v6.5.2, BallonsTranslator v1.5.18, Comic-Translate v2.8.9, Koharu v0.83.5, Dango Translator v7.0, ComiTrans v2.3.0, Saber Translator v3.5.10, Manga-Translator-UI v3.0.4, and Nekotranslator v1.3.0.&lt;/p&gt;

&lt;p&gt;The table below summarizes the basics of these nine tools. Note that the size given is the Windows installer or archive, and where models are downloaded separately or bundled, that is noted. “Paid” here refers to the software itself, and “models bundled” means whether the installer already contains the OCR and text removal (inpainting) models.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Software&lt;/th&gt;
      &lt;th&gt;Released&lt;/th&gt;
      &lt;th&gt;Tech stack&lt;/th&gt;
      &lt;th&gt;Size&lt;/th&gt;
      &lt;th&gt;Paid&lt;/th&gt;
      &lt;th&gt;Open source&lt;/th&gt;
      &lt;th&gt;Models bundled&lt;/th&gt;
      &lt;th&gt;Link&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ImageTrans&lt;/td&gt;
      &lt;td&gt;2020&lt;/td&gt;
      &lt;td&gt;Java + JavaFX&lt;/td&gt;
      &lt;td&gt;~400MB (models included)&lt;/td&gt;
      &lt;td&gt;Paid (personal from ¥75)&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;✅ Bundled (OCR and Lama inpainting models, runs offline)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.basiccat.org/imagetrans/&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;BallonsTranslator&lt;/td&gt;
      &lt;td&gt;April 2022&lt;/td&gt;
      &lt;td&gt;Python + PyTorch&lt;/td&gt;
      &lt;td&gt;~32MB (~1.7GB models to download)&lt;/td&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;✅ GPL-3.0&lt;/td&gt;
      &lt;td&gt;❌ Downloads on first run&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/dmMaze/BallonsTranslator&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Comic-Translate&lt;/td&gt;
      &lt;td&gt;January 2024&lt;/td&gt;
      &lt;td&gt;Python + ONNXRuntime&lt;/td&gt;
      &lt;td&gt;~95MB&lt;/td&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;✅ Apache-2.0&lt;/td&gt;
      &lt;td&gt;❌ Downloads from Hugging Face&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/ogkalu2/comic-translate&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Koharu&lt;/td&gt;
      &lt;td&gt;April 2025&lt;/td&gt;
      &lt;td&gt;Rust + Tauri&lt;/td&gt;
      &lt;td&gt;~166MB&lt;/td&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;✅ Apache-2.0&lt;/td&gt;
      &lt;td&gt;❌ Downloads on first run&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/koharu-rs/koharu&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Manga-Translator-UI&lt;/td&gt;
      &lt;td&gt;August 2025&lt;/td&gt;
      &lt;td&gt;Python&lt;/td&gt;
      &lt;td&gt;~1.3GB (models included)&lt;/td&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;✅ GPL-3.0&lt;/td&gt;
      &lt;td&gt;✅ Bundled (integrates manga-image-translator models)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/hgmzhn/manga-translator-ui&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Saber Translator&lt;/td&gt;
      &lt;td&gt;February 2025&lt;/td&gt;
      &lt;td&gt;Python + TypeScript/Vue&lt;/td&gt;
      &lt;td&gt;~3.5GB (plus ~1.3GB models)&lt;/td&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;✅ GPL-3.0&lt;/td&gt;
      &lt;td&gt;⚠️ Models packaged separately&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/MashiroSaber03/Saber-Translator&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nekotranslator&lt;/td&gt;
      &lt;td&gt;July 2026&lt;/td&gt;
      &lt;td&gt;Flutter&lt;/td&gt;
      &lt;td&gt;~66MB&lt;/td&gt;
      &lt;td&gt;Free (bring your own translation API)&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;❌ Requires a download&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://nekonekone.com/translator&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ComiTrans&lt;/td&gt;
      &lt;td&gt;December 2025&lt;/td&gt;
      &lt;td&gt;Python + ONNXRuntime&lt;/td&gt;
      &lt;td&gt;~442MB (plus ~463MB models)&lt;/td&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;⚠️ No open-source license&lt;/td&gt;
      &lt;td&gt;⚠️ Models packaged separately&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/Aaaaamadeus/ComiTrans&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dango Translator&lt;/td&gt;
      &lt;td&gt;February 2020&lt;/td&gt;
      &lt;td&gt;Go&lt;/td&gt;
      &lt;td&gt;~833MB&lt;/td&gt;
      &lt;td&gt;Manga translation is paid&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;❌ Fully cloud-dependent, no local models&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/PantsuDango/Dango-Translator&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;As you can see, the overwhelming majority of manga translation tools choose Python with PyTorch or ONNXRuntime, go the free and open-source route, and distribute the program and models separately, downloading the models on first run. ImageTrans ships its models by default for an out-of-the-box experience, yet still comes in at only 400MB.&lt;/p&gt;

&lt;p&gt;The technical details are too numerous to compare one by one. These tools broadly support all the models and algorithms for the four translation steps above. As it stands, ImageTrans additionally supports panel detection and BallonsTranslator additionally supports font detection.&lt;/p&gt;

&lt;h2 id=&quot;the-tools-one-by-one&quot;&gt;The Tools, One by One&lt;/h2&gt;

&lt;p&gt;Below are brief reviews, screenshots and result comparisons for each tool.&lt;/p&gt;

&lt;p&gt;The sample manga page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/18_094.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The official Chinese edition of the sample page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/target-zh.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The official English edition of the sample page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/official-translation.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;imagetrans&quot;&gt;ImageTrans&lt;/h3&gt;

&lt;p&gt;ImageTrans is a cross-platform computer-aided image translation tool written in JavaFX. As paid software it genuinely delivers on usability, with rich features and deep customizability. At around 400MB it includes the runtime and all the basic OCR and text removal models, and it works well whether you are translating fully automatically or editing by hand — and it is fast. It supports a browser extension and PSD export, along with many other features such as translation memory — the core of a traditional computer-aided translation tool — searchable PDF generation, converting traditional comics to webtoon format, and document scanning. The flip side of having so many features is that you may struggle to find your way around without reading the documentation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/imagetrans/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/imagetrans/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Chinese version after manual editing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/imagetrans/modified-zh.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated English version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/imagetrans/translated-en.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;English version after manual editing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/target-en.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Because ImageTrans makes lettering and removing sound effects relatively easy, a manually adjusted version is included here as well.&lt;/p&gt;

&lt;h3 id=&quot;ballonstranslator&quot;&gt;BallonsTranslator&lt;/h3&gt;

&lt;p&gt;BallonsTranslator was the earliest open-source computer-aided manga translation tool. It is written in Python, runs most of its models through PyTorch, and supports acceleration on a range of GPUs. It can be difficult to use from mainland China, though — the models often fail to download — and the default output and the degree of interactivity leave something to be desired. Still, the number of adjustable parameters is already substantial, and being open source means anyone can customize it to their heart’s content.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/ballonstranslator/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/ballonstranslator/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated English version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/ballonstranslator/translated-en.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;comic-translate&quot;&gt;Comic-Translate&lt;/h3&gt;

&lt;p&gt;Another open-source tool focused on manga translation, also written in Python, but running most models through ONNXRuntime. The default translation requires buying credits, though a custom endpoint lets you use your own API. The editing interface is on the simple side.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/comic-translate/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/comic-translate/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated English version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/comic-translate/translated-en.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;koharu&quot;&gt;Koharu&lt;/h3&gt;

&lt;p&gt;Koharu is an open-source manga translation tool written in Rust and Tauri. Its author has contributed a great deal to the Rust ecosystem along the way, training and converting a number of models. Users in mainland China need a VPN to download the binaries, which is less than friendly. The engineering here is strong, but I still did not find it comfortable to use. Text is centered on the speech bubble, for instance, and bubbles are often irregular, so centering gives a poor result.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/koharu/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/koharu/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated English version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/koharu/translated-en.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;manga-translator-ui&quot;&gt;Manga-Translator-UI&lt;/h3&gt;

&lt;p&gt;An open-source graphical program built on the main models and design of manga-image-translator. The developer communicates technical ideas well and works quickly, and there are plenty of distinctive features. I am not fond of its line breaking, though: you have to insert line breaks by hand and it cannot wrap automatically, so the lettering is only so-so. It offers two interfaces, a web one and a Python desktop one. The default desktop interface is shown here.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/manga-translator-ui/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/manga-translator-ui/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated English version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/manga-translator-ui/translated-en.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;saber-translator&quot;&gt;Saber Translator&lt;/h3&gt;

&lt;p&gt;This open-source project uses a separated front-end and back-end architecture and offers an online version, so users can run it in the browser without downloading anything. It has some original ideas too, such as book management that generates a synopsis to assist translation. For now it is mainly used to translate into Chinese, and support for other languages is average.&lt;/p&gt;

&lt;p&gt;Images rendered through web technology look decent, but I really do not like this web interface. It is rough, and it does not display well on my small screen.&lt;/p&gt;

&lt;p&gt;Main interface:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/saber-translator/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Editing interface:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/saber-translator/ui-editor.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/saber-translator/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;nekotranslator&quot;&gt;Nekotranslator&lt;/h3&gt;

&lt;p&gt;A new manga translation tool from the author of LabelPlus, the collaborative scanlation software. Written in Flutter, it is the only tool here that supports desktop, Android and iOS.&lt;/p&gt;

&lt;p&gt;The workflow is novel, but I am still more used to an interface like Photoshop’s. It is also still fairly rough around the edges.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/nekotranslator/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/nekotranslator/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated English version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/nekotranslator/translated-en.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;comitrans&quot;&gt;ComiTrans&lt;/h3&gt;

&lt;p&gt;A recently released open-source tool. It is simple to use and easy to install, with a 400MB main program and 400MB of models. The number of configurable options is still very limited, and neither the interface language nor translating into other languages can be set yet. Like ImageTrans, it can choose an appropriate inpainting method depending on whether the background is complex.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/comitrans/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Automatically translated Chinese version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/comitrans/translated.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;dango-translator&quot;&gt;Dango Translator&lt;/h3&gt;

&lt;p&gt;Dango Translator appeared back in 2020, mainly for translating games via screenshot OCR, and it now supports manga translation as well. It is written in Go. Unfortunately its manga translation depends entirely on cloud services for OCR and text removal, which requires a subscription. When I tried it, the cloud service was down and the translation never went through. It does offer some editing, though the display has problems on small screens.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/imagetrans-comparison/dangatranslator/ui.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;They are all manga translation tools, but each has its own features and interface design, some open source and some paid. Use this article as a reference when choosing the translation tool that suits you. If, for example, I need to translate an image into several languages, I can use ImageTrans: it has a dedicated project file, so I can OCR and then translate, saving each language in its own project, and it even supports being driven by an AI agent. If I want to edit images on my phone, on the other hand, Nekotranslator is the only option.&lt;/p&gt;
</description>
        <pubDate>Sun, 04 Oct 2026 12:00:00 +0000</pubDate>
        <link>https://www.basiccat.org/manga-translation-software-comparison-ai-translation-and-manual-editing/</link>
        <guid isPermaLink="true">https://www.basiccat.org/manga-translation-software-comparison-ai-translation-and-manual-editing/</guid>
        
        <category>imagetrans</category>
        
        <category>computer-aided-translation</category>
        
        <category>post-editing</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Improving the Availability of the ImageTrans Remote Translation Service</title>
        <description>&lt;p&gt;ImageTrans can connect to a remote server over WebSocket to provide image translation as a service. There are two ends in this setup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The server (&lt;a href=&quot;https://github.com/xulihang/ImageTrans_wsServer&quot;&gt;ImageTrans_wsServer&lt;/a&gt;) runs on a VPS, receiving and dispatching translation requests&lt;/li&gt;
  &lt;li&gt;The client (ImageTrans) runs on a mac mini m4 at home, doing the actual OCR, text detection, translation and typesetting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture itself is simple, but keeping it running reliably means dealing with a fair number of availability problems. Here’s a record of the improvements made so far and the pitfalls found along the way.&lt;/p&gt;

&lt;h2 id=&quot;dispatching-on-the-server&quot;&gt;Dispatching on the server&lt;/h2&gt;

&lt;p&gt;The most basic problem is dispatching across multiple clients. The server keeps a connection pool, and each client reports its own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;displayName&lt;/code&gt; on connect via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_name_and_password&lt;/code&gt;; the server dispatches based on that.&lt;/p&gt;

&lt;p&gt;Dispatching prefers idle instances. If the requested instance is busy, another idle one picks up the work. Each instance has its own busy flag, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConcurrentHashMap&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;putIfAbsent&lt;/code&gt; providing atomicity so two requests can’t grab the same instance at once.&lt;/p&gt;

&lt;p&gt;There’s also a failure cooldown: after an instance fails repeatedly, it stops receiving work for a while. And timeout redispatch — if an instance doesn’t respond within 3 seconds, or hasn’t finished within 60 seconds, the request is redispatched to another instance.&lt;/p&gt;

&lt;h2 id=&quot;process-isolation-on-the-client&quot;&gt;Process isolation on the client&lt;/h2&gt;

&lt;p&gt;On the client side, two things were done:&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;starting a separate process&lt;/strong&gt;. Translation jobs run in their own process, so memory leaks don’t accumulate in the main one. This is controlled by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useIndependentProcessForServer&lt;/code&gt; preference and kicks in during &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExecuteWorkFlow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;periodically restarting itself&lt;/strong&gt;. The client has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RestartTimer&lt;/code&gt;, defaulting to an 8-hour interval, which relaunches itself when it fires. In theory this clears out accumulated state.&lt;/p&gt;

&lt;h2 id=&quot;problems-found&quot;&gt;Problems found&lt;/h2&gt;

&lt;p&gt;The mechanisms above look reasonably complete, but some problems showed up in actual operation.&lt;/p&gt;

&lt;p&gt;One major problem is that the client receives an image, starts processing it, and then mysteriously hangs, leaving that instance unavailable from then on.&lt;/p&gt;

&lt;h2 id=&quot;workarounds&quot;&gt;Workarounds&lt;/h2&gt;

&lt;p&gt;Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RestartTimer&lt;/code&gt; can’t be relied on once things hang, the fix bypasses it and works from outside the process. A scheduled task on the host system &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pkill&lt;/code&gt;s the client process and restarts it every 24 hours.&lt;/p&gt;

&lt;p&gt;Scheduled killing has an obvious flaw: &lt;strong&gt;24 hours is a fixed period, not “restart when stuck”&lt;/strong&gt;. If the hang happens 10 minutes after a restart, you wait another 23 hours and 50 minutes.&lt;/p&gt;

&lt;p&gt;So a watchdog can be added on top: watch the clients connected via the server’s list port, and if a connected client’s name is no longer there, restart that process.&lt;/p&gt;
</description>
        <pubDate>Mon, 21 Sep 2026 12:10:00 +0000</pubDate>
        <link>https://www.basiccat.org/imagetrans-remote-service-availability/</link>
        <guid isPermaLink="true">https://www.basiccat.org/imagetrans-remote-service-availability/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Multi-Language Jekyll Sites and SEO</title>
        <description>&lt;p&gt;Jekyll has no built-in support for multiple languages. When a site only has one or two languages, the most common shortcut is to branch with conditionals:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/zh/&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  ...Chinese content...
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  ...English content...
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With one or two languages this is tolerable. But every language you add means another level of nesting in every template, and the branches start to interleave with each other until nobody dares to touch them.&lt;/p&gt;

&lt;p&gt;The better approach is to &lt;strong&gt;make language a first-class citizen&lt;/strong&gt;: move the language list into config, let each page declare its own language, and have templates do nothing but look things up. This post covers that change, plus the multi-language SEO work you should do along the way.&lt;/p&gt;

&lt;h2 id=&quot;start-with-config&quot;&gt;Start with config&lt;/h2&gt;

&lt;p&gt;The core idea is to define the language list in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;languages&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;en&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;English&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;en&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hreflang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;en&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zh&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;中文&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/zh/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zh_CN&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hreflang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zh-CN&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ja&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;日本語&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/ja/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ja&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hreflang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ja&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ar&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;العربية&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/ar/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ar&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hreflang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ar&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;rtl&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then each page declares its language in the front matter:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;page&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ダウンロード&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ja&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In templates, the if/else is replaced by a lookup:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;languages&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The benefit is that &lt;strong&gt;adding a language means changing one config entry and adding one directory&lt;/strong&gt; — no template changes at all.&lt;/p&gt;

&lt;p&gt;If you don’t want to hand-write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lang&lt;/code&gt; in every file, set it in bulk per directory with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defaults&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;zh&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;zh_CN&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;zh&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ja&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ja&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ja&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;locale-and-hreflang-are-two-separate-fields&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locale&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hreflang&lt;/code&gt; are two separate fields&lt;/h3&gt;

&lt;p&gt;Note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locale&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hreflang&lt;/code&gt; are kept apart in the config above and &lt;strong&gt;cannot be merged into one&lt;/strong&gt;. This is an easy detail to overlook:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locale&lt;/code&gt; is used for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;html lang&amp;gt;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;og:locale&lt;/code&gt;, and takes the underscore form (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zh_CN&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hreflang&lt;/code&gt; is used for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link rel=&quot;alternate&quot;&amp;gt;&lt;/code&gt;, and takes the BCP 47 hyphen form (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zh-CN&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the wrong format and either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;og:locale&lt;/code&gt; stops working or the hreflang is silently ignored by search engines — no error, they just act as if it weren’t there.&lt;/p&gt;

&lt;h2 id=&quot;two-liquid-pitfalls&quot;&gt;Two Liquid pitfalls&lt;/h2&gt;

&lt;p&gt;There are two problems you tend to run into when adding languages, and both have to do with how Liquid looks up values and passes arguments.&lt;/p&gt;

&lt;h3 id=&quot;pitfall-1-a-directory-and-a-file-under-_data-are-not-equivalent&quot;&gt;Pitfall 1: a directory and a file under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_data&lt;/code&gt; are not equivalent&lt;/h3&gt;

&lt;p&gt;Jekyll data files can be laid out two ways, and they are accessed differently:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_data/text.yml         ← file, accessed as site.data.text
_data/zh/text.yml      ← directory, accessed as site.data.zh.text
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That looks symmetrical, but if you write a generic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.data[lang].text&lt;/code&gt;, it holds for the directory form and fails for the file form — that would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.data[&apos;en&apos;]&lt;/code&gt;, a key that simply doesn’t exist, so you get nil.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liquid renders undefined variables as empty strings without complaining.&lt;/strong&gt; The symptom is therefore a page with blank text where the links still work fine (the URLs usually come from somewhere else entirely), which makes it painful to track down.&lt;/p&gt;

&lt;p&gt;So either use the directory form consistently (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_data/en/&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_data/zh/&lt;/code&gt;), or add a fallback:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;pitfall-2-include-and-render-have-different-scopes&quot;&gt;Pitfall 2: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt; have different scopes&lt;/h3&gt;

&lt;p&gt;When writing multi-language templates you often need to pass the current language into an include, like this:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;nav.html&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;page.lang&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here you need to be aware that in Jekyll, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt; follow &lt;strong&gt;different scoping rules&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt; &lt;strong&gt;shares scope&lt;/strong&gt; — a variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assign&lt;/code&gt;ed inside the include is still visible in the outer template, and in later sibling includes&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt; is &lt;strong&gt;isolated&lt;/strong&gt; — it can’t see outer variables by default and only sees the arguments passed to it explicitly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The shared semantics of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt; are convenient sometimes, but they make it easy to write fragile code: one include depending on another include having run first and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assign&lt;/code&gt;ed some variable. That kind of code breaks when used on its own, breaks when you reorder the includes, and fails silently (Liquid renders undefined variables as empty strings).&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;always pass values across includes as explicit arguments&lt;/strong&gt;, never rely on leakage. If you want to enforce isolation, switch to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt; (requires Jekyll 4.x, and it requires the include file to live under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes&lt;/code&gt;; it’s also faster).&lt;/p&gt;

&lt;h2 id=&quot;the-language-chooser&quot;&gt;The language chooser&lt;/h2&gt;

&lt;p&gt;With the language list in place, you can build a language switcher. The simplest form is a dropdown:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;select onchange=&quot;if(this.value)location.href=this.value&quot;&amp;gt;
  &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlangs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;forloop.index0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
    &amp;lt;option value=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt; selected&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;&amp;gt;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlabels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&amp;lt;/option&amp;gt;
  &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;endfor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&amp;lt;/select&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;altlangs&lt;/code&gt; is the list of “URLs of this page in the other languages” and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;altlabels&lt;/code&gt; is the matching language name. How to compute them is below.&lt;/p&gt;

&lt;p&gt;The key step is working out “the URL of this page in the other languages”.&lt;/p&gt;

&lt;p&gt;The idea is: &lt;strong&gt;first compute the current page’s path relative to its own language root, then append it to the other language roots&lt;/strong&gt;.&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;unless&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;remove_first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prepend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endunless&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Stripping &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/zh/&lt;/code&gt; off &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/zh/imagetrans/&lt;/code&gt; gives &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/imagetrans/&lt;/code&gt;, and appending &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/ja/&lt;/code&gt; to that gives &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/ja/imagetrans/&lt;/code&gt;. For the root language the prefix is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rest&lt;/code&gt; is just the original URL.&lt;/p&gt;

&lt;h3 id=&quot;dont-offer-options-for-pages-that-dont-exist&quot;&gt;Don’t offer options for pages that don’t exist&lt;/h3&gt;

&lt;p&gt;Computing the URLs isn’t enough. &lt;strong&gt;If a language doesn’t have this page, it shouldn’t be offered as an option&lt;/strong&gt; — otherwise clicking through lands on a 404.&lt;/p&gt;

&lt;p&gt;So after getting the candidate URL you have to actually look it up:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;found&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pages&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;found&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;found&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;documents&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You have to check two places: regular pages live in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.pages&lt;/code&gt;, while post-like content (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts&lt;/code&gt; and custom collections) lives in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.documents&lt;/code&gt;. Checking only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.pages&lt;/code&gt; means the chooser is missing options on post pages.&lt;/p&gt;

&lt;p&gt;That gives you &lt;strong&gt;every language version of this page that actually exists&lt;/strong&gt;. Only render the whole selector when there is more than one option.&lt;/p&gt;

&lt;h3 id=&quot;a-subtle-failure-mode&quot;&gt;A subtle failure mode&lt;/h3&gt;

&lt;p&gt;Consider this logic for determining the current language:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;is_cur&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page.lang&lt;/code&gt; comes back empty (say a batch of pages forgot to declare &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lang&lt;/code&gt;), then &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_cur&lt;/code&gt; is false for every language&lt;/strong&gt;, including the current one itself. The result is that the array may contain only the other languages, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size &amp;gt; 1&lt;/code&gt; doesn’t hold, and &lt;strong&gt;the entire selector disappears&lt;/strong&gt; — not just one option missing.&lt;/p&gt;

&lt;p&gt;To debug: search the page source for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class=&quot;language-chooser&quot;&lt;/code&gt;. If it isn’t there, the array never exceeded one item, so first check whether &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page.lang&lt;/code&gt; is being picked up correctly.&lt;/p&gt;

&lt;h2 id=&quot;seo-hreflang&quot;&gt;SEO: hreflang&lt;/h2&gt;

&lt;p&gt;This is &lt;strong&gt;the single most important thing&lt;/strong&gt; in multi-language SEO.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/download/&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/zh/download/&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/ja/download/&lt;/code&gt; have different content but the same subject. Search engines don’t know they’re the same thing, so they may treat them as &lt;strong&gt;duplicate content&lt;/strong&gt;, diluting each other’s weight, or index only one of them — which means Japanese users searching in Japanese land on the English page.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hreflang&lt;/code&gt; is how you declare “these pages are different language versions of the same content”:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/download/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;zh-CN&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/zh/download/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ja&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/ja/download/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;x-default&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/download/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A few points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;It must be an absolute URL&lt;/strong&gt;, not a relative path&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Every language version must declare all the others&lt;/strong&gt;, not just one side (if A points to B, B must point to A)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-default&lt;/code&gt; points to the fallback version used when no language matches the user, usually the main site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Jekyll, if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.url&lt;/code&gt; isn’t configured (so local preview still works), remember the fallback:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;siteurl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;https://example.com&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;reuse-the-choosers-data&quot;&gt;Reuse the chooser’s data&lt;/h3&gt;

&lt;p&gt;The chooser step already computed “which languages this page exists in”, and hreflang wants &lt;strong&gt;the same data&lt;/strong&gt;. So it’s just a matter of collecting one more field in the same loop:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;site.languages&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  ...
  &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;is_cur&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;found&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlangs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlangs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlabels&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlabels&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;althreflangs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;althreflangs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;hreflang&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;endfor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then output it in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlangs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;altlangs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;forloop.index0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  &amp;lt;link rel=&quot;alternate&quot; hreflang=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;althreflangs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot; href=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;siteurl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cand&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot; /&amp;gt;
  &lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;endfor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
  &amp;lt;link rel=&quot;alternate&quot; hreflang=&quot;x-default&quot; href=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;siteurl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;englishurl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot; /&amp;gt;
&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reusing the same data has a bonus: &lt;strong&gt;you never emit hreflang pointing at a page that doesn’t exist&lt;/strong&gt;. That matters, because &lt;strong&gt;a wrong hreflang is worse than no hreflang&lt;/strong&gt;. If you declare that a page’s Japanese version is at a URL that doesn’t exist, search engines will consider the page’s configuration broken and may discard the correct signals along with the bad one.&lt;/p&gt;

&lt;h2 id=&quot;right-to-left-languages&quot;&gt;Right-to-left languages&lt;/h2&gt;

&lt;p&gt;Arabic, Hebrew, Persian and Urdu are written right to left. When you add one of these, translating the content isn’t enough — &lt;strong&gt;the layout has to mirror too&lt;/strong&gt;, or the interface feels wrong everywhere.&lt;/p&gt;

&lt;h3 id=&quot;step-1-declare-the-direction&quot;&gt;Step 1: declare the direction&lt;/h3&gt;

&lt;p&gt;The HTML &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dir&lt;/code&gt; attribute does this:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ar&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;dir=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lang&lt;/code&gt;, don’t hard-code it in templates — put it in the language config:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ar&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;العربية&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/ar/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ar&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hreflang&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ar&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;rtl&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And output it from the field in the template:&lt;/p&gt;

&lt;div class=&quot;language-liquid highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;html lang=&quot;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;&quot;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rtl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt; dir=&quot;rtl&quot;&lt;span class=&quot;p&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;endif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%}&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way, adding Hebrew later is just one more &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rtl: true&lt;/code&gt; line in the config, with no template changes.&lt;/p&gt;

&lt;h3 id=&quot;step-2-deal-with-physical-direction-properties-in-css&quot;&gt;Step 2: deal with physical direction properties in CSS&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is the biggest and most easily missed part of the RTL work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dir=&quot;rtl&quot;&lt;/code&gt; only affects text flow and some default alignment. It does &lt;strong&gt;not automatically flip&lt;/strong&gt; styles that hard-code a direction, and almost every theme uses physical properties heavily:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;ul&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;margin-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;              &lt;span class=&quot;c&quot;&gt;/* list indent on the left */&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;blockquote&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;border-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#ccc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;/* quote bar on the left */&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;th&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;               &lt;span class=&quot;c&quot;&gt;/* table headers left-aligned */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In an RTL page all of these stay exactly where they were, so list indentation ends up on the wrong side, the quote bar appears opposite the text, and table headers are aligned the wrong way.&lt;/p&gt;

&lt;p&gt;There are two ways to fix it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: switch to logical properties&lt;/strong&gt; (the modern approach)&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;ul&lt;/span&gt;         &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;margin-inline-start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;blockquote&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;border-inline-start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#ccc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;th&lt;/span&gt;         &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline-start&lt;/code&gt; means “the start of the inline direction” — the left in LTR, automatically the right in RTL. This is the cleanest solution, but it &lt;strong&gt;requires going through every physical property in the theme&lt;/strong&gt; and confirming browser support (newer properties like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;border-inline-start&lt;/code&gt; do nothing in old browsers).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: override in bulk with attribute selectors&lt;/strong&gt; (less invasive)&lt;/p&gt;

&lt;p&gt;Leave the original styles alone and append a set of overrides at the end of the stylesheet:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;margin-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;margin-right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;blockquote&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;padding-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;padding-right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#ccc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;This has an extra benefit&lt;/strong&gt;: the selector is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[dir=&quot;rtl&quot;]&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[lang=&quot;ar&quot;]&lt;/code&gt;, so it applies to &lt;strong&gt;all&lt;/strong&gt; RTL languages at once. Adding Hebrew or Persian later needs no more CSS.&lt;/p&gt;

&lt;p&gt;The cost is that the rules are now split across two places, so it’s easy to forget to update the overrides when you change the original styles. Which one to pick depends on whether you’re willing to touch the original theme — if it’s a third-party theme you want to keep upgradable, the second is more practical.&lt;/p&gt;

&lt;h3 id=&quot;step-3-keep-code-blocks-left-to-right&quot;&gt;Step 3: keep code blocks left-to-right&lt;/h3&gt;

&lt;p&gt;This one is often overlooked, but &lt;strong&gt;skipping it directly hurts readability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In an RTL page, if code blocks, URLs, file paths and command-line examples mirror along with everything else, they become nearly unreadable:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# after mirroring (illustrative)
--with-ssl https://example.com/path    →  the path reads backwards
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The semantics of this content is “read left to right” regardless of the interface language. So &lt;strong&gt;pin it back to LTR&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;pre&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;kbd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;rtl&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;samp&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;direction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ltr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;unicode-bidi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;embed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All three properties matter, none can be dropped:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;direction: ltr&lt;/code&gt; — declares the direction&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-align: left&lt;/code&gt; — alignment does not follow &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;direction&lt;/code&gt; automatically, it has to be set separately&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unicode-bidi: embed&lt;/code&gt; — makes this block its own bidirectional text scope, so it isn’t disturbed by the surrounding RTL context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unicode-bidi&lt;/code&gt; line is especially easy to forget. Without it, mixed punctuation and digits inside code blocks can still get reordered.&lt;/p&gt;

&lt;h3 id=&quot;step-4-handle-icons-and-directional-symbols&quot;&gt;Step 4: handle icons and directional symbols&lt;/h3&gt;

&lt;p&gt;Some characters carry direction in their meaning, and mirroring reverses it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Arrows&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;→&lt;/code&gt; reads as “back” in an RTL context and should become &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;←&lt;/code&gt; for “forward”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Back/forward buttons&lt;/strong&gt;: the icons usually need swapping&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Progress bars, breadcrumbs&lt;/strong&gt;: the starting point should visually be reversed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t something CSS can fix automatically — it has to be handled at the content level.&lt;/p&gt;

&lt;p&gt;Conversely, &lt;strong&gt;some things should not be mirrored&lt;/strong&gt;: clock icons, play/pause buttons, logos, numerals. Forcing a mirror on those is just confusing.&lt;/p&gt;

&lt;h3 id=&quot;step-5-check-standalone-pages&quot;&gt;Step 5: check standalone pages&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, &lt;strong&gt;standalone HTML pages that don’t go through a layout won’t inherit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dir&lt;/code&gt;&lt;/strong&gt;. The site’s root element, standalone tool pages and embedded small apps all need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dir=&quot;rtl&quot;&lt;/code&gt; set individually. This is the most commonly missed piece.&lt;/p&gt;

&lt;h3 id=&quot;a-related-side-effect-hreflang-and-oglocale&quot;&gt;A related side effect: hreflang and og:locale&lt;/h3&gt;

&lt;p&gt;There’s an easy trap here that &lt;strong&gt;has nothing to do with RTL but always shows up alongside it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;og:locale&lt;/code&gt; uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;language_REGION&lt;/code&gt; format, where the region code is an &lt;strong&gt;ISO 3166 country code&lt;/strong&gt;. Arabic as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ar&lt;/code&gt; has no single region, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AR&lt;/code&gt; in ISO 3166 is &lt;strong&gt;Argentina&lt;/strong&gt; — so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ar_AR&lt;/code&gt; literally means “Arabic (Argentina)”, which is wrong. Use the language’s default region, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ar_SA&lt;/code&gt;, or just write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ar&lt;/code&gt; on its own.&lt;/p&gt;

&lt;p&gt;The lesson from this trap: &lt;strong&gt;don’t guess region codes by intuition&lt;/strong&gt;, especially for languages spanning many countries like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ar&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;en&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pt&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;en_US&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;en_GB&lt;/code&gt; are different regions, as are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es_ES&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es_MX&lt;/code&gt;, and which one to use depends on who your actual target users are.&lt;/p&gt;

&lt;h2 id=&quot;result-and-checks&quot;&gt;Result and checks&lt;/h2&gt;

&lt;p&gt;After the change, every page outputs tags like these:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;zh-CN&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/zh/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ja&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/ja/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alternate&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hreflang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;x-default&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To check, you can look at the page source directly, or run the live pages through Google’s &lt;a href=&quot;https://search.google.com/test/rich-results&quot;&gt;Rich Results Test&lt;/a&gt; or a dedicated hreflang validator.&lt;/p&gt;

&lt;p&gt;One thing that’s easy to miss: &lt;strong&gt;this template only covers pages that go through a layout&lt;/strong&gt;. If the site has fully hand-written HTML pages that bypass the default layout, they won’t get these tags automatically and have to be handled separately.&lt;/p&gt;

&lt;h2 id=&quot;a-few-smaller-points&quot;&gt;A few smaller points&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The sitemap needs no attention.&lt;/strong&gt; If you’re using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-sitemap&lt;/code&gt;, newly added language pages are picked up automatically with no manual maintenance, and the sitemap it generates carries hreflang information itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;html lang&amp;gt;&lt;/code&gt; has to be correct.&lt;/strong&gt; This uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locale&lt;/code&gt; field — don’t mix up the format with hreflang. RTL languages also need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dir&lt;/code&gt; output alongside it, see above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the content isn’t ready, prefer leaving it empty.&lt;/strong&gt; If a language’s translations aren’t done yet, the corresponding listing pages (blog, tags) should show an empty list rather than being padded out with content from another language — that just shows users a pile of headlines they can’t read, and the bounce rate ends up dragging down that directory’s weight. Add content once the translation exists; the template will display it automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Machine-translated pages should be labelled.&lt;/strong&gt; If a language is machine-translated, flag it in the config, show a notice bar on the page and link to the source language. It’s honest, and it stops users from misjudging the quality of the translation. The implementation is simple: one more field in the config and a check in the template.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;: Make languages config data and let templates do nothing but look things up. The language chooser and hreflang reuse the same computation of “which language versions of this page exist” — so adding a language means touching only the config and the content, never the templates. RTL languages take one extra step: mark &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rtl: true&lt;/code&gt; in the config, then add a set of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[dir=&quot;rtl&quot;]&lt;/code&gt; selector overrides, and every right-to-left language from then on shares them.&lt;/p&gt;
</description>
        <pubDate>Fri, 18 Sep 2026 11:40:00 +0000</pubDate>
        <link>https://www.basiccat.org/multi-language-jekyll-site-and-seo/</link>
        <guid isPermaLink="true">https://www.basiccat.org/multi-language-jekyll-site-and-seo/</guid>
        
        <category>jekyll</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Arabic and Other Bidirectional Text Support in an OCR Tool</title>
        <description>&lt;p&gt;Arabic, Hebrew and Persian are written from right to left, while the Arabic numerals and Latin words mixed into them are read the normal left-to-right way. That is why they are called bidirectional text. Furthermore, the actual glyphs displayed for Arabic characters depend on the context — that is, the surrounding characters — resulting in changes such as ligatures and overlaps. This is known as complex text. When image OCR and translation software runs into these languages, it has to deal with an extra set of problems: how the interface displays the text, how to restore the character order of the OCR output, how to join words into lines in the correct order, and how to lay text out in reading order when exporting a PDF.&lt;/p&gt;

&lt;p&gt;This article describes how &lt;a href=&quot;/imagetrans/&quot;&gt;ImageTrans&lt;/a&gt; handles these.&lt;/p&gt;

&lt;h2 id=&quot;interface-display-and-text-rendering&quot;&gt;Interface display and text rendering&lt;/h2&gt;

&lt;p&gt;A JavaFX Node has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodeOrientation&lt;/code&gt; property. Once you set it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RIGHT_TO_LEFT&lt;/code&gt;, JavaFX takes care of text alignment, cursor movement direction, and which side punctuation falls on, with no manual intervention needed.&lt;/p&gt;

&lt;p&gt;ImageTrans sets this property on every control that displays bidirectional text, including the source text box, the target text box, and the typesetting engine.&lt;/p&gt;

&lt;p&gt;Besides display, there is also a project setting for right-to-left reading, which affects &lt;strong&gt;ordering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In an Arabic paragraph, the first word is at the &lt;strong&gt;far right&lt;/strong&gt;. If we sort the usual way, from smallest to largest X coordinate, the reading order of the whole paragraph comes out completely reversed. So this switch is passed to several modules involved in sorting and merging:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Text box sorting&lt;/li&gt;
  &lt;li&gt;In-line box sorting&lt;/li&gt;
  &lt;li&gt;Panel detection&lt;/li&gt;
  &lt;li&gt;Box merging&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;character-handling-in-ocr&quot;&gt;Character handling in OCR&lt;/h2&gt;

&lt;p&gt;ImageTrans uses the PaddleOCR family of models (integrated through RapidOCR). When these models recognize Arabic, what they output is in &lt;strong&gt;visual order&lt;/strong&gt; — they scan the image pixels from left to right.&lt;/p&gt;

&lt;p&gt;Visual order means the actual writing order of the letters is reversed, but digits, which are already laid out from left to right visually, come out in the correct order.&lt;/p&gt;

&lt;p&gt;So a reversal is needed, one that preserves digit runs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A pure digit string: return it as is&lt;/li&gt;
  &lt;li&gt;Everything else: reverse the whole string first, then flip each run of consecutive digits back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;م٢٠٢٦&lt;/code&gt; should be restored to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;٢٠٢٦م&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At the same time, a bracket mirroring pass swaps left and right brackets.&lt;/p&gt;

&lt;p&gt;Other OCR engines, however, do not need this.&lt;/p&gt;

&lt;h2 id=&quot;joining-words-into-lines-right-to-left&quot;&gt;Joining words into lines: right to left&lt;/h2&gt;

&lt;p&gt;What OCR produces is a pile of word boxes, which first have to be merged into lines and then into paragraphs.&lt;/p&gt;

&lt;p&gt;Merging works by starting from a seed box and repeatedly swallowing adjacent boxes in some direction. Which direction to try first is reversed for Arabic:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-basic&quot;&gt;growRightFirstRound = True              &apos; try right first by default
If right2left And horizontal Then
    growRightFirstRound = False         &apos; for Arabic, try left first
End If
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It first gathers the words of the same line going left; when this round can no longer grow, it tries the other direction; when neither direction can grow, the line is finished.&lt;/p&gt;

&lt;p&gt;But the merge direction alone is not enough. There is one kind of mixed-script case that needs separate handling.&lt;/p&gt;

&lt;p&gt;An Arabic line merges from right to left, and that order is correct for Arabic words, but &lt;strong&gt;Latin text embedded in the line must stay left to right&lt;/strong&gt; — otherwise a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello world&lt;/code&gt; comes out of the merge as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;world hello&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So when the end of the first box carries two or more non-Arabic letters and the second box is not an Arabic word either, the two boxes are taken to belong to the same LTR text run and their order needs to be swapped. In implementation, the tail segment of the first box is cut off and appended to the second box:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;source1 = source1 without the tail  +  &quot; &quot;  +  source2  +  tail
source2 = &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For instance, if the first box is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;world&lt;/code&gt; and the second is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello&lt;/code&gt;, after processing they are restored to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello world&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;exporting-pdf-shape-and-reverse&quot;&gt;Exporting PDF: shape and reverse&lt;/h2&gt;

&lt;p&gt;This is the hardest step.&lt;/p&gt;

&lt;p&gt;PDF itself does no complex text layout. When &lt;a href=&quot;https://pdfbox.apache.org/&quot;&gt;PDFBox&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;showText&lt;/code&gt; gets a string, it looks up each Unicode code point in the font’s cmap one by one and draws the glyph it finds. It does &lt;strong&gt;not&lt;/strong&gt; handle Arabic contextual shaping — the fact that the same letter has a different shape at the beginning, middle or end of a word, or standing alone. PDFBox will not do that for you.&lt;/p&gt;

&lt;p&gt;So ImageTrans does two steps of its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first step is shape&lt;/strong&gt;: replace each letter with the variant it takes at its current position. Unicode has an “Arabic Presentation Forms” block (U+FE70–FEFF) set aside for exactly these variant glyphs. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ت&lt;/code&gt; standing alone is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FE95&lt;/code&gt;, at the beginning of a word it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FE97&lt;/code&gt;, in the middle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FE98&lt;/code&gt;, and at the end &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FE96&lt;/code&gt;. The decision rests on whether the neighbouring letters can join — letters such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ا&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;د&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ر&lt;/code&gt; can only join to the preceding letter, never to the following one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second step is reverse&lt;/strong&gt;: the product of the previous step is a string of “glyph codes” rather than “characters”. Character order is logical order, whereas glyph code order has to be visual order — the order in which they are drawn on the page. So the whole string is turned around, so that drawing it from left to right comes out right.&lt;/p&gt;

&lt;p&gt;The reversal has a pitfall: &lt;strong&gt;digits must not be reversed along with it&lt;/strong&gt;. So this is not a simple one-step reversal but a per-word process, splitting on spaces:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Pure digit words: put back as they were&lt;/li&gt;
  &lt;li&gt;Arabic words: keep, but flip each run of consecutive digits inside the word&lt;/li&gt;
  &lt;li&gt;Other words (Latin words and so on): collect them into a group and reverse the group as a whole&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally the brackets are mirrored once more and the direction control characters are stripped, and only then is it handed to PDFBox to draw.&lt;/p&gt;

&lt;h3 id=&quot;font-requirements&quot;&gt;Font requirements&lt;/h3&gt;

&lt;p&gt;This approach depends on the font carrying the presentation form glyphs in U+FE80–FEFC. Common system fonts such as Arial, Tahoma and Segoe UI have them (for historical reasons, for compatibility with old software). But some fonts do not, such as Windows’ own Arabic Typesetting, which will simply throw an error on export.&lt;/p&gt;

&lt;h3 id=&quot;searchability&quot;&gt;Searchability&lt;/h3&gt;

&lt;p&gt;There is an unexpected benefit: although what is stored in the PDF is presentation form glyphs, what gets copied out is ordinary Arabic text.&lt;/p&gt;

&lt;p&gt;The reason is that text extraction applies NFKC normalization to each word, and NFKC happens to compatibility-decompose U+FE70–FEFF back into the base letters. This is standard behaviour, not something specific to any one library, so the same holds in other PDF readers. So the exported PDF both displays correctly and can be copied and searched.&lt;/p&gt;

&lt;h3 id=&quot;known-limitations&quot;&gt;Known limitations&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;The font must contain the presentation form glyphs (U+FE80–FEFC), otherwise the export will fail. If your font runs into this problem, just switch to one with full coverage.&lt;/li&gt;
  &lt;li&gt;If the source text carries diacritics (harakat), they enter the text layer along with everything else. Searching may then require including the diacritics to match exactly; the exact behaviour depends on the reader’s implementation.&lt;/li&gt;
  &lt;li&gt;Ligatures (such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;لا&lt;/code&gt;) are currently assembled from two joined glyphs. The visual result is close, and extraction restores them correctly as two letters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;bidirectional-and-complex-text-list&quot;&gt;Bidirectional and Complex Text List&lt;/h2&gt;

&lt;p&gt;The following table combines &lt;strong&gt;bidirectionality&lt;/strong&gt; and &lt;strong&gt;complexity&lt;/strong&gt;, marking each dimension with “Yes/No”.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Language/Script&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Bidirectional Text (RTL/LTR mixing)&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Complex Text (contextual shaping/reordering)&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Main Region&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Key Characteristics&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Arabic&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Middle East, North Africa&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;RTL direction; letters take initial/medial/final/isolated forms, with ligatures&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Hebrew&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Israel&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;RTL direction; vowel marks (niqqud) require positioning, often mixed with English&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Persian (Farsi)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Iran&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Arabic script variant, RTL, contextual shaping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Urdu&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Pakistan, India&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Arabic script, RTL, contextual shaping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Uyghur&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Xinjiang, China&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Arabic script, RTL, contextual shaping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Syriac&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Parts of the Middle East&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;RTL; complex ligatures and vowel marks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Dhivehi (Thaana)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Maldives&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;RTL direction; glyphs are largely independent, no complex shaping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Yiddish&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Global Jewish communities&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Written in Hebrew script, RTL; vowel mark positioning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Kurdish&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Iraq, Syria, etc.&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Some dialects use Arabic script, RTL, contextual shaping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Sindhi&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Pakistan, India&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Arabic script, RTL, contextual shaping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Hindi (Devanagari)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;India&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;LTR; consonant clusters, vowel sign reordering&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Bengali&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Bangladesh, India&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;LTR; consonant clusters and shaping rules&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Tamil&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;South India, Sri Lanka&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;LTR; consonant-vowel combinations and ligatures&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Thai&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Thailand&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;LTR; stacked vowels, tone mark positioning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Tibetan&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Tibet, Qinghai, etc., China&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;LTR; stacked consonants, vowel sign shaping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Mongolian (Traditional)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Inner Mongolia (China), Mongolia&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Vertical writing; initial/medial/final shaping (direction differs from RTL, it is vertical)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Khmer&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Cambodia&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;LTR; complex consonant clusters and vowel reordering&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Burmese&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Myanmar&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;LTR; complex shaping similar to Khmer&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;English, Chinese, etc.&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;No&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Global&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Basic LTR, characters and glyphs largely one-to-one&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;key-points&quot;&gt;Key Points&lt;/h3&gt;

&lt;p&gt;Dedicated shaping libraries like HarfBuzz and bidirectional algorithm libraries can support the processing of various languages. Here are some key points.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Bidirectional ⊂ Complex, but not fully overlapping&lt;/strong&gt;: Arabic, Hebrew, etc. are both bidirectional and complex; Dhivehi is bidirectional but not complex; Tibetan, Thai are complex but not bidirectional.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scope of HarfBuzz&lt;/strong&gt;: All languages marked “Complex = Yes” can receive shaping support from HarfBuzz (glyph selection, ligatures, positioning).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scope of Bidi algorithms&lt;/strong&gt;: All languages marked “Bidirectional = Yes” require additional Bidi algorithms (such as ICU, FriBidi) for direction segmentation; HarfBuzz itself does not do this.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Special case of Mongolian&lt;/strong&gt;: Traditional Mongolian is written vertically, and its direction is neither standard LTR nor RTL, but it is still complex text, and HarfBuzz also supports its contextual shaping.&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;Related reading: &lt;a href=&quot;./how-to-determine-the-reading-order-of-text-in-image.html&quot;&gt;How to Determine the Reading Order of Text in an Image&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 17 Sep 2026 07:30:00 +0000</pubDate>
        <link>https://www.basiccat.org/arabic-bidirectional-text-support-for-OCR-tool/</link>
        <guid isPermaLink="true">https://www.basiccat.org/arabic-bidirectional-text-support-for-OCR-tool/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Use a Self-Hosted ImageTrans Server with a Self-Signed Certificate on iOS</title>
        <description>&lt;p&gt;In &lt;a href=&quot;/iphone-browser-extension-doujin-manga-image-translation/&quot;&gt;a previous post&lt;/a&gt;, I explained how to use the Orion Browser on an iPhone together with an ImageTrans server and the ImageTrans browser extension to translate images on web pages.&lt;/p&gt;

&lt;p&gt;This post covers the actual setup. Here is what you need to do.&lt;/p&gt;

&lt;p&gt;On your computer:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;In ImageTrans, go to Preferences - Server and generate a self-signed certificate. Make sure to fill in the correct LAN address of your computer.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/server-preference.png&quot; alt=&quot;&quot; width=&quot;960&quot; height=&quot;780&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/server-self-cert-creation.png&quot; alt=&quot;&quot; width=&quot;882&quot; height=&quot;573&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Start the local server from the Server menu.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/server.png&quot; alt=&quot;&quot; width=&quot;979&quot; height=&quot;422&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Click the LAN link in the server window to show a QR code, then scan it with your phone to open the server. If the IP is wrong, find the correct LAN IP yourself and enter the address manually, for example https://192.168.0.169:51043.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/qr.png&quot; alt=&quot;&quot; width=&quot;557&quot; height=&quot;706&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On your phone:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Once the page is open, find the line that says &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Install self-signed cert &lt;/code&gt; and click it to download the certificate.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/install-certificate.jpg&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;1334&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Open Settings. You will see a “Profile Downloaded” option. Open it and install the downloaded certificate.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/settings.PNG&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;1334&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/install-profile.PNG&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;1334&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/vpn-device-management.PNG&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;1334&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;After that, the installed certificate will appear under VPN &amp;amp; Device Management.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Next, go to Settings - General - About - Certificate Trust Settings and turn on trust for this certificate.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/certificate-trust-settings.PNG&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;1334&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Open Orion Browser, go to the ImageTrans extension settings, and set the server address to the LAN IP address of your computer, for example https://192.168.0.169:51043. Replace 192.168.0.169 with your own IP. It is usually the address shown in the server’s QR code; if it is wrong, find the correct IP yourself.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/extension-server-settings.PNG&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;1334&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Choose ImageTrans as the translation method, and you can start translating.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/album/certificate/extension-translation-settings.jpg&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;1334&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;external-network-sharing&quot;&gt;External Network Sharing&lt;/h2&gt;

&lt;p&gt;You can use the intranet penetration service provided by ngrok to perform translation on your home computer when you are outside. Since ngrok provides an HTTPS certificate, the certificate installation steps above can also be skipped.&lt;/p&gt;
</description>
        <pubDate>Wed, 16 Sep 2026 12:37:50 +0000</pubDate>
        <link>https://www.basiccat.org/use-self-hosted-imagetrans-server-with-self-signed-certificate-for-ios/</link>
        <guid isPermaLink="true">https://www.basiccat.org/use-self-hosted-imagetrans-server-with-self-signed-certificate-for-ios/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>How to Translate Japanese Manga Sound Effects</title>
        <description>&lt;p&gt;Japanese makes heavy use of onomatopoeia and mimetic words — they make the language vivid, but they also pose plenty of challenges for translators.&lt;/p&gt;

&lt;p&gt;Below we list Japanese onomatopoeia and mimetic words by category, together with reference translations in both &lt;strong&gt;Chinese&lt;/strong&gt; and &lt;strong&gt;English&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;common-onomatopoeia-and-mimetic-words&quot;&gt;Common Onomatopoeia and Mimetic Words&lt;/h2&gt;

&lt;h3 id=&quot;1-擬音語-giongo--imitating-real-sounds&quot;&gt;1. 擬音語 (Giongo) — Imitating Real Sounds&lt;/h3&gt;

&lt;h4 id=&quot;animal-calls&quot;&gt;Animal Calls&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Reading&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ワンワン&lt;/td&gt;
      &lt;td&gt;wanwan&lt;/td&gt;
      &lt;td&gt;汪汪&lt;/td&gt;
      &lt;td&gt;woof woof&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ニャーニャー&lt;/td&gt;
      &lt;td&gt;nyānyā&lt;/td&gt;
      &lt;td&gt;喵喵&lt;/td&gt;
      &lt;td&gt;meow meow&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;モーモー&lt;/td&gt;
      &lt;td&gt;mōmō&lt;/td&gt;
      &lt;td&gt;哞哞&lt;/td&gt;
      &lt;td&gt;moo moo&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;コケコッコー&lt;/td&gt;
      &lt;td&gt;kokekokkō&lt;/td&gt;
      &lt;td&gt;咯咯咯&lt;/td&gt;
      &lt;td&gt;cock-a-doodle-doo&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ケロケロ&lt;/td&gt;
      &lt;td&gt;kerokero&lt;/td&gt;
      &lt;td&gt;呱呱&lt;/td&gt;
      &lt;td&gt;ribbit ribbit&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;チューチュー&lt;/td&gt;
      &lt;td&gt;chūchū&lt;/td&gt;
      &lt;td&gt;吱吱&lt;/td&gt;
      &lt;td&gt;squeak squeak&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ヒヒーン&lt;/td&gt;
      &lt;td&gt;hihīn&lt;/td&gt;
      &lt;td&gt;咴咴&lt;/td&gt;
      &lt;td&gt;neigh&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;nature-and-weather&quot;&gt;Nature and Weather&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Reading&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ザーザー&lt;/td&gt;
      &lt;td&gt;zāzā&lt;/td&gt;
      &lt;td&gt;哗哗（大雨）&lt;/td&gt;
      &lt;td&gt;pouring&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;シトシト&lt;/td&gt;
      &lt;td&gt;shitoshiro&lt;/td&gt;
      &lt;td&gt;淅淅沥沥&lt;/td&gt;
      &lt;td&gt;drizzling&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ゴロゴロ&lt;/td&gt;
      &lt;td&gt;gorogoro&lt;/td&gt;
      &lt;td&gt;轰隆隆&lt;/td&gt;
      &lt;td&gt;rumble&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ピカピカ&lt;/td&gt;
      &lt;td&gt;pikapika&lt;/td&gt;
      &lt;td&gt;一闪一闪&lt;/td&gt;
      &lt;td&gt;flash / sparkle&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ヒュー&lt;/td&gt;
      &lt;td&gt;hyū&lt;/td&gt;
      &lt;td&gt;呼——（风声）&lt;/td&gt;
      &lt;td&gt;whoosh&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ポツポツ&lt;/td&gt;
      &lt;td&gt;potsupotsu&lt;/td&gt;
      &lt;td&gt;滴滴答答&lt;/td&gt;
      &lt;td&gt;pitter-patter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;パラパラ&lt;/td&gt;
      &lt;td&gt;parapara&lt;/td&gt;
      &lt;td&gt;噼里啪啦（小雨）&lt;/td&gt;
      &lt;td&gt;sprinkling&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;objects-and-actions&quot;&gt;Objects and Actions&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Reading&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;バン&lt;/td&gt;
      &lt;td&gt;ban&lt;/td&gt;
      &lt;td&gt;砰&lt;/td&gt;
      &lt;td&gt;bang&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ドカーン&lt;/td&gt;
      &lt;td&gt;dokān&lt;/td&gt;
      &lt;td&gt;轰&lt;/td&gt;
      &lt;td&gt;boom&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ガシャーン&lt;/td&gt;
      &lt;td&gt;gashān&lt;/td&gt;
      &lt;td&gt;哗啦（碎裂）&lt;/td&gt;
      &lt;td&gt;crash / smash&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ドンドン&lt;/td&gt;
      &lt;td&gt;dondon&lt;/td&gt;
      &lt;td&gt;咚咚&lt;/td&gt;
      &lt;td&gt;thump thump&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;カチカチ&lt;/td&gt;
      &lt;td&gt;kachikachi&lt;/td&gt;
      &lt;td&gt;咔哒咔哒&lt;/td&gt;
      &lt;td&gt;tick-tock / clatter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;シュッ&lt;/td&gt;
      &lt;td&gt;shu&lt;/td&gt;
      &lt;td&gt;嗖&lt;/td&gt;
      &lt;td&gt;swish&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;パチパチ&lt;/td&gt;
      &lt;td&gt;pachipachi&lt;/td&gt;
      &lt;td&gt;噼啪 / 啪啪（鼓掌）&lt;/td&gt;
      &lt;td&gt;crackle / clap&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ゴトゴト&lt;/td&gt;
      &lt;td&gt;gotogoto&lt;/td&gt;
      &lt;td&gt;咕咚咕咚&lt;/td&gt;
      &lt;td&gt;rattle&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ガタガタ&lt;/td&gt;
      &lt;td&gt;gatagata&lt;/td&gt;
      &lt;td&gt;嘎吱嘎吱 / 哐当&lt;/td&gt;
      &lt;td&gt;clatter / rattle&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;2-擬態語-gitaigo--describing-states-and-appearances&quot;&gt;2. 擬態語 (Gitaigo) — Describing States and Appearances&lt;/h3&gt;

&lt;h4 id=&quot;visual-states&quot;&gt;Visual States&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Reading&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;キラキラ&lt;/td&gt;
      &lt;td&gt;kirakira&lt;/td&gt;
      &lt;td&gt;闪闪发光&lt;/td&gt;
      &lt;td&gt;sparkle / glitter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ピカピカ&lt;/td&gt;
      &lt;td&gt;pikapika&lt;/td&gt;
      &lt;td&gt;亮晶晶&lt;/td&gt;
      &lt;td&gt;shiny / gleaming&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;フワフワ&lt;/td&gt;
      &lt;td&gt;fuwafuwa&lt;/td&gt;
      &lt;td&gt;软绵绵 / 蓬松&lt;/td&gt;
      &lt;td&gt;fluffy / soft&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ツルツル&lt;/td&gt;
      &lt;td&gt;tsurutsuru&lt;/td&gt;
      &lt;td&gt;滑溜溜&lt;/td&gt;
      &lt;td&gt;smooth / slippery&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ザラザラ&lt;/td&gt;
      &lt;td&gt;zarazara&lt;/td&gt;
      &lt;td&gt;粗糙&lt;/td&gt;
      &lt;td&gt;rough / gritty&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ベトベト&lt;/td&gt;
      &lt;td&gt;betobeto&lt;/td&gt;
      &lt;td&gt;黏糊糊&lt;/td&gt;
      &lt;td&gt;sticky&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;グチャグチャ&lt;/td&gt;
      &lt;td&gt;guchagucha&lt;/td&gt;
      &lt;td&gt;乱七八糟 / 黏糊糊&lt;/td&gt;
      &lt;td&gt;messy / squishy&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ボロボロ&lt;/td&gt;
      &lt;td&gt;boroboro&lt;/td&gt;
      &lt;td&gt;破破烂烂&lt;/td&gt;
      &lt;td&gt;worn-out / tattered&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ツヤツヤ&lt;/td&gt;
      &lt;td&gt;tsuyatsuya&lt;/td&gt;
      &lt;td&gt;油光发亮&lt;/td&gt;
      &lt;td&gt;glossy&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;カラカラ&lt;/td&gt;
      &lt;td&gt;karakara&lt;/td&gt;
      &lt;td&gt;干巴巴&lt;/td&gt;
      &lt;td&gt;dry / parched&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;manner-and-movement&quot;&gt;Manner and Movement&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Reading&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ゆっくり&lt;/td&gt;
      &lt;td&gt;yukkuri&lt;/td&gt;
      &lt;td&gt;慢慢地&lt;/td&gt;
      &lt;td&gt;slowly&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;そっと&lt;/td&gt;
      &lt;td&gt;sotto&lt;/td&gt;
      &lt;td&gt;轻轻地 / 悄悄地&lt;/td&gt;
      &lt;td&gt;gently / quietly&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;じっと&lt;/td&gt;
      &lt;td&gt;jitto&lt;/td&gt;
      &lt;td&gt;一动不动 / 盯着&lt;/td&gt;
      &lt;td&gt;still / staring&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ぐっすり&lt;/td&gt;
      &lt;td&gt;gussuri&lt;/td&gt;
      &lt;td&gt;酣睡&lt;/td&gt;
      &lt;td&gt;soundly&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ふらふら&lt;/td&gt;
      &lt;td&gt;furafura&lt;/td&gt;
      &lt;td&gt;摇摇晃晃 / 晕乎乎&lt;/td&gt;
      &lt;td&gt;dizzy / wobbly&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ぶらぶら&lt;/td&gt;
      &lt;td&gt;burabura&lt;/td&gt;
      &lt;td&gt;溜达 / 闲逛&lt;/td&gt;
      &lt;td&gt;strolling / wandering&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ゴロゴロ&lt;/td&gt;
      &lt;td&gt;gorogoro&lt;/td&gt;
      &lt;td&gt;懒洋洋地躺&lt;/td&gt;
      &lt;td&gt;lazing around&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;パクパク&lt;/td&gt;
      &lt;td&gt;pakupaku&lt;/td&gt;
      &lt;td&gt;大口大口（吃）&lt;/td&gt;
      &lt;td&gt;munching&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ペラペラ&lt;/td&gt;
      &lt;td&gt;perapera&lt;/td&gt;
      &lt;td&gt;流利（说话）&lt;/td&gt;
      &lt;td&gt;fluently&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ドサドサ&lt;/td&gt;
      &lt;td&gt;dosadosa&lt;/td&gt;
      &lt;td&gt;哗啦哗啦（倒下）&lt;/td&gt;
      &lt;td&gt;thudding&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;3-擬情語-gijōgo--describing-feelings-and-emotions&quot;&gt;3. 擬情語 (Gijōgo) — Describing Feelings and Emotions&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Reading&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ドキドキ&lt;/td&gt;
      &lt;td&gt;dokidoki&lt;/td&gt;
      &lt;td&gt;扑通扑通 / 心怦怦跳&lt;/td&gt;
      &lt;td&gt;thump thump&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ワクワク&lt;/td&gt;
      &lt;td&gt;wakuwaku&lt;/td&gt;
      &lt;td&gt;兴奋不已&lt;/td&gt;
      &lt;td&gt;excited / thrilled&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;イライラ&lt;/td&gt;
      &lt;td&gt;iraira&lt;/td&gt;
      &lt;td&gt;烦躁 / 心急火燎&lt;/td&gt;
      &lt;td&gt;irritated / frustrated&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ムカムカ&lt;/td&gt;
      &lt;td&gt;mukamuka&lt;/td&gt;
      &lt;td&gt;恶心 / 火冒三丈&lt;/td&gt;
      &lt;td&gt;annoyed / queasy&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ウキウキ&lt;/td&gt;
      &lt;td&gt;ukiuki&lt;/td&gt;
      &lt;td&gt;兴高采烈&lt;/td&gt;
      &lt;td&gt;cheerful / buoyant&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ハラハラ&lt;/td&gt;
      &lt;td&gt;harahara&lt;/td&gt;
      &lt;td&gt;提心吊胆&lt;/td&gt;
      &lt;td&gt;anxious / on edge&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;グスン&lt;/td&gt;
      &lt;td&gt;gusun&lt;/td&gt;
      &lt;td&gt;抽泣 / 吸鼻子&lt;/td&gt;
      &lt;td&gt;sniffle&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ニコニコ&lt;/td&gt;
      &lt;td&gt;nikoniko&lt;/td&gt;
      &lt;td&gt;笑眯眯&lt;/td&gt;
      &lt;td&gt;smiling&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;クヨクヨ&lt;/td&gt;
      &lt;td&gt;kuyokuyo&lt;/td&gt;
      &lt;td&gt;闷闷不乐 / 纠结&lt;/td&gt;
      &lt;td&gt;moping / brooding&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ズキズキ&lt;/td&gt;
      &lt;td&gt;zukizuki&lt;/td&gt;
      &lt;td&gt;一阵阵刺痛&lt;/td&gt;
      &lt;td&gt;throbbing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ソワソワ&lt;/td&gt;
      &lt;td&gt;sowasowa&lt;/td&gt;
      &lt;td&gt;坐立不安&lt;/td&gt;
      &lt;td&gt;restless / fidgety&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ガッカリ&lt;/td&gt;
      &lt;td&gt;gakkari&lt;/td&gt;
      &lt;td&gt;失望 / 泄气&lt;/td&gt;
      &lt;td&gt;disappointed&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;translation-methods&quot;&gt;Translation Methods&lt;/h2&gt;

&lt;p&gt;Japanese has an enormous number of onomatopoeic and mimetic words, and many of them have no ready-made equivalent in Chinese or English. Translators therefore usually fall back on two core strategies: &lt;strong&gt;replacing them with an onomatopoeic word&lt;/strong&gt;, or &lt;strong&gt;rendering them descriptively&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;1-replacing-with-an-onomatopoeic-word&quot;&gt;1. Replacing with an Onomatopoeic Word&lt;/h3&gt;

&lt;p&gt;Find an onomatopoeic word in the target language that matches the original. There are two ways to do this.&lt;/p&gt;

&lt;h4 id=&quot;11-use-an-existing-onomatopoeic-word-in-the-target-language&quot;&gt;1.1 Use an Existing Onomatopoeic Word in the Target Language&lt;/h4&gt;

&lt;p&gt;If the target language has an onomatopoeic word close in meaning and feel, simply substitute it.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ドキドキ&lt;/td&gt;
      &lt;td&gt;扑通扑通&lt;/td&gt;
      &lt;td&gt;Thump thump / Ba-dump&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;バン&lt;/td&gt;
      &lt;td&gt;砰！&lt;/td&gt;
      &lt;td&gt;BAM!&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ドカーン&lt;/td&gt;
      &lt;td&gt;轰！&lt;/td&gt;
      &lt;td&gt;BOOM!&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;シュッ&lt;/td&gt;
      &lt;td&gt;嗖！&lt;/td&gt;
      &lt;td&gt;Swish&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ワンワン&lt;/td&gt;
      &lt;td&gt;汪汪&lt;/td&gt;
      &lt;td&gt;Woof woof&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ニャーニャー&lt;/td&gt;
      &lt;td&gt;喵喵&lt;/td&gt;
      &lt;td&gt;Meow meow&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ザーザー&lt;/td&gt;
      &lt;td&gt;哗哗&lt;/td&gt;
      &lt;td&gt;Pouring&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ガシャーン&lt;/td&gt;
      &lt;td&gt;哗啦！&lt;/td&gt;
      &lt;td&gt;Crash!&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;12-transliteration-romaji-plus-a-gloss&quot;&gt;1.2 Transliteration (Romaji) Plus a Gloss&lt;/h4&gt;

&lt;p&gt;If no suitable onomatopoeic word exists in the target language, or you want to keep the Japanese flavor, you can keep the romaji transliteration and add a gloss to convey the meaning. This approach is very common in fan translations.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ドキドキ&lt;/td&gt;
      &lt;td&gt;多基多基（心跳声）&lt;/td&gt;
      &lt;td&gt;Doki doki (heartbeat)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ギャー&lt;/td&gt;
      &lt;td&gt;嘎——！（惨叫）&lt;/td&gt;
      &lt;td&gt;Gyaaah! (scream)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ワクワク&lt;/td&gt;
      &lt;td&gt;哇库哇库（兴奋）&lt;/td&gt;
      &lt;td&gt;Waku waku (excitement)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;2-rendering-descriptively&quot;&gt;2. Rendering Descriptively&lt;/h3&gt;

&lt;p&gt;If there is no matching onomatopoeic word and a transliteration would only get in the way of understanding, describe the sound, state or action in ordinary words. This is the usual approach for gitaigo and gijōgo.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Japanese&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;しっとり&lt;/td&gt;
      &lt;td&gt;轻轻地 / 柔和地&lt;/td&gt;
      &lt;td&gt;softly&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;じっと&lt;/td&gt;
      &lt;td&gt;一动不动地盯着&lt;/td&gt;
      &lt;td&gt;staring intently&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;そっと&lt;/td&gt;
      &lt;td&gt;悄悄地 / 轻轻地&lt;/td&gt;
      &lt;td&gt;quietly / gently&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ぐっすり&lt;/td&gt;
      &lt;td&gt;沉沉地（睡着）&lt;/td&gt;
      &lt;td&gt;soundly&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;シーン&lt;/td&gt;
      &lt;td&gt;一片寂静&lt;/td&gt;
      &lt;td&gt;silence&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ニコニコ&lt;/td&gt;
      &lt;td&gt;笑眯眯&lt;/td&gt;
      &lt;td&gt;smiling&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ワクワク&lt;/td&gt;
      &lt;td&gt;兴奋不已&lt;/td&gt;
      &lt;td&gt;excited&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;a-real-example&quot;&gt;A Real Example&lt;/h2&gt;

&lt;p&gt;Now that we have seen the examples above, let’s translate an actual image, taken from volume 18 of &lt;em&gt;Detective Conan&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/18_094.jpg&quot; alt=&quot;&quot; width=&quot;707&quot; height=&quot;1100&quot; /&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Sound effect&lt;/th&gt;
      &lt;th&gt;Position&lt;/th&gt;
      &lt;th&gt;Chinese&lt;/th&gt;
      &lt;th&gt;English&lt;/th&gt;
      &lt;th&gt;Approach&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ガラ&lt;/td&gt;
      &lt;td&gt;Top right&lt;/td&gt;
      &lt;td&gt;哗啦 / 嘎啦&lt;/td&gt;
      &lt;td&gt;Clack / Rattle&lt;/td&gt;
      &lt;td&gt;Onomatopoeic replacement&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ワー&lt;/td&gt;
      &lt;td&gt;Middle panel&lt;/td&gt;
      &lt;td&gt;哇——！&lt;/td&gt;
      &lt;td&gt;Waah!&lt;/td&gt;
      &lt;td&gt;Onomatopoeic replacement&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;スタ (×3)&lt;/td&gt;
      &lt;td&gt;Bottom left&lt;/td&gt;
      &lt;td&gt;嗒、嗒、嗒&lt;/td&gt;
      &lt;td&gt;Tap, tap, tap&lt;/td&gt;
      &lt;td&gt;Onomatopoeic replacement&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;In practice you’ll find that these are not always familiar four-kana sound effects — the author builds them on common expressions. That is when you have to translate flexibly.&lt;/p&gt;

&lt;h2 id=&quot;typesetting-the-result&quot;&gt;Typesetting the Result&lt;/h2&gt;

&lt;p&gt;Below we use the computer-aided comic translation software &lt;a href=&quot;/imagetrans/&quot;&gt;ImageTrans&lt;/a&gt; to remove the text from the original image and put the translation back in.&lt;/p&gt;

&lt;p&gt;The cleaned image with the text removed (using Lama Inpaint):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/18_094.jpg-text-removed.jpg&quot; alt=&quot;&quot; width=&quot;707&quot; height=&quot;1100&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Our English version:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/target-en.jpg&quot; alt=&quot;&quot; width=&quot;707&quot; height=&quot;1100&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The official English translation:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/manga-sound-effect/official-translation.png&quot; alt=&quot;&quot; width=&quot;1066&quot; height=&quot;1600&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve included the official translation here as well. As you can see, it translates the sound effects too, though the words it uses seem to be made up.&lt;/p&gt;
</description>
        <pubDate>Sun, 13 Sep 2026 08:12:50 +0000</pubDate>
        <link>https://www.basiccat.org/how-to-translate-japanese-manga-sound-effects/</link>
        <guid isPermaLink="true">https://www.basiccat.org/how-to-translate-japanese-manga-sound-effects/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>How to Determine the Reading Order of Text in an Image</title>
        <description>&lt;p&gt;Text in an image has to go through OCR before it can be extracted. After that, it usually still needs to be sorted into reading order. PDFs have a similar need: even though the text can be copied, it takes sensible ordering by the PDF viewer to support selecting, copying and searching continuous runs of text. This article mainly demonstrates how the computer-aided image translation software &lt;a href=&quot;/imagetrans/&quot;&gt;ImageTrans&lt;/a&gt; handles the text order in various kinds of images.&lt;/p&gt;

&lt;p&gt;For ordinary paragraph text, where lines are simply stacked one after the other, things are easy: just sort by the Y coordinate.&lt;/p&gt;

&lt;p&gt;For vertical text such as Japanese light novels, we instead sort by the X coordinate, and from right to left.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/vertical-japanese-pdf-imagetrans-ui.jpg&quot; alt=&quot;&quot; width=&quot;1600&quot; height=&quot;1128&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But real-world images are often far more varied.&lt;/p&gt;

&lt;p&gt;Japanese manga, for example, requires determining the reading order of the panels first, and then the reading order of the text. This needs ImageTrans’s panel detection feature.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/reading-order/manga-with-imagetrans-ui.jpg&quot; alt=&quot;&quot; width=&quot;1600&quot; height=&quot;1128&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Academic papers are another case. They require layout analysis to deal with two-column layouts, figures and tables, and other page elements. Layout analysis models such as PaddleOCR’s PPDocLayout v3 are needed here. This model comes with its own reading order detection, though not every result is correct, and occasionally you have to fix it manually in the software.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/pdf-translation/panel-detected.jpg&quot; alt=&quot;&quot; width=&quot;1599&quot; height=&quot;1140&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If the layout detection method you choose does not support reading order detection, we need to use another sorting method, such as XYCut.&lt;/p&gt;

&lt;p&gt;There are also many complex images, such as the infographic below, in which the listed items are ordered clockwise. Only a vision-capable large language model can handle this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/gallery/projects/infographic/capture001.webp&quot; alt=&quot;&quot; width=&quot;1219&quot; height=&quot;1600&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But since large language models cannot return coordinates directly, we also need a suitable prompt that matches the order they give with the text at the corresponding coordinates.&lt;/p&gt;
</description>
        <pubDate>Sun, 13 Sep 2026 03:38:50 +0000</pubDate>
        <link>https://www.basiccat.org/how-to-determine-the-reading-order-of-text-in-image/</link>
        <guid isPermaLink="true">https://www.basiccat.org/how-to-determine-the-reading-order-of-text-in-image/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>How to Detect Rotated Text in an Image</title>
        <description>&lt;p&gt;Text in an image may be tilted. Sometimes this is a deliberate design choice, and sometimes it happens because a document was not placed straight when it was scanned.&lt;/p&gt;

&lt;p&gt;Most OCR engines today can handle rotated text. PaddleOCR, for example, uses the DB algorithm to produce a binary image, and from that image we can get each line of text together with the coordinates of its four corner points and its rotation information.&lt;/p&gt;

&lt;p&gt;The computer-aided image translation software &lt;a href=&quot;/imagetrans/&quot;&gt;ImageTrans&lt;/a&gt; supports all kinds of OCR that handle rotated text.&lt;/p&gt;

&lt;p&gt;The interface:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/rotated-text/rotated-text-with-imagetrans-ui.jpg&quot; alt=&quot;&quot; width=&quot;1600&quot; height=&quot;1128&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The binary image:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/rotated-text/db-thresh.png&quot; alt=&quot;&quot; width=&quot;1056&quot; height=&quot;1504&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Image source: &lt;a href=&quot;https://ac.qq.com/ComicView/index/id/547921/cid/1&quot;&gt;https://ac.qq.com/ComicView/index/id/547921/cid/1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Knowing the rotation angle is useful in more than one way: it can be used for document deskewing, and for restoring the rotation angle when translating an image.&lt;/p&gt;

&lt;p&gt;The text in the example image, however, is distorted by a perspective transformation, so for real translation work it is still better to edit the original PSD file. ImageTrans can export the recognized text to PSD, or modify the text in a PSD directly.&lt;/p&gt;

&lt;p&gt;OCR engines with rotation support in ImageTrans:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;WinRT (the OCR built into Windows 10 and later)&lt;/li&gt;
  &lt;li&gt;rapid&lt;/li&gt;
  &lt;li&gt;PaddleOCR&lt;/li&gt;
  &lt;li&gt;mangaTranslator&lt;/li&gt;
  &lt;li&gt;macOCR (the OCR built into macOS)&lt;/li&gt;
  &lt;li&gt;oneocr (the OCR built into the Windows 11 Snipping Tool)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;90-degree-rotation&quot;&gt;90-degree rotation&lt;/h2&gt;

&lt;p&gt;One kind of rotation is special: a 90-degree rotation. When Western letters are set vertically, the text has to be rotated 90 degrees, and its angle can no longer be calculated simply from the coordinates of the four corner points. You can detect it with an orientation detection model, or just work it out from the aspect ratio: if the height divided by the width is greater than, say, 1.2, treat it as a 90-degree rotation.&lt;/p&gt;
</description>
        <pubDate>Sun, 13 Sep 2026 03:38:50 +0000</pubDate>
        <link>https://www.basiccat.org/how-to-detect-rotated-text-in-image/</link>
        <guid isPermaLink="true">https://www.basiccat.org/how-to-detect-rotated-text-in-image/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Turn Scanned Documents into Searchable PDFs with a Text Layer</title>
        <description>&lt;p&gt;Scanned documents are usually single-layer, pure-image PDFs whose text cannot be selected, copied or searched. In this case, we can create a two-layer PDF, by putting a hidden text layer over the images, so that when the PDF is opened in a PDF viewer, the text inside it can be searched and copied.&lt;/p&gt;

&lt;p&gt;For example, the PDF below shows a part of the Japanese light novel &lt;em&gt;Mushoku Tensei&lt;/em&gt; (&lt;a href=&quot;https://www.kadokawa.co.jp/product/301312000360/&quot;&gt;source&lt;/a&gt;). The text inside is vertical, read from right to left, and has already been extracted and turned into a two-layer PDF.&lt;/p&gt;

&lt;iframe src=&quot;/assets/mushoku_tensei.pdf&quot; width=&quot;240px&quot; height=&quot;320px&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Let me explain how this is done step by step. We need &lt;a href=&quot;/imagetrans/&quot;&gt;ImageTrans&lt;/a&gt;, an OCR software.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open ImageTrans, create a new project, and import the PDF or images.&lt;/li&gt;
  &lt;li&gt;Choose an OCR engine, such as PaddleOCR (named rapid in ImageTrans), to recognize the text. If the recognition result is not accurate, we can first use PaddleOCR or other methods to locate the text, and then use large models such as PaddleOCR-VL and minerU, or a dedicated vertical-up Japanese model such as mit48px ctc, to recognize the text.&lt;/li&gt;
  &lt;li&gt;Sort the recognized text according to the reading order. Here we just need to enable right-to-left reading mode and sort by the X coordinate in the project settings. If the layout is complex, e.g., with multiple columns, we can run document layout analysis with software like minerU and PPDocLayout before sorting.&lt;/li&gt;
  &lt;li&gt;Detect which text is horizontal and which text is vertical. We need to define two font styles in the project settings, one for horizontal text and one for vertical text. Then run the “Detect text direction and apply the corresponding style” workflow in the custom workflow.&lt;/li&gt;
  &lt;li&gt;In the menu bar, click Export -&amp;gt; Raster PDF. Use the original images, add a text layer with the source text, and choose a font that contains Japanese characters, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\Windows\Fonts\arial unicode ms.ttf&lt;/code&gt; on Windows, a font that covers nearly all languages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After saving, we get a PDF like the one above. The software will automatically work out an appropriate font size so that the text in the text layer fits the text in the images as well as possible.&lt;/p&gt;

&lt;p&gt;Open it in Edge, and the text can be correctly selected, copied and searched.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/edge-pdf-reading.jpg&quot; alt=&quot;&quot; width=&quot;1600&quot; height=&quot;1128&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The ImageTrans interface:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/album/vertical-japanese-pdf-imagetrans-ui.jpg&quot; alt=&quot;&quot; width=&quot;1600&quot; height=&quot;1128&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ImageTrans can also further translate the images, giving us the following Chinese searchable two-layer PDF.&lt;/p&gt;

&lt;iframe src=&quot;/assets/mushoku_tensei_zh.pdf&quot; width=&quot;240px&quot; height=&quot;320px&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;about-adding-a-vertical-text-layer&quot;&gt;About Adding a Vertical Text Layer&lt;/h2&gt;

&lt;p&gt;PDF’s native support for vertical text is weak. When creating the text layer, we need to add characters one by one in reading order. I have tried it, and common PDF viewers can all display vertical text added in this way.&lt;/p&gt;
</description>
        <pubDate>Wed, 02 Sep 2026 13:19:50 +0000</pubDate>
        <link>https://www.basiccat.org/searchable-pdf-with-text-layer-from-image-pdf/</link>
        <guid isPermaLink="true">https://www.basiccat.org/searchable-pdf-with-text-layer-from-image-pdf/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>How ImageTrans Optimizes Processing Speed and AI Models</title>
        <description>&lt;p&gt;ImageTrans is only 400 MB in size, yet it bundles PaddleOCR supporting over a hundred languages, a dedicated Japanese manga OCR, the mit48px_ctc OCR model with color recognition, YOLO text/bubble detection models, and the inpainting model Lama Inpaint. No GPU is required — it fully utilizes the CPU, and translating an image usually takes under 10 seconds.&lt;/p&gt;

&lt;p&gt;Below are some of the optimization methods used.&lt;/p&gt;

&lt;h2 id=&quot;model-quantization&quot;&gt;Model Quantization&lt;/h2&gt;

&lt;p&gt;Take the manga-ocr model: the original FP32 version is about 400 MB, while the int8-quantized version is only about 100 MB, with virtually no loss in recognition performance.&lt;/p&gt;

&lt;p&gt;Lama inpaint and mit48px ctc both use quantized versions.&lt;/p&gt;

&lt;h2 id=&quot;splitting-the-transformer-model&quot;&gt;Splitting the Transformer Model&lt;/h2&gt;

&lt;p&gt;manga-ocr is a Transformer model. It is split into encoder and decoder versions, allowing the encoder’s output to be reused and improving processing performance.&lt;/p&gt;

&lt;h2 id=&quot;multiprocessing&quot;&gt;Multiprocessing&lt;/h2&gt;

&lt;p&gt;When processing many images, multiple processes can be used to fully utilize the CPU.&lt;/p&gt;

&lt;h2 id=&quot;gpunpu-acceleration&quot;&gt;GPU/NPU Acceleration&lt;/h2&gt;

&lt;p&gt;If the device has a GPU or NPU, it can also be used to accelerate computation.&lt;/p&gt;

&lt;p&gt;On Apple devices, models can be converted to mlx or coreml versions, and processing is very fast. ImageTrans provides a coreml version of Lama Inpaint and an mlx version of PPDocLayout v3. It also supports using Pytorch with mps enabled to accelerate OCR with Metal.&lt;/p&gt;

&lt;p&gt;On Windows, if you have an Nvidia graphics card, you can also use the Pytorch versions of OCR and Lama inpaint to offload computation.&lt;/p&gt;

&lt;h2 id=&quot;performance-testing&quot;&gt;Performance Testing&lt;/h2&gt;

&lt;p&gt;Below are the results of translating various images on a MinisForum 760 Plus (AMD Ryzen 5 7640HS).&lt;/p&gt;

&lt;h3 id=&quot;black-and-white-manga&quot;&gt;Black and White Manga&lt;/h3&gt;

&lt;p&gt;Original:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.jsdmirror.com/gh/xulihang/ImageTrans-Benchmark@master/manga/capture002.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Translated:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.jsdmirror.com/gh/xulihang/ImageTrans-Benchmark@master/manga/en/capture002.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Processing time:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;workflows&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4323&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;08&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2026 20:58:58&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;steps&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2344&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Text detection&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Merge areas&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Sort&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1437&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Translate&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;489&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Generate translated images&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;color-cg&quot;&gt;Color CG&lt;/h3&gt;

&lt;p&gt;Original:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.jsdmirror.com/gh/xulihang/ImageTrans-Benchmark@master/cg/003_0101.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Translated:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.jsdmirror.com/gh/xulihang/ImageTrans-Benchmark@master/cg/en/003_0101.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Processing time:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;workflows&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8703&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;08&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2026 20:48:47&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;steps&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;236&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Text detection&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1575&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Generate masks and text-removed images for all images&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Shrink areas&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3815&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Text recognition&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Set stroke color based on text color depth&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;72&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Match style based on stroke color&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Merge areas&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Sort&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1759&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Translate&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1152&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Generate translated images&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;chinese-manhua&quot;&gt;Chinese Manhua&lt;/h3&gt;

&lt;p&gt;Original:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.jsdmirror.com/gh/xulihang/ImageTrans-Benchmark@master/manhua/capture001.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Translated:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.jsdmirror.com/gh/xulihang/ImageTrans-Benchmark@master/manhua/en/capture001.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Processing time:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;workflows&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3298&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;08&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2026 21:05:34&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;steps&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Text detection&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Remove areas without source text from all images&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Merge areas&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Sort&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1736&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Translate&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;duration_ms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;468&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Generate translated images&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 01 Sep 2026 12:08:50 +0000</pubDate>
        <link>https://www.basiccat.org/ai-performance-optimization-imagetrans/</link>
        <guid isPermaLink="true">https://www.basiccat.org/ai-performance-optimization-imagetrans/</guid>
        
        <category>imagetrans</category>
        
        
        <category>blog</category>
        
      </item>
    
  </channel>
</rss>
