{"id":20005481,"url":"https://github.com/mrgrd56/textractor-translator","last_synced_at":"2025-05-04T17:31:57.322Z","repository":{"id":112189232,"uuid":"574237685","full_name":"MRGRD56/textractor-translator","owner":"MRGRD56","description":"Translate visual novels in real time","archived":false,"fork":false,"pushed_at":"2024-11-15T05:55:18.000Z","size":2294,"stargazers_count":12,"open_issues_count":10,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-08T09:01:38.728Z","etag":null,"topics":["anime","electron","games","javascript","text-extraction","textractor","textractor-extension","translation","translator","typescript","visual-novel"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MRGRD56.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-04T20:52:14.000Z","updated_at":"2025-01-25T07:10:05.000Z","dependencies_parsed_at":"2024-03-30T21:33:16.988Z","dependency_job_id":"8e6309db-23fd-43b0-9c5c-48c48b13d660","html_url":"https://github.com/MRGRD56/textractor-translator","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MRGRD56%2Ftextractor-translator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MRGRD56%2Ftextractor-translator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MRGRD56%2Ftextractor-translator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MRGRD56%2Ftextractor-translator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MRGRD56","download_url":"https://codeload.github.com/MRGRD56/textractor-translator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252371893,"owners_count":21737421,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anime","electron","games","javascript","text-extraction","textractor","textractor-extension","translation","translator","typescript","visual-novel"],"created_at":"2024-11-13T05:41:01.623Z","updated_at":"2025-05-04T17:31:57.257Z","avatar_url":"https://github.com/MRGRD56.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Textractor Translator\n\n**Translate visual novels in real time, while reading**  \n**Customize it however you wish**\n\n\u003cimg src=\"https://user-images.githubusercontent.com/35491968/210263578-b57cb7fd-c081-4cb9-9ebd-2e09b22f1f09.png\" width=\"600\"\u003e\n\nIt works with [Textractor](https://github.com/Artikash/Textractor) letting you **configure** it for **each separate game**: **transform** the extracted text using **JavaScript**, **translate** it using a pre-defined translator or write **your own**, **stylize** the text window to **blend in** with the game.  \n\nIt can replace a huge part of the built-in `xdll` extensions for Textractor.\n\nIt allows you to perform and configure the following things for each game:\n- Parsing and transforming the text extracted by Textractor, making it readable;\n- Translating the transformed text;\n- Transforming the translated text;\n- Stylizing the text using HTML \u0026 CSS;\n- The appearance of the text window.\n\n\u003e [!NOTE]\n\u003e I don't have anything to do with the original software (Textractor) developers.\n\n## Demo\n\n![2024-04-03_22-44-40-ezgif com-optimize](https://github.com/MRGRD56/textractor-translator/assets/35491968/ed6daf3e-d76a-48d3-a813-7160b790066f)\n\n\u003cdetails\u003e\n\u003csummary\u003eView more\u003c/summary\u003e\n\n\u003cimg src=\"https://user-images.githubusercontent.com/35491968/210839740-3f1b3801-1b06-4814-9dba-0a737b7890cd.gif\" width=800\u003e\n\u003c!--![textractor-translator-v0 2 0-demo_3](https://user-images.githubusercontent.com/35491968/210839740-3f1b3801-1b06-4814-9dba-0a737b7890cd.gif)--\u003e\n    \n\u003cimg src=\"https://github.com/MRGRD56/textractor-translator/assets/35491968/f1e83c22-7810-4eaf-9c45-4eff49963fda\" width=\"800\"\u003e\n\n\u003cimg src=\"https://github.com/MRGRD56/textractor-translator/assets/35491968/576e8d87-7bd1-439c-87c2-8ad5063861a5\" width=\"800\"\u003e\n\n\u003cimg src=\"https://user-images.githubusercontent.com/35491968/216782294-7ac22557-c6a8-40c1-968f-9ad88c8ec810.png\" width=\"800\"\u003e\n\n\u003c/details\u003e\n\n## Installation\n\nCurrently, only Windows it supported.\nYou can use either `ia32` or `x64` versions depending on your OS.\nBoth versions (at least the x64 one) can work with both `Textractor x86` and `Textractor x64`.\n\n1. Download the binaries for your operating system in the [Releases](https://github.com/MRGRD56/textractor-translator/releases) section ([x86](https://github.com/MRGRD56/textractor-translator/releases/latest/download/TextractorTranslator-win32-ia32.zip) [x64](https://github.com/MRGRD56/textractor-translator/releases/latest/download/TextractorTranslator-win32-x64.zip)).  \n2. Unzip the archive to the directory where the app will be stored.\n3. Run the `TextractorTranslator.exe` file to run the application.\n4. You will see the main window of the app:  \n   ![image](https://github.com/MRGRD56/textractor-translator/assets/35491968/e9ec0a8c-d941-40a8-8eaf-88c86f4b983e)\n5. Hover the top right corner of the window, then click the \u003cimg src=\"https://github.com/MRGRD56/textractor-translator/assets/35491968/c17cea08-7af2-4427-8af8-589ce7e05f22\"\u003e\u003c/img\u003e settings button.\n6. You will see the settings window:  \n   ![image](https://github.com/MRGRD56/textractor-translator/assets/35491968/ca35e748-4d0c-4212-835d-f3ec8cee367e)\n7. Ensure you have [Textractor](https://github.com/Artikash/Textractor) installed. If you don't, install it before you continue. Remember the directory where you'll have installed it.  \n8. Having Textractor installed, click a \u003cimg src=\"https://github.com/MRGRD56/textractor-translator/assets/35491968/cff85f4b-85e8-4fdc-89b4-da397347c4c3\"\u003e\u003c/img\u003e folder button to select the `Textractor.exe` executable of Textractor.\n9. When you select either `Textractor x86` or `Textractor x64` location, the other one will be selected automatically. If not, please, select it manually if you need it.\n10. It's recommended to check the `Autorun` checkbox for the Textractor you're planning to run with. It depends on the bitness of the game process. Usually it's `x86` for visual novels.\n11. After you have configured the path(s) to Textractor, you'll have to install the [TextractorPipe](https://github.com/MRGRD56/textractor-integration-extensions) extension. It can be done automatically by clicking the install button(s):  \n    ![image](https://github.com/MRGRD56/textractor-translator/assets/35491968/733824e3-1bd8-4e23-b0aa-b8152d021dcc)\n12. After installing the extension, you can run Textractor by clicking one of the buttons:  \n    ![image](https://github.com/MRGRD56/textractor-translator/assets/35491968/1a053ddd-51fe-4666-9ae2-2e19a7366980)\n13. Now, text seen in Textractor should be displayed in Textractor Translator too:  \n    ![image](https://github.com/MRGRD56/textractor-translator/assets/35491968/c38cf2e4-8228-4a47-89aa-cd87f4706037)\n\n\u003chr\u003e\n\n### The following part of this README is supposed to be rewritten. Some info below might be obsolete.\n\n\u003cdetails\u003e\n\u003csummary\u003eHistory of this app\u003c/summary\u003e\n\n### Version 0.6.0\n\n#### Aokana `en -\u003e ru`\n\n![image](https://github.com/MRGRD56/textractor-translator/assets/35491968/d29c4f89-512b-4152-b8a5-af90e853a52a)\n\n### Version 0.5.0\n\n#### eden* PLUS+MOSAIC `en -\u003e ru`\n\n![image](https://github.com/MRGRD56/textractor-translator/assets/35491968/576e8d87-7bd1-439c-87c2-8ad5063861a5)\n\n### Version 0.2.1\n\n#### Summer Pockets `en -\u003e ru`\n\n![image](https://user-images.githubusercontent.com/35491968/216782294-7ac22557-c6a8-40c1-968f-9ad88c8ec810.png)\n\n### Version 0.2.0\n\n#### Summer Pockets `en -\u003e ru`\n\n![image](https://user-images.githubusercontent.com/35491968/210804578-bbef4152-c46c-4722-bd9e-3a6cdaadee4d.png)\n\n### Version 2023-01-03\n\n#### Aokana `en -\u003e ru`\n\n![image](https://user-images.githubusercontent.com/35491968/210275440-7ccfa536-922f-4f72-bec8-d20c7f160f20.png)\n\n### Version 2022-12-17\n\n#### Memoria `en -\u003e ru`\n\n![Без названия (4)](https://user-images.githubusercontent.com/35491968/208255633-71fe3183-2762-480d-a50c-7f88f5b69fb0.jpg)\n\n#### Aokana `en -\u003e ru`\n\n![image](https://user-images.githubusercontent.com/35491968/209694538-5e491b2f-25db-4418-b2e9-8ac6db492dab.png)\n\n### Version 2022-12-05\n\n#### White Album 2 `en -\u003e ru`\n\n![image](https://user-images.githubusercontent.com/35491968/205514998-f00fcb94-93c9-4bfd-8b73-bbbce2f1ee15.png)  \n\n\u003c/details\u003e\n\nIt also requires `TextractorPipe.xdll` extension for Textractor: https://github.com/MRGRD56/textractor-integration-extensions The app will not work without this extension installed. It can be automatically installed right in the app settings. You might need to restart Textractor for the extension to start working.\n\nYou need some JavaScript knowledge to configure and use this application.\n\nThe purpose of this software is to be able to fine-tune Textractor in terms of parsing, modifying and translating texts for each separate game.\n\nRequires `TextractorPipe.xdll` extension for Textractor: https://github.com/MRGRD56/textractor-integration-extensions\nThe app will not work without this extension installed.\n\nThe extension can be installed right in the app:  \n![image](https://user-images.githubusercontent.com/35491968/209697469-ba47b501-9c52-4a22-9c48-a43d8fb4089d.png)\n\n#### Some configs that can be used\n\n##### Common\n\n```js\nconfig.languages = {\n    source: 'en',\n    target: 'ru'\n};\n\nconst googleTranslate = Translators.GoogleTranslate();\n// const libreTranslate = Translators.LibreTranslate({\n//     host: 'https://libretranslate.example.com'\n// });\n\n// implements caching translations of not really long sentences\n// also optimizes translation if there are no English letters in text when translating from English - this kind of text returns as is\nTranslators.Custom = {};\n/**\n * @returns {Translator}\n */\nTranslators.Custom.MainTranslator = () =\u003e ({\n    translate: async (text, sourceLanguage, targetLanguage) =\u003e {\n        const doTranslate = () =\u003e googleTranslate.translate(text, sourceLanguage, targetLanguage);\n\n        if (sourceLanguage === 'en' \u0026\u0026 !/[a-z]+/i.test(text)) {\n            return text;\n        }\n\n        if (text.length \u003c= 200) {\n            const translatedCache = memory.translatedCache ??= {};\n            const cachedTranslation = translatedCache[text];\n            if (cachedTranslation === undefined) {\n                const translation = await doTranslate();\n                translatedCache[text] = translation;\n                return translation;\n            } else {\n                return cachedTranslation;\n            }\n        }\n\n        return doTranslate();\n    }\n});\n\nconfig.translator = Translators.Custom.MainTranslator();\n\nconfig.transformOriginal = ({text, meta}) =\u003e {\n    if (text.startsWith('Textractor:') || text.startsWith('vnreng:')) {\n        return undefined;\n    }\n\n    return text;\n};\n\nconfig.transformTranslated = (text) =\u003e {\n    return {\n        plain: text,\n        displayed: common.htmlifyText(text),\n        isHtml: true\n    };\n};\n\nconst nameColor = '#ef9a9a';\n\n/** @param {string} text */\ncommon.htmlifyText = (text) =\u003e {\n    return text\n        .replace(/^([^:]+?): [\"«](.+)[\"»][.!?]?$/, '\u003cspan style=\"color: ' + nameColor + ';\"\u003e$1:\u003c/span\u003e «$2»')\n};\n\n/** @param {string} text */\ncommon.htmlifyTextJa = (text) =\u003e {\n    return text\n        .replace(/^([^:]+?): 「(.+)」[.!?]?$/, '\u003cspan style=\"color: ' + nameColor + ';\"\u003e$1:\u003c/span\u003e 「$2」')\n};\n\ncommon.style = (text, css) =\u003e {\n    return `\u003cspan style=\"${css}\"\u003e${text}\u003c/span\u003e`;\n};\n```\n\n##### Siglus Engine\n\n```js\nconfig.transformOriginal = ({text, meta}) =\u003e {\n    text = commonConfig.transformOriginal({text, meta});\n    if (!text) {\n        return text;\n    }\n\n    const result = text\n        .replaceAll(/([a-z]\\d){2,}/g, '')\n        .replaceAll(/_stage_action/g, '');\n        \n    if (!result?.trim()) {\n        return;\n    }\n\n    return result;\n};\n\nconfig.transformTranslated = (text) =\u003e text\n    .replaceAll('…', '...');\n```\n\n##### Aokana EN\n\n```js\nconst {style, htmlifyText} = common;\n\nconfig.transformOriginal = ({text, meta}) =\u003e {\n    text = commonConfig.transformOriginal({text, meta});\n    if (!text) {\n        return;\n    }\n\n    const englishText = /([【　].+?)␂/.exec(text)?.[1]?.trim();\n\n    if (!englishText) {\n        return;\n    }\n\n    const plainText = englishText\n        .replace(/^【(.+?)】：(.+)$/, '$1: \"$2\"');\n\n    if (!plainText) {\n        return;\n    }\n\n    return {\n        plain: plainText,\n        displayed: htmlifyText(plainText),\n        isHtml: true\n    };\n};\n```\n\n##### Aokana JA\n\n```js\nconst {style, htmlifyText, htmlifyTextJa} = common;\n\nconfig.languages.source = 'ja';\nconfig.languages.target = 'en';\n\nconfig.transformOriginal = ({text, meta}) =\u003e {\n    text = commonConfig.transformOriginal({text, meta});\n    if (!text) {\n        return;\n    }\n\n    const japaneseText = /␂([【　]?.+?)␂/.exec(text)?.[1]?.trim();\n\n    if (!japaneseText) {\n        return;\n    }\n\n    const plainText = japaneseText\n        .replace(/^【(.+?)】：(.+)$/, '$1: $2');\n\n    if (!plainText) {\n        return;\n    }\n\n    return {\n        plain: plainText,\n        displayed: htmlifyTextJa(plainText),\n        isHtml: true\n    };\n};\n```\n\n##### White Album 2\n\n```js\n// /** @param {string} text */\n// const htmlifyText = (text) =\u003e {\n//     return text\n//         .replace(/^([^:]+?): [\"«](.+)[\"»][.!?]?$/, '\u003cspan style=\"color: #ffcdd2;\"\u003e$1:\u003c/span\u003e «$2»')\n// };\n\nconfig.transformOriginal = ({text, meta}) =\u003e {\n    text = commonConfig.transformOriginal({text, meta});\n    if (!text) {\n        return;\n    }\n\n    if (/^mv\\d+$/.test(text) || text === 'sepia.AMP') {\n        return;\n    }\n\n    const normalText = text\n        .replaceAll('~', ',')\n        .replaceAll('\\\\n', ' ')\n        .replaceAll('�c', '...')\n        .replaceAll('�`', '~')\n        .replaceAll('�[', ' - ')\n        .replaceAll('\\\\k', '❄️');\n    \n    const plainText = normalText\n        .replace(/^([^:\"]+?)\"(.+)\"$/, '$1: \"$2\"');\n\n    // const displayedText = normalText\n    //     .replace(/^(.+?)\"(.*)\"$/, '\u003cb style=\"color: #ffcdd2;\"\u003e$1:\u003c/b\u003e \"$2\"');\n    \n    return {\n        plain: plainText,\n        displayed: common.htmlifyText(plainText),\n        isHtml: true\n    };\n};\n```\n\n##### eden* PLUS+MOSAIC (English edition)\n\n```js\nconfig.transformOriginal = ({text, meta}) =\u003e {\n    text = commonConfig.transformOriginal({text, meta});\n\n    if (!text) {\n        return;\n    }\n    \n    text = text\n        .replaceAll(/\\d{4}\\/\\d{2}\\/\\d{2}\\s\\d{2}:\\d{2}/g, '')\n        .replaceAll(/\\S+\\.(png|ogg|ani)/g, '')\n        .replaceAll(/\\\\n/g, ' ')\n        .replaceAll(/\\\\\\w/g, '')\n        .replaceAll('�', '\\'')\n        .replaceAll('#4', '~')\n        .replaceAll('#5', '♪')\n        .replace(/^'(.+)'$/, '\"$1\"')\n        .trim();\n    \n    if (!text) {\n        return;\n    }\n\n    return text;\n};\n\nconfig.transformTranslated = (text) =\u003e {\n    text = text\n        .replace(/^— (.+)$/, '«$1»')\n        .replace(/^[\"'](.+)[\"']$/, '«$1»');\n\n    return text;\n};\n```\n\n##### Implementing a custom translator\n\n`net` (the node.js module), `httpRequest` (\"electron-request\" library), `queryString` (\"query-string\" library), `URL`, `URLSearchParams` variables can help you create your custom translators.\n\n```js\n/**\n * @param config {{host?: string, format?: string, apiKey?: string}}\n * @returns {Translator}\n */\nTranslators.LibreTranslateCustom = (config = {}) =\u003e ({\n    translate: async (text, sourceLanguage, targetLanguage) =\u003e {\n        const host = config.host || 'https://libretranslate.com';\n        const url = new URL('/translate', host).toString();\n\n        const responseData = await httpRequest(url, {\n            method: 'POST',\n            headers: {\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify({\n                q: text,\n                source: sourceLanguage,\n                target: targetLanguage,\n                format: config.format,\n                api_key: config.apiKey\n            })\n        }).then((response) =\u003e response.json());\n\n        if (!responseData || responseData.translatedText == null) {\n            console.error('LibreTranslator unable to translate: ', responseData);\n            throw responseData;\n        }\n\n        console.log('custom translator response:', responseData);\n        return responseData.translatedText;\n    }\n});\n```\n\n#### Ideas to be implemented in the future\n\n- ⚠️ Add a \"retry\" button if an error occurred while translating, also add auto retries (since v0.3.0 auto retries can be implemented by creating an own translator by extending the existing, in theory)\n- ⚠️ Fix dragging when history mode is enabled\n- ~Maybe return `200 OK` immediately after a reqeust (`/sentence`) to the app~ Not relevant anymore since HTTP has been replaced with named pipes\n- ~If Textractor Translator is not running, TTBridge shows errors when sending `/sentence` requests to the app, so Textractor crashes~ Probably not relevant anymore\n- ~Sometimes Google Translator works incorrectly, returning incomplete sentences as a translation, fix it if possible~ (not possible)\n- Limit history size\n- ⏬ Maybe add \"export history\" feature\n- ⏬ Maybe save history to the storage and also add \"clear history\" button\n- ⏬ Add a switch to disable automatic translation of each phrase, phrases would be translated by clicking on the button\n- ⏬ Add more appearance settings: text shadows (✅), outline (✅), text only background (✅), vertical and horizontal text alignment\n- Add a dictionary of words, you can add words there while reading and learn them later\n- Add DeepL translator, improve custom translator creating feature\n- Maybe move languages and translator settings somewhere from profiles code\n- Profiles: add translator and languages options to `config.transformOriginal`\n- ~Profiles: add `translators` object with predefined translators (objects, not names) in it~ The `DefinedTranslators` object has been added\n- ⏬ Add Google Translate extension if it's possible\n- ~Add a `global` object so that it's possible to store some global (mutable) variables~ The `memory` variable has been added for this purpose\n- ~⚠️ Fix this: https://user-images.githubusercontent.com/35491968/215345061-34eb33c0-68f2-4651-b826-422856eff69c.png~ Not relevant\n- ~Добавить возможность настраивать конфиг TTBridge (и вернуть туда JSON конфиг), включая возможность настройки порта для коммуникации TTBridge и Textractor Translator~\n- Добавить возможность настраивать TextractorPipe, включая возможность фильтрации отправляемых данных (например только `isCurrentSelect` или все без исключения)\n- Добавить перевод с контекстом для более точного перевода\n\n---\n\nSee also: https://github.com/MRGRD56/RealTimeTranslator\n\n---\n\n__`readme` coming soon...__\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrgrd56%2Ftextractor-translator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrgrd56%2Ftextractor-translator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrgrd56%2Ftextractor-translator/lists"}